Repository: keon/algorithms Branch: main Commit: a1bf1f39f95b Files: 440 Total size: 1021.7 KB Directory structure: gitextract_s6ixwhb4/ ├── .github/ │ └── workflows/ │ ├── publish.yml │ └── python-app.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── algorithms/ │ ├── __init__.py │ ├── array/ │ │ ├── __init__.py │ │ ├── delete_nth.py │ │ ├── flatten.py │ │ ├── garage.py │ │ ├── josephus.py │ │ ├── limit.py │ │ ├── longest_non_repeat.py │ │ ├── max_ones_index.py │ │ ├── merge_intervals.py │ │ ├── missing_ranges.py │ │ ├── move_zeros.py │ │ ├── n_sum.py │ │ ├── plus_one.py │ │ ├── remove_duplicates.py │ │ ├── rotate.py │ │ ├── summarize_ranges.py │ │ ├── three_sum.py │ │ ├── top_1.py │ │ ├── trimmean.py │ │ └── two_sum.py │ ├── backtracking/ │ │ ├── __init__.py │ │ ├── add_operators.py │ │ ├── anagram.py │ │ ├── array_sum_combinations.py │ │ ├── combination_sum.py │ │ ├── factor_combinations.py │ │ ├── find_words.py │ │ ├── generate_abbreviations.py │ │ ├── generate_parenthesis.py │ │ ├── letter_combination.py │ │ ├── minimax.py │ │ ├── palindrome_partitioning.py │ │ ├── pattern_match.py │ │ ├── permute.py │ │ ├── permute_unique.py │ │ ├── subsets.py │ │ └── subsets_unique.py │ ├── bit_manipulation/ │ │ ├── __init__.py │ │ ├── add_bitwise_operator.py │ │ ├── binary_gap.py │ │ ├── bit_operation.py │ │ ├── bytes_int_conversion.py │ │ ├── count_flips_to_convert.py │ │ ├── count_ones.py │ │ ├── find_difference.py │ │ ├── find_missing_number.py │ │ ├── flip_bit_longest_sequence.py │ │ ├── gray_code.py │ │ ├── has_alternative_bit.py │ │ ├── insert_bit.py │ │ ├── power_of_two.py │ │ ├── remove_bit.py │ │ ├── reverse_bits.py │ │ ├── single_number.py │ │ ├── single_number2.py │ │ ├── single_number3.py │ │ ├── subsets.py │ │ └── swap_pair.py │ ├── common/ │ │ ├── __init__.py │ │ ├── graph.py │ │ ├── list_node.py │ │ └── tree_node.py │ ├── compression/ │ │ ├── __init__.py │ │ ├── elias.py │ │ ├── huffman_coding.py │ │ └── rle_compression.py │ ├── data_structures/ │ │ ├── __init__.py │ │ ├── avl_tree.py │ │ ├── b_tree.py │ │ ├── bst.py │ │ ├── fenwick_tree.py │ │ ├── graph.py │ │ ├── hash_table.py │ │ ├── heap.py │ │ ├── iterative_segment_tree.py │ │ ├── kd_tree.py │ │ ├── linked_list.py │ │ ├── priority_queue.py │ │ ├── queue.py │ │ ├── red_black_tree.py │ │ ├── segment_tree.py │ │ ├── separate_chaining_hash_table.py │ │ ├── sqrt_decomposition.py │ │ ├── stack.py │ │ ├── trie.py │ │ ├── union_find.py │ │ └── veb_tree.py │ ├── dynamic_programming/ │ │ ├── __init__.py │ │ ├── bitmask.py │ │ ├── buy_sell_stock.py │ │ ├── climbing_stairs.py │ │ ├── coin_change.py │ │ ├── combination_sum.py │ │ ├── count_paths_dp.py │ │ ├── edit_distance.py │ │ ├── egg_drop.py │ │ ├── fib.py │ │ ├── hosoya_triangle.py │ │ ├── house_robber.py │ │ ├── int_divide.py │ │ ├── job_scheduling.py │ │ ├── k_factor.py │ │ ├── knapsack.py │ │ ├── longest_common_subsequence.py │ │ ├── longest_increasing.py │ │ ├── matrix_chain_order.py │ │ ├── max_product_subarray.py │ │ ├── max_subarray.py │ │ ├── min_cost_path.py │ │ ├── num_decodings.py │ │ ├── planting_trees.py │ │ ├── regex_matching.py │ │ ├── rod_cut.py │ │ └── word_break.py │ ├── graph/ │ │ ├── __init__.py │ │ ├── a_star.py │ │ ├── all_factors.py │ │ ├── all_pairs_shortest_path.py │ │ ├── bellman_ford.py │ │ ├── blossom.py │ │ ├── check_bipartite.py │ │ ├── check_digraph_strongly_connected.py │ │ ├── clone_graph.py │ │ ├── count_connected_number_of_component.py │ │ ├── count_islands_bfs.py │ │ ├── count_islands_dfs.py │ │ ├── count_islands_unionfind.py │ │ ├── cycle_detection.py │ │ ├── dijkstra.py │ │ ├── dijkstra_heapq.py │ │ ├── find_all_cliques.py │ │ ├── find_path.py │ │ ├── graph.py │ │ ├── kahns_algorithm.py │ │ ├── markov_chain.py │ │ ├── maximum_flow.py │ │ ├── maximum_flow_bfs.py │ │ ├── maximum_flow_dfs.py │ │ ├── maze_search_bfs.py │ │ ├── maze_search_dfs.py │ │ ├── minimum_spanning_tree.py │ │ ├── pacific_atlantic.py │ │ ├── path_between_two_vertices_in_digraph.py │ │ ├── prims_minimum_spanning.py │ │ ├── satisfiability.py │ │ ├── shortest_distance_from_all_buildings.py │ │ ├── strongly_connected_components_kosaraju.py │ │ ├── sudoku_solver.py │ │ ├── tarjan.py │ │ ├── topological_sort_bfs.py │ │ ├── topological_sort_dfs.py │ │ ├── transitive_closure_dfs.py │ │ ├── traversal.py │ │ ├── walls_and_gates.py │ │ └── word_ladder.py │ ├── greedy/ │ │ ├── __init__.py │ │ ├── gale_shapley.py │ │ └── max_contiguous_subsequence_sum.py │ ├── heap/ │ │ ├── __init__.py │ │ ├── k_closest_points.py │ │ ├── merge_sorted_k_lists.py │ │ ├── skyline.py │ │ └── sliding_window_max.py │ ├── linked_list/ │ │ ├── __init__.py │ │ ├── add_two_numbers.py │ │ ├── copy_random_pointer.py │ │ ├── delete_node.py │ │ ├── first_cyclic_node.py │ │ ├── intersection.py │ │ ├── is_cyclic.py │ │ ├── is_palindrome.py │ │ ├── is_sorted.py │ │ ├── kth_to_last.py │ │ ├── merge_two_list.py │ │ ├── partition.py │ │ ├── remove_duplicates.py │ │ ├── remove_range.py │ │ ├── reverse.py │ │ ├── rotate_list.py │ │ └── swap_in_pairs.py │ ├── map/ │ │ ├── __init__.py │ │ ├── is_anagram.py │ │ ├── is_isomorphic.py │ │ ├── longest_common_subsequence.py │ │ ├── longest_palindromic_subsequence.py │ │ ├── randomized_set.py │ │ ├── valid_sudoku.py │ │ └── word_pattern.py │ ├── math/ │ │ ├── __init__.py │ │ ├── base_conversion.py │ │ ├── chinese_remainder_theorem.py │ │ ├── combination.py │ │ ├── cosine_similarity.py │ │ ├── decimal_to_binary_ip.py │ │ ├── diffie_hellman_key_exchange.py │ │ ├── distance_between_two_points.py │ │ ├── euler_totient.py │ │ ├── extended_gcd.py │ │ ├── factorial.py │ │ ├── fft.py │ │ ├── find_order_simple.py │ │ ├── find_primitive_root_simple.py │ │ ├── gcd.py │ │ ├── generate_strobogrammtic.py │ │ ├── goldbach.py │ │ ├── hailstone.py │ │ ├── is_strobogrammatic.py │ │ ├── krishnamurthy_number.py │ │ ├── linear_regression.py │ │ ├── magic_number.py │ │ ├── manhattan_distance.py │ │ ├── modular_exponential.py │ │ ├── modular_inverse.py │ │ ├── next_bigger.py │ │ ├── next_perfect_square.py │ │ ├── nth_digit.py │ │ ├── num_digits.py │ │ ├── num_perfect_squares.py │ │ ├── polynomial.py │ │ ├── polynomial_division.py │ │ ├── power.py │ │ ├── prime_check.py │ │ ├── primes_sieve_of_eratosthenes.py │ │ ├── pythagoras.py │ │ ├── rabin_miller.py │ │ ├── recursive_binomial_coefficient.py │ │ ├── rsa.py │ │ ├── sqrt_precision_factor.py │ │ ├── summing_digits.py │ │ ├── surface_area_of_torus.py │ │ └── symmetry_group_cycle_index.py │ ├── matrix/ │ │ ├── __init__.py │ │ ├── bomb_enemy.py │ │ ├── cholesky_matrix_decomposition.py │ │ ├── copy_transform.py │ │ ├── count_paths.py │ │ ├── crout_matrix_decomposition.py │ │ ├── matrix_exponentiation.py │ │ ├── matrix_inversion.py │ │ ├── multiply.py │ │ ├── rotate_image.py │ │ ├── search_in_sorted_matrix.py │ │ ├── sort_matrix_diagonally.py │ │ ├── sparse_dot_vector.py │ │ ├── sparse_mul.py │ │ ├── spiral_traversal.py │ │ ├── sudoku_validator.py │ │ └── sum_sub_squares.py │ ├── py.typed │ ├── queue/ │ │ ├── __init__.py │ │ ├── max_sliding_window.py │ │ ├── moving_average.py │ │ ├── reconstruct_queue.py │ │ └── zigzagiterator.py │ ├── searching/ │ │ ├── __init__.py │ │ ├── binary_search.py │ │ ├── exponential_search.py │ │ ├── find_min_rotate.py │ │ ├── first_occurrence.py │ │ ├── generalized_binary_search.py │ │ ├── interpolation_search.py │ │ ├── jump_search.py │ │ ├── last_occurrence.py │ │ ├── linear_search.py │ │ ├── next_greatest_letter.py │ │ ├── search_insert.py │ │ ├── search_range.py │ │ ├── search_rotate.py │ │ ├── sentinel_search.py │ │ ├── ternary_search.py │ │ └── two_sum.py │ ├── set/ │ │ ├── __init__.py │ │ ├── find_keyboard_row.py │ │ ├── randomized_set.py │ │ └── set_covering.py │ ├── sorting/ │ │ ├── __init__.py │ │ ├── bead_sort.py │ │ ├── bitonic_sort.py │ │ ├── bogo_sort.py │ │ ├── bubble_sort.py │ │ ├── bucket_sort.py │ │ ├── cocktail_shaker_sort.py │ │ ├── comb_sort.py │ │ ├── counting_sort.py │ │ ├── cycle_sort.py │ │ ├── exchange_sort.py │ │ ├── gnome_sort.py │ │ ├── heap_sort.py │ │ ├── insertion_sort.py │ │ ├── meeting_rooms.py │ │ ├── merge_sort.py │ │ ├── pancake_sort.py │ │ ├── pigeonhole_sort.py │ │ ├── quick_sort.py │ │ ├── radix_sort.py │ │ ├── selection_sort.py │ │ ├── shell_sort.py │ │ ├── sort_colors.py │ │ ├── stooge_sort.py │ │ └── wiggle_sort.py │ ├── stack/ │ │ ├── __init__.py │ │ ├── is_consecutive.py │ │ ├── is_sorted.py │ │ ├── longest_abs_path.py │ │ ├── ordered_stack.py │ │ ├── remove_min.py │ │ ├── simplify_path.py │ │ ├── stutter.py │ │ ├── switch_pairs.py │ │ └── valid_parenthesis.py │ ├── streaming/ │ │ ├── __init__.py │ │ ├── misra_gries.py │ │ └── one_sparse_recovery.py │ ├── string/ │ │ ├── __init__.py │ │ ├── add_binary.py │ │ ├── alphabet_board_path.py │ │ ├── atbash_cipher.py │ │ ├── breaking_bad.py │ │ ├── caesar_cipher.py │ │ ├── check_pangram.py │ │ ├── contain_string.py │ │ ├── count_binary_substring.py │ │ ├── decode_string.py │ │ ├── delete_reoccurring.py │ │ ├── domain_extractor.py │ │ ├── encode_decode.py │ │ ├── first_unique_char.py │ │ ├── fizzbuzz.py │ │ ├── group_anagrams.py │ │ ├── int_to_roman.py │ │ ├── is_palindrome.py │ │ ├── is_rotated.py │ │ ├── judge_circle.py │ │ ├── knuth_morris_pratt.py │ │ ├── license_number.py │ │ ├── longest_common_prefix.py │ │ ├── longest_palindromic_substring.py │ │ ├── make_sentence.py │ │ ├── manacher.py │ │ ├── merge_string_checker.py │ │ ├── min_distance.py │ │ ├── multiply_strings.py │ │ ├── one_edit_distance.py │ │ ├── panagram.py │ │ ├── rabin_karp.py │ │ ├── repeat_string.py │ │ ├── repeat_substring.py │ │ ├── reverse_string.py │ │ ├── reverse_vowel.py │ │ ├── reverse_words.py │ │ ├── roman_to_int.py │ │ ├── rotate.py │ │ ├── strip_url_params.py │ │ ├── strong_password.py │ │ ├── swap_characters.py │ │ ├── text_justification.py │ │ ├── unique_morse.py │ │ ├── validate_coordinates.py │ │ ├── word_squares.py │ │ └── z_algorithm.py │ └── tree/ │ ├── __init__.py │ ├── bin_tree_to_list.py │ ├── binary_tree_paths.py │ ├── binary_tree_views.py │ ├── bst_array_to_bst.py │ ├── bst_closest_value.py │ ├── bst_count_left_node.py │ ├── bst_delete_node.py │ ├── bst_depth_sum.py │ ├── bst_height.py │ ├── bst_is_bst.py │ ├── bst_iterator.py │ ├── bst_kth_smallest.py │ ├── bst_lowest_common_ancestor.py │ ├── bst_num_empty.py │ ├── bst_predecessor.py │ ├── bst_serialize_deserialize.py │ ├── bst_successor.py │ ├── bst_unique_bst.py │ ├── bst_validate_bst.py │ ├── construct_tree_postorder_preorder.py │ ├── deepest_left.py │ ├── invert_tree.py │ ├── is_balanced.py │ ├── is_subtree.py │ ├── is_symmetric.py │ ├── longest_consecutive.py │ ├── lowest_common_ancestor.py │ ├── max_height.py │ ├── max_path_sum.py │ ├── min_height.py │ ├── path_sum.py │ ├── path_sum2.py │ ├── pretty_print.py │ ├── same_tree.py │ ├── traversal_inorder.py │ ├── traversal_level_order.py │ ├── traversal_postorder.py │ ├── traversal_preorder.py │ ├── traversal_zigzag.py │ ├── tree.py │ └── trie_add_and_search.py ├── docs/ │ └── index.html ├── pyproject.toml └── tests/ ├── test_array.py ├── test_backtracking.py ├── test_bit_manipulation.py ├── test_community_algorithms.py ├── test_compression.py ├── test_data_structures.py ├── test_dynamic_programming.py ├── test_graph.py ├── test_greedy.py ├── test_heap.py ├── test_issue_fixes.py ├── test_iterative_segment_tree.py ├── test_linked_list.py ├── test_map.py ├── test_math.py ├── test_matrix.py ├── test_monomial.py ├── test_polynomial.py ├── test_queue.py ├── test_searching.py ├── test_set.py ├── test_sorting.py ├── test_stack.py ├── test_streaming.py ├── test_string.py ├── test_tree.py └── test_veb_tree.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish to PyPI on: release: types: [published] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.13" - name: Install build tools run: python -m pip install --upgrade pip build - name: Build package run: python -m build - name: Upload build artifacts uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 with: name: dist path: dist/ publish: needs: build runs-on: ubuntu-latest environment: pypi permissions: id-token: write steps: - name: Download build artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 with: name: dist path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 ================================================ FILE: .github/workflows/python-app.yml ================================================ name: Tests on: push: branches: [master, main] pull_request: branches: [master, main] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e ".[dev]" - name: Lint with ruff run: ruff check algorithms/ tests/ - name: Test with pytest run: python -m pytest ================================================ FILE: .gitignore ================================================ __pycache__/ *.py[cod] *.iml *.xml .idea/ .cache/ .pytest_cache/ .coverage # Setuptools distribution folder. /dist/ # Python egg metadata, regenerated from source files by setuptools. /*.egg-info /*.egg # docs build/ pythonenv3.8/ .vscode/ # Ignoring the virtual Environment when using GitHub Codespaces .venv/ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kwk236@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing We love pull requests from everyone. By contributing to this repository, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md). ## Get Started * First [fork][fork] the repository and then clone it using: git clone git@github.com:your-username/algorithms.git * After that create a branch for your changes. For example: * add_XXX if you will add new algorithms or data structures. * fix_XXX if you will fix a bug on a certain algorithm or data structure. * test_XXX if you wrote a test/s. * doc_XXX if you added to or edited documentation. You may contribute by: - implementing new algorithms in the repo. Be sure to keep it under right section (e.g. [array](array), [dp](dp), etc). Make a new section for it if it doesn't fall under any section. Make sure that your implementation works. - optimizing or improving the existing algorithms. - adding a different solution for the problem. - finding and fixing bugs. - adding examples to explain the algorithms better. - adding test cases. - improving documentation. ## Pull Requests Push to your fork and [submit a pull request][pr]. We will review and may suggest some changes or improvements or alternatives. Some things that will increase the chance that your pull request is accepted: * All algorithms should be written in **Python 3**. (There are a few algorithms still in _Python 2_ syntax. You can start by converting [those][issue120] to _Python 3_.) * Write clean and understandable code. * Properly comment the code and briefly explain what the algorithm is doing in the [docstrings][docstr]. * You may also explain the output using a minimal example. * Try to also include a couple of test cases for the algorithm. * Write a [good commit message][commit]. ## Issues Submit a [new issue][newissue] if there is an algorithm to add, or if a bug was found in an existing algorithm. Before submitting a new issue please review the [existing issues][issues] to avoid creating duplicates. Also, consider resolving current issues or contributing to the discussion on an issue. ## Collaborators You can ask for any help or clarifications from the collaborators. [Keon Kim](https://github.com/keon) [Rahul Goswami](https://github.com/goswami-rahul) [Ankit Agarwal](https://github.com/ankit167) [Hai Hoang Dang](https://github.com/danghai) [Saad](https://github.com/SaadBenn) [fork]: https://help.github.com/articles/fork-a-repo/ [docstr]: https://www.python.org/dev/peps/pep-0257/#multi-line-docstrings [commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [pr]: https://github.com/keon/algorithms/compare/ [newissue]: https://github.com/keon/algorithms/issues/new [issue120]: https://github.com/keon/algorithms/issues/120 [issues]: https://github.com/keon/algorithms/issues/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Keon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MANIFEST.in ================================================ include README.md include LICENSE include algorithms/* ================================================ FILE: README.md ================================================ [![PyPI version](https://badge.fury.io/py/algorithms.svg)](https://badge.fury.io/py/algorithms) [![Open Source Helpers](https://www.codetriage.com/keon/algorithms/badges/users.svg)](https://www.codetriage.com/keon/algorithms) # algorithms Minimal, clean, and well-documented implementations of data structures and algorithms in Python 3. Each file is self-contained with docstrings, type hints, and complexity notes — designed to be read and learned from. ## Quick Start ### Install ```bash pip install algorithms ``` ### Use ```python from algorithms.sorting import merge_sort print(merge_sort([38, 27, 43, 3, 9, 82, 10])) # [3, 9, 10, 27, 38, 43, 82] ``` ```python from algorithms.data_structures import BinaryHeap, Trie, BST from algorithms.graph import dijkstra, bellman_ford from algorithms.tree import TreeNode ``` ### Examples **Graph — Dijkstra's shortest path:** ```python from algorithms.graph import dijkstra graph = { "s": {"a": 2, "b": 1}, "a": {"s": 3, "b": 4, "c": 8}, "b": {"s": 4, "a": 2, "d": 2}, "c": {"a": 2, "d": 7, "t": 4}, "d": {"b": 1, "c": 11, "t": 5}, "t": {"c": 3, "d": 5}, } print(dijkstra(graph, "s", "t")) # (8, ['s', 'b', 'd', 't']) ``` **Dynamic programming — coin change:** ```python from algorithms.dynamic_programming import count # Number of ways to make amount 10 using denominations [2, 5, 3, 6] print(count([2, 5, 3, 6], 10)) # 5 ``` **Backtracking — generate permutations:** ```python from algorithms.backtracking import permute print(permute([1, 2, 3])) # [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] ``` **Data structures — binary heap:** ```python from algorithms.data_structures import BinaryHeap heap = BinaryHeap() for val in [5, 3, 8, 1, 9]: heap.insert(val) print(heap.remove_min()) # 1 print(heap.remove_min()) # 3 ``` **Searching — binary search:** ```python from algorithms.searching import binary_search print(binary_search([1, 3, 5, 7, 9, 11], 7)) # 3 (index of target) ``` **Tree — inorder traversal:** ```python from algorithms.tree import TreeNode from algorithms.tree import inorder root = TreeNode(4) root.left = TreeNode(2) root.right = TreeNode(6) root.left.left = TreeNode(1) root.left.right = TreeNode(3) print(inorder(root)) # [1, 2, 3, 4, 6] ``` **String — Knuth-Morris-Pratt pattern matching:** ```python from algorithms.string import knuth_morris_pratt print(knuth_morris_pratt("abxabcabcaby", "abcaby")) # 6 (starting index of match) ``` ### Run Tests ```bash python -m pytest tests/ ``` ## Project Structure ``` algorithms/ data_structures/ # Reusable data structure implementations array/ # Array manipulation algorithms backtracking/ # Constraint satisfaction & enumeration bit_manipulation/ # Bitwise operations & tricks compression/ # Encoding & compression schemes dynamic_programming/ # Optimal substructure & memoization graph/ # Graph algorithms (BFS, DFS, shortest path, flow, ...) greedy/ # Greedy strategies heap/ # Heap-based algorithms linked_list/ # Linked list algorithms map/ # Hash-map-based algorithms math/ # Number theory, combinatorics, algebra matrix/ # 2D array & linear algebra operations queue/ # Queue-based algorithms searching/ # Search algorithms (binary, linear, ...) set/ # Set-based algorithms sorting/ # Sorting algorithms stack/ # Stack-based algorithms streaming/ # Streaming & sketching algorithms string/ # String matching, manipulation, parsing tree/ # Tree algorithms (traversal, BST ops, ...) tests/ # One test file per topic ``` ## Data Structures All core data structures live in [`algorithms/data_structures/`](algorithms/data_structures): | Data Structure | Module | Key Classes | |---|---|---| | AVL Tree | `avl_tree.py` | `AvlTree` | | B-Tree | `b_tree.py` | `BTree` | | Binary Search Tree | `bst.py` | `BST` | | Fenwick Tree | `fenwick_tree.py` | `Fenwick_Tree` | | Graph | `graph.py` | `Node`, `DirectedEdge`, `DirectedGraph` | | Hash Table | `hash_table.py` | `HashTable`, `ResizableHashTable` | | Heap | `heap.py` | `BinaryHeap` | | KD Tree | `kd_tree.py` | `KDTree` | | Linked List | `linked_list.py` | `SinglyLinkedListNode`, `DoublyLinkedListNode` | | Priority Queue | `priority_queue.py` | `PriorityQueue` | | Queue | `queue.py` | `ArrayQueue`, `LinkedListQueue` | | Red-Black Tree | `red_black_tree.py` | `RBTree` | | Segment Tree | `segment_tree.py`, `iterative_segment_tree.py` | `SegmentTree` | | Separate Chaining Hash Table | `separate_chaining_hash_table.py` | `SeparateChainingHashTable` | | Sqrt Decomposition | `sqrt_decomposition.py` | `SqrtDecomposition` | | Stack | `stack.py` | `ArrayStack`, `LinkedListStack` | | Trie | `trie.py` | `Trie` | | Union-Find | `union_find.py` | `Union` | | vEB Tree | `veb_tree.py` | `VEBTree` | ## Algorithms ### Array - [delete_nth](algorithms/array/delete_nth.py) — keep at most N occurrences of each element - [flatten](algorithms/array/flatten.py) — recursively flatten nested arrays into a single list - [garage](algorithms/array/garage.py) — minimum swaps to rearrange a parking lot - [josephus](algorithms/array/josephus.py) — eliminate every k-th person in a circular arrangement - [limit](algorithms/array/limit.py) — filter elements within min/max bounds - [longest_non_repeat](algorithms/array/longest_non_repeat.py) — longest substring without repeating characters - [max_ones_index](algorithms/array/max_ones_index.py) — find the zero to flip for the longest run of ones - [merge_intervals](algorithms/array/merge_intervals.py) — combine overlapping intervals - [missing_ranges](algorithms/array/missing_ranges.py) — find gaps between a low and high bound - [move_zeros](algorithms/array/move_zeros.py) — move all zeros to the end, preserving order - [n_sum](algorithms/array/n_sum.py) — find all unique n-tuples that sum to a target - [plus_one](algorithms/array/plus_one.py) — add one to a number represented as a digit array - [remove_duplicates](algorithms/array/remove_duplicates.py) — remove duplicate elements preserving order - [rotate](algorithms/array/rotate.py) — rotate an array right by k positions - [summarize_ranges](algorithms/array/summarize_ranges.py) — summarize consecutive integers as range tuples - [three_sum](algorithms/array/three_sum.py) — find all unique triplets that sum to zero - [top_1](algorithms/array/top_1.py) — find the most frequently occurring values - [trimmean](algorithms/array/trimmean.py) — compute mean after trimming extreme values - [two_sum](algorithms/array/two_sum.py) — find two indices whose values sum to a target ### Backtracking - [add_operators](algorithms/backtracking/add_operators.py) — insert +, -, * between digits to reach a target - [anagram](algorithms/backtracking/anagram.py) — check if two strings are anagrams - [array_sum_combinations](algorithms/backtracking/array_sum_combinations.py) — find three-element combos from arrays that hit a target sum - [combination_sum](algorithms/backtracking/combination_sum.py) — find combinations (with reuse) that sum to a target - [factor_combinations](algorithms/backtracking/factor_combinations.py) — generate all factor combinations of a number - [find_words](algorithms/backtracking/find_words.py) — find words on a letter board via trie-based search - [generate_abbreviations](algorithms/backtracking/generate_abbreviations.py) — generate all possible abbreviations of a word - [generate_parenthesis](algorithms/backtracking/generate_parenthesis.py) — generate all valid parenthesis combinations - [letter_combination](algorithms/backtracking/letter_combination.py) — phone keypad digit-to-letter combinations - [palindrome_partitioning](algorithms/backtracking/palindrome_partitioning.py) — partition a string into palindromic substrings - [pattern_match](algorithms/backtracking/pattern_match.py) — match a string to a pattern via bijection mapping - [permute](algorithms/backtracking/permute.py) — generate all permutations of distinct elements - [permute_unique](algorithms/backtracking/permute_unique.py) — generate unique permutations when duplicates exist - [subsets](algorithms/backtracking/subsets.py) — generate all subsets (power set) - [minimax](algorithms/backtracking/minimax.py) — game-tree search with alpha-beta pruning - [subsets_unique](algorithms/backtracking/subsets_unique.py) — generate unique subsets when duplicates exist ### Bit Manipulation - [add_bitwise_operator](algorithms/bit_manipulation/add_bitwise_operator.py) — add two integers using only bitwise operations - [binary_gap](algorithms/bit_manipulation/binary_gap.py) — longest distance between consecutive 1-bits - [bit_operation](algorithms/bit_manipulation/bit_operation.py) — get, set, clear, and update individual bits - [bytes_int_conversion](algorithms/bit_manipulation/bytes_int_conversion.py) — convert between integers and byte sequences - [count_flips_to_convert](algorithms/bit_manipulation/count_flips_to_convert.py) — count bit flips needed to convert one integer to another - [count_ones](algorithms/bit_manipulation/count_ones.py) — count the number of 1-bits (Hamming weight) - [find_difference](algorithms/bit_manipulation/find_difference.py) — find the added character between two strings using XOR - [find_missing_number](algorithms/bit_manipulation/find_missing_number.py) — find a missing number in a sequence using XOR - [flip_bit_longest_sequence](algorithms/bit_manipulation/flip_bit_longest_sequence.py) — longest run of 1s after flipping a single 0 - [gray_code](algorithms/bit_manipulation/gray_code.py) — generate Gray code sequences and convert between Gray and binary - [has_alternative_bit](algorithms/bit_manipulation/has_alternative_bit.py) — check if binary representation has alternating bits - [insert_bit](algorithms/bit_manipulation/insert_bit.py) — insert bits at a specific position in an integer - [power_of_two](algorithms/bit_manipulation/power_of_two.py) — check if an integer is a power of two - [remove_bit](algorithms/bit_manipulation/remove_bit.py) — remove a bit at a given position - [reverse_bits](algorithms/bit_manipulation/reverse_bits.py) — reverse all 32 bits of an unsigned integer - [single_number](algorithms/bit_manipulation/single_number.py) — find the element appearing once (others appear twice) via XOR - [single_number2](algorithms/bit_manipulation/single_number2.py) — find the element appearing once (others appear three times) - [single_number3](algorithms/bit_manipulation/single_number3.py) — find two unique elements (others appear twice) - [subsets](algorithms/bit_manipulation/subsets.py) — generate all subsets using bitmask enumeration - [swap_pair](algorithms/bit_manipulation/swap_pair.py) — swap adjacent bit pairs in an integer ### Compression - [elias](algorithms/compression/elias.py) — Elias gamma and delta universal integer coding - [huffman_coding](algorithms/compression/huffman_coding.py) — variable-length prefix codes for lossless compression - [rle_compression](algorithms/compression/rle_compression.py) — run-length encoding for consecutive character compression ### Dynamic Programming - [bitmask](algorithms/dynamic_programming/bitmask.py) — travelling salesman problem via bitmask dynamic programming - [buy_sell_stock](algorithms/dynamic_programming/buy_sell_stock.py) — maximize profit from a stock price array - [climbing_stairs](algorithms/dynamic_programming/climbing_stairs.py) — count ways to climb stairs taking 1 or 2 steps - [coin_change](algorithms/dynamic_programming/coin_change.py) — minimum coins to make a given amount - [combination_sum](algorithms/dynamic_programming/combination_sum.py) — count combinations that sum to a target (with reuse) - [count_paths_dp](algorithms/dynamic_programming/count_paths_dp.py) — count paths in a grid using recursion, memoization, and bottom-up DP - [edit_distance](algorithms/dynamic_programming/edit_distance.py) — minimum edits to transform one string into another - [egg_drop](algorithms/dynamic_programming/egg_drop.py) — minimize trials to find the critical floor - [fibonacci](algorithms/dynamic_programming/fib.py) — compute Fibonacci numbers with memoization - [hosoya_triangle](algorithms/dynamic_programming/hosoya_triangle.py) — generate the Hosoya triangle of Fibonacci-like numbers - [house_robber](algorithms/dynamic_programming/house_robber.py) — maximize loot from non-adjacent houses - [int_divide](algorithms/dynamic_programming/int_divide.py) — count the number of integer partitions - [job_scheduling](algorithms/dynamic_programming/job_scheduling.py) — maximize profit from weighted job scheduling - [k_factor](algorithms/dynamic_programming/k_factor.py) — find the k-factor of a string pattern - [knapsack](algorithms/dynamic_programming/knapsack.py) — maximize value under a weight constraint - [longest_common_subsequence](algorithms/dynamic_programming/longest_common_subsequence.py) — find the longest common subsequence of two strings - [longest_increasing](algorithms/dynamic_programming/longest_increasing.py) — find the longest increasing subsequence - [matrix_chain_order](algorithms/dynamic_programming/matrix_chain_order.py) — minimize scalar multiplications for matrix chain - [max_product_subarray](algorithms/dynamic_programming/max_product_subarray.py) — find the contiguous subarray with maximum product - [max_subarray](algorithms/dynamic_programming/max_subarray.py) — maximum sum subarray (Kadane's algorithm) - [min_cost_path](algorithms/dynamic_programming/min_cost_path.py) — minimum-cost path through a grid - [num_decodings](algorithms/dynamic_programming/num_decodings.py) — count ways to decode a digit string into letters - [planting_trees](algorithms/dynamic_programming/planting_trees.py) — optimize tree planting for maximum profit - [regex_matching](algorithms/dynamic_programming/regex_matching.py) — match a string against a pattern with `.` and `*` wildcards - [rod_cut](algorithms/dynamic_programming/rod_cut.py) — maximize revenue from cutting a rod into pieces - [word_break](algorithms/dynamic_programming/word_break.py) — check if a string can be segmented into dictionary words ### Graph - [a_star](algorithms/graph/a_star.py) — heuristic shortest-path search (A* algorithm) - [all_factors](algorithms/graph/all_factors.py) — find all factor combinations of a number - [all_pairs_shortest_path](algorithms/graph/all_pairs_shortest_path.py) — Floyd-Warshall all-pairs shortest paths - [bellman_ford](algorithms/graph/bellman_ford.py) — single-source shortest path with negative edge weights - [blossom](algorithms/graph/blossom.py) — Edmonds' blossom algorithm for maximum matching in general graphs - [check_bipartite](algorithms/graph/check_bipartite.py) — determine if a graph is two-colorable - [check_digraph_strongly_connected](algorithms/graph/check_digraph_strongly_connected.py) — check if a directed graph is strongly connected - [clone_graph](algorithms/graph/clone_graph.py) — deep-copy an undirected graph - [count_connected_number_of_component](algorithms/graph/count_connected_number_of_component.py) — count connected components in an undirected graph - [count_islands (BFS)](algorithms/graph/count_islands_bfs.py) — count islands in a grid using breadth-first search - [count_islands (DFS)](algorithms/graph/count_islands_dfs.py) — count islands in a grid using depth-first search - [count_islands (Union-Find)](algorithms/graph/count_islands_unionfind.py) — count islands using a disjoint-set structure - [cycle_detection](algorithms/graph/cycle_detection.py) — detect cycles in a directed graph - [dijkstra](algorithms/graph/dijkstra.py) — single-source shortest path for non-negative weights - [dijkstra_heapq](algorithms/graph/dijkstra_heapq.py) — heap-optimised Dijkstra in O((V+E) log V) for sparse graphs - [find_all_cliques](algorithms/graph/find_all_cliques.py) — Bron-Kerbosch algorithm for finding all cliques - [find_path](algorithms/graph/find_path.py) — find paths between two vertices - [kahns_algorithm](algorithms/graph/kahns_algorithm.py) — topological sort via in-degree counting (Kahn's) - [markov_chain](algorithms/graph/markov_chain.py) — Markov chain probability modeling - [maximum_flow](algorithms/graph/maximum_flow.py) — compute maximum flow in a flow network - [maximum_flow (BFS)](algorithms/graph/maximum_flow_bfs.py) — Edmonds-Karp max-flow (BFS-based Ford-Fulkerson) - [maximum_flow (DFS)](algorithms/graph/maximum_flow_dfs.py) — Ford-Fulkerson max-flow via DFS augmenting paths - [maze_search (BFS)](algorithms/graph/maze_search_bfs.py) — find shortest path through a maze using BFS - [maze_search (DFS)](algorithms/graph/maze_search_dfs.py) — find a path through a maze using DFS - [minimum_spanning_tree](algorithms/graph/minimum_spanning_tree.py) — Kruskal's minimum spanning tree - [pacific_atlantic](algorithms/graph/pacific_atlantic.py) — find cells that can flow to both oceans - [path_between_two_vertices_in_digraph](algorithms/graph/path_between_two_vertices_in_digraph.py) — check if a path exists in a directed graph - [prims_minimum_spanning](algorithms/graph/prims_minimum_spanning.py) — Prim's minimum spanning tree - [satisfiability](algorithms/graph/satisfiability.py) — 2-SAT satisfiability via implication graph - [shortest_distance_from_all_buildings](algorithms/graph/shortest_distance_from_all_buildings.py) — find the optimal meeting point in a grid - [strongly_connected_components (Kosaraju)](algorithms/graph/strongly_connected_components_kosaraju.py) — Kosaraju's SCC algorithm - [sudoku_solver](algorithms/graph/sudoku_solver.py) — solve a Sudoku puzzle using constraint backtracking - [tarjan](algorithms/graph/tarjan.py) — Tarjan's strongly connected components algorithm - [topological_sort (BFS)](algorithms/graph/topological_sort_bfs.py) — topological ordering using BFS (Kahn's variant) - [topological_sort (DFS)](algorithms/graph/topological_sort_dfs.py) — topological ordering using DFS post-order - [transitive_closure (DFS)](algorithms/graph/transitive_closure_dfs.py) — compute the transitive closure of a graph - [traversal](algorithms/graph/traversal.py) — BFS and DFS graph traversal - [walls_and_gates](algorithms/graph/walls_and_gates.py) — fill each empty room with distance to nearest gate - [word_ladder](algorithms/graph/word_ladder.py) — shortest word-to-word transformation sequence ### Greedy - [gale_shapley](algorithms/greedy/gale_shapley.py) — stable matching for bipartite preferences (Gale-Shapley) - [max_contiguous_subsequence_sum](algorithms/greedy/max_contiguous_subsequence_sum.py) — maximum contiguous subarray sum (Kadane's algorithm) ### Heap - [k_closest_points](algorithms/heap/k_closest_points.py) — find k points closest to the origin - [merge_sorted_k_lists](algorithms/heap/merge_sorted_k_lists.py) — merge k sorted linked lists using a min-heap - [skyline](algorithms/heap/skyline.py) — compute the skyline silhouette from building rectangles - [sliding_window_max](algorithms/heap/sliding_window_max.py) — maximum value in each sliding window position ### Linked List - [add_two_numbers](algorithms/linked_list/add_two_numbers.py) — add two numbers stored as reversed linked lists - [copy_random_pointer](algorithms/linked_list/copy_random_pointer.py) — deep-copy a linked list with random pointers - [delete_node](algorithms/linked_list/delete_node.py) — delete a node given only a reference to it - [first_cyclic_node](algorithms/linked_list/first_cyclic_node.py) — find the first node where a cycle begins (Floyd's) - [intersection](algorithms/linked_list/intersection.py) — find the intersection point of two singly linked lists - [is_cyclic](algorithms/linked_list/is_cyclic.py) — detect whether a linked list has a cycle - [is_palindrome](algorithms/linked_list/is_palindrome.py) — check if a linked list reads the same forwards and backwards - [is_sorted](algorithms/linked_list/is_sorted.py) — check if a linked list is sorted in order - [kth_to_last](algorithms/linked_list/kth_to_last.py) — find the k-th element from the end - [merge_two_list](algorithms/linked_list/merge_two_list.py) — merge two sorted linked lists into one - [partition](algorithms/linked_list/partition.py) — partition a list around a pivot value - [remove_duplicates](algorithms/linked_list/remove_duplicates.py) — remove duplicate values from a linked list - [remove_range](algorithms/linked_list/remove_range.py) — remove nodes within a given index range - [reverse](algorithms/linked_list/reverse.py) — reverse a linked list iteratively and recursively - [rotate_list](algorithms/linked_list/rotate_list.py) — rotate a list right by k positions - [swap_in_pairs](algorithms/linked_list/swap_in_pairs.py) — swap every two adjacent nodes ### Map - [is_anagram](algorithms/map/is_anagram.py) — check if two strings are anagrams via character counting - [is_isomorphic](algorithms/map/is_isomorphic.py) — check if two strings have the same character mapping structure - [longest_common_subsequence](algorithms/map/longest_common_subsequence.py) — longest common substring using a hash map - [longest_palindromic_subsequence](algorithms/map/longest_palindromic_subsequence.py) — longest palindromic substring via hash map - [randomized_set](algorithms/map/randomized_set.py) — O(1) insert, delete, and get-random data structure - [valid_sudoku](algorithms/map/valid_sudoku.py) — validate a Sudoku board configuration - [word_pattern](algorithms/map/word_pattern.py) — check if a string follows a given pattern mapping ### Math - [base_conversion](algorithms/math/base_conversion.py) — convert integers between arbitrary number bases - [chinese_remainder_theorem](algorithms/math/chinese_remainder_theorem.py) — solve a system of modular congruences - [combination](algorithms/math/combination.py) — compute binomial coefficients (n choose r) - [cosine_similarity](algorithms/math/cosine_similarity.py) — compute cosine similarity between two vectors - [decimal_to_binary_ip](algorithms/math/decimal_to_binary_ip.py) — convert an IP address between decimal and binary - [diffie_hellman_key_exchange](algorithms/math/diffie_hellman_key_exchange.py) — Diffie-Hellman cryptographic key exchange - [distance_between_two_points](algorithms/math/distance_between_two_points.py) — Euclidean distance in 2D space - [euler_totient](algorithms/math/euler_totient.py) — count integers up to n that are coprime to n - [extended_gcd](algorithms/math/extended_gcd.py) — extended Euclidean algorithm (GCD with coefficients) - [factorial](algorithms/math/factorial.py) — compute n! iteratively and recursively - [fft](algorithms/math/fft.py) — Fast Fourier Transform (Cooley-Tukey) - [find_order_simple](algorithms/math/find_order_simple.py) — find the multiplicative order of an element mod n - [find_primitive_root_simple](algorithms/math/find_primitive_root_simple.py) — find a primitive root modulo a prime - [gcd](algorithms/math/gcd.py) — greatest common divisor and least common multiple - [generate_strobogrammtic](algorithms/math/generate_strobogrammtic.py) — generate strobogrammatic numbers of length n - [goldbach](algorithms/math/goldbach.py) — decompose an even number into a sum of two primes (Goldbach's conjecture) - [hailstone](algorithms/math/hailstone.py) — Collatz conjecture (hailstone) sequence - [is_strobogrammatic](algorithms/math/is_strobogrammatic.py) — check if a number looks the same upside-down - [krishnamurthy_number](algorithms/math/krishnamurthy_number.py) — check if a number equals the sum of the factorials of its digits - [linear_regression](algorithms/math/linear_regression.py) — ordinary least-squares linear regression with R² and RMSE - [magic_number](algorithms/math/magic_number.py) — check if a number is a magic number - [manhattan_distance](algorithms/math/manhattan_distance.py) — compute Manhattan (L1) distance between two points in any dimension - [modular_exponential](algorithms/math/modular_exponential.py) — compute (base^exp) mod m efficiently - [modular_inverse](algorithms/math/modular_inverse.py) — compute the modular multiplicative inverse - [next_bigger](algorithms/math/next_bigger.py) — next larger number with the same digits - [next_perfect_square](algorithms/math/next_perfect_square.py) — find the next perfect square after n - [nth_digit](algorithms/math/nth_digit.py) — find the n-th digit in the sequence 1, 2, 3, ... - [num_digits](algorithms/math/num_digits.py) — count the number of digits in an integer - [num_perfect_squares](algorithms/math/num_perfect_squares.py) — minimum perfect squares that sum to n - [polynomial](algorithms/math/polynomial.py) — polynomial and monomial arithmetic operations - [polynomial_division](algorithms/math/polynomial_division.py) — polynomial long division returning quotient and remainder - [power](algorithms/math/power.py) — compute x^n via binary exponentiation - [prime_check](algorithms/math/prime_check.py) — check if a number is prime - [primes_sieve_of_eratosthenes](algorithms/math/primes_sieve_of_eratosthenes.py) — generate primes up to n using the Sieve of Eratosthenes - [pythagoras](algorithms/math/pythagoras.py) — Pythagorean theorem calculations - [rabin_miller](algorithms/math/rabin_miller.py) — Miller-Rabin probabilistic primality test - [recursive_binomial_coefficient](algorithms/math/recursive_binomial_coefficient.py) — binomial coefficient via Pascal's triangle recursion - [rsa](algorithms/math/rsa.py) — RSA public-key encryption and decryption - [sqrt_precision_factor](algorithms/math/sqrt_precision_factor.py) — square root to arbitrary precision (Newton's method) - [summing_digits](algorithms/math/summing_digits.py) — recursively sum the digits of a number - [surface_area_of_torus](algorithms/math/surface_area_of_torus.py) — calculate the surface area of a torus - [symmetry_group_cycle_index](algorithms/math/symmetry_group_cycle_index.py) — cycle index polynomials for symmetry groups ### Matrix - [bomb_enemy](algorithms/matrix/bomb_enemy.py) — maximize enemies killed by a single bomb placement - [cholesky_matrix_decomposition](algorithms/matrix/cholesky_matrix_decomposition.py) — Cholesky decomposition of a positive-definite matrix - [copy_transform](algorithms/matrix/copy_transform.py) — copy and transform a matrix - [count_paths](algorithms/matrix/count_paths.py) — count paths from top-left to bottom-right of a grid - [crout_matrix_decomposition](algorithms/matrix/crout_matrix_decomposition.py) — Crout's LU matrix decomposition - [matrix_exponentiation](algorithms/matrix/matrix_exponentiation.py) — raise a matrix to the n-th power efficiently - [matrix_inversion](algorithms/matrix/matrix_inversion.py) — compute the inverse of a square matrix - [multiply](algorithms/matrix/multiply.py) — standard and Strassen matrix multiplication - [rotate_image](algorithms/matrix/rotate_image.py) — rotate an n x n matrix 90 degrees in-place - [search_in_sorted_matrix](algorithms/matrix/search_in_sorted_matrix.py) — search in a row- and column-sorted matrix - [sort_matrix_diagonally](algorithms/matrix/sort_matrix_diagonally.py) — sort each diagonal of a matrix independently - [sparse_dot_vector](algorithms/matrix/sparse_dot_vector.py) — dot product of two sparse vectors - [sparse_mul](algorithms/matrix/sparse_mul.py) — multiply two sparse matrices efficiently - [spiral_traversal](algorithms/matrix/spiral_traversal.py) — traverse a matrix in spiral order - [sudoku_validator](algorithms/matrix/sudoku_validator.py) — validate that a Sudoku board follows all rules - [sum_sub_squares](algorithms/matrix/sum_sub_squares.py) — sum of all k x k sub-squares in a matrix ### Queue - [max_sliding_window](algorithms/queue/max_sliding_window.py) — maximum in each sliding window using a deque - [moving_average](algorithms/queue/moving_average.py) — compute a running moving average from a stream - [reconstruct_queue](algorithms/queue/reconstruct_queue.py) — reconstruct a queue from (height, count) pairs - [zigzagiterator](algorithms/queue/zigzagiterator.py) — alternate elements from multiple iterators ### Searching - [binary_search](algorithms/searching/binary_search.py) — search a sorted array in O(log n) - [exponential_search](algorithms/searching/exponential_search.py) — search a sorted array by doubling the range then binary searching - [find_min_rotate](algorithms/searching/find_min_rotate.py) — find the minimum in a rotated sorted array - [first_occurrence](algorithms/searching/first_occurrence.py) — find the first occurrence of a target value - [generalized_binary_search](algorithms/searching/generalized_binary_search.py) — binary search with a custom predicate - [interpolation_search](algorithms/searching/interpolation_search.py) — search using value-based interpolation - [jump_search](algorithms/searching/jump_search.py) — search a sorted array by jumping in fixed blocks - [last_occurrence](algorithms/searching/last_occurrence.py) — find the last occurrence of a target value - [linear_search](algorithms/searching/linear_search.py) — sequential scan through an unsorted array - [next_greatest_letter](algorithms/searching/next_greatest_letter.py) — find the smallest letter greater than a target - [search_insert](algorithms/searching/search_insert.py) — find the insertion position for a target value - [search_range](algorithms/searching/search_range.py) — find the first and last positions of a target - [search_rotate](algorithms/searching/search_rotate.py) — search in a rotated sorted array - [sentinel_search](algorithms/searching/sentinel_search.py) — linear search optimized by placing a sentinel at the end - [ternary_search](algorithms/searching/ternary_search.py) — search by dividing the array into three parts - [two_sum](algorithms/searching/two_sum.py) — find two numbers that sum to a target ### Set - [find_keyboard_row](algorithms/set/find_keyboard_row.py) — filter words that can be typed on one keyboard row - [randomized_set](algorithms/set/randomized_set.py) — O(1) insert, delete, and random-element access - [set_covering](algorithms/set/set_covering.py) — greedy approximation for the set cover problem ### Sorting - [bead_sort](algorithms/sorting/bead_sort.py) — gravity-based natural sorting (bead/abacus sort) - [bitonic_sort](algorithms/sorting/bitonic_sort.py) — parallel-friendly comparison sort via bitonic sequences - [bogo_sort](algorithms/sorting/bogo_sort.py) — random permutation sort (intentionally inefficient) - [bubble_sort](algorithms/sorting/bubble_sort.py) — repeatedly swap adjacent out-of-order elements - [bucket_sort](algorithms/sorting/bucket_sort.py) — distribute elements into buckets, then sort each - [cocktail_shaker_sort](algorithms/sorting/cocktail_shaker_sort.py) — bidirectional bubble sort - [comb_sort](algorithms/sorting/comb_sort.py) — bubble sort improved with a shrinking gap - [counting_sort](algorithms/sorting/counting_sort.py) — sort integers by counting occurrences - [cycle_sort](algorithms/sorting/cycle_sort.py) — in-place sort that minimizes total writes - [exchange_sort](algorithms/sorting/exchange_sort.py) — simple pairwise comparison and exchange - [gnome_sort](algorithms/sorting/gnome_sort.py) — sort by swapping elements backward until ordered - [heap_sort](algorithms/sorting/heap_sort.py) — sort via a binary heap (in-place, O(n log n)) - [insertion_sort](algorithms/sorting/insertion_sort.py) — build a sorted portion one element at a time - [meeting_rooms](algorithms/sorting/meeting_rooms.py) — determine if meeting intervals overlap - [merge_sort](algorithms/sorting/merge_sort.py) — divide-and-conquer stable sort (O(n log n)) - [pancake_sort](algorithms/sorting/pancake_sort.py) — sort using only prefix reversals - [pigeonhole_sort](algorithms/sorting/pigeonhole_sort.py) — sort by placing elements into pigeonhole buckets - [quick_sort](algorithms/sorting/quick_sort.py) — partition-based divide-and-conquer sort - [radix_sort](algorithms/sorting/radix_sort.py) — non-comparative sort processing one digit at a time - [selection_sort](algorithms/sorting/selection_sort.py) — repeatedly select the minimum and swap it forward - [shell_sort](algorithms/sorting/shell_sort.py) — generalized insertion sort with a decreasing gap sequence - [sort_colors](algorithms/sorting/sort_colors.py) — Dutch national flag three-way partition - [stooge_sort](algorithms/sorting/stooge_sort.py) — recursive sort by dividing into overlapping thirds - [wiggle_sort](algorithms/sorting/wiggle_sort.py) — rearrange into an alternating peak-valley pattern ### Stack - [is_consecutive](algorithms/stack/is_consecutive.py) — check if stack elements are consecutive integers - [is_sorted](algorithms/stack/is_sorted.py) — check if a stack is sorted in ascending order - [longest_abs_path](algorithms/stack/longest_abs_path.py) — find the longest absolute file path in a file system string - [ordered_stack](algorithms/stack/ordered_stack.py) — maintain a stack in sorted order - [remove_min](algorithms/stack/remove_min.py) — remove the minimum element from a stack - [simplify_path](algorithms/stack/simplify_path.py) — simplify a Unix-style file path - [stutter](algorithms/stack/stutter.py) — duplicate each element in a stack - [switch_pairs](algorithms/stack/switch_pairs.py) — swap adjacent pairs of stack elements - [valid_parenthesis](algorithms/stack/valid_parenthesis.py) — check for balanced parentheses / brackets ### Streaming - [misra_gries](algorithms/streaming/misra_gries.py) — approximate frequent-item detection in a data stream - [one_sparse_recovery](algorithms/streaming/one_sparse_recovery.py) — recover a single non-zero element from a stream ### String - [add_binary](algorithms/string/add_binary.py) — add two binary number strings - [alphabet_board_path](algorithms/string/alphabet_board_path.py) — navigate a 5×5 alphabet board to spell a target word - [atbash_cipher](algorithms/string/atbash_cipher.py) — Atbash substitution cipher (reverse the alphabet) - [breaking_bad](algorithms/string/breaking_bad.py) — spell a string using periodic-table element symbols - [caesar_cipher](algorithms/string/caesar_cipher.py) — Caesar shift cipher encryption / decryption - [check_pangram](algorithms/string/check_pangram.py) — check if a string contains every letter of the alphabet - [contain_string](algorithms/string/contain_string.py) — find a substring in a string (strStr) - [count_binary_substring](algorithms/string/count_binary_substring.py) — count substrings with equal consecutive 0s and 1s - [decode_string](algorithms/string/decode_string.py) — decode a run-length encoded string like `3[a2[c]]` - [delete_reoccurring](algorithms/string/delete_reoccurring.py) — remove consecutive duplicate characters - [domain_extractor](algorithms/string/domain_extractor.py) — extract a domain name from a URL - [encode_decode](algorithms/string/encode_decode.py) — encode and decode a list of strings - [first_unique_char](algorithms/string/first_unique_char.py) — find the first non-repeating character - [fizzbuzz](algorithms/string/fizzbuzz.py) — classic FizzBuzz problem - [group_anagrams](algorithms/string/group_anagrams.py) — group strings that are anagrams of each other - [int_to_roman](algorithms/string/int_to_roman.py) — convert an integer to a Roman numeral string - [is_palindrome](algorithms/string/is_palindrome.py) — check if a string reads the same forwards and backwards - [is_rotated](algorithms/string/is_rotated.py) — check if one string is a rotation of another - [judge_circle](algorithms/string/judge_circle.py) — determine if a sequence of moves returns to the origin - [knuth_morris_pratt](algorithms/string/knuth_morris_pratt.py) — KMP linear-time pattern matching - [license_number](algorithms/string/license_number.py) — reformat a license key string with dashes - [longest_common_prefix](algorithms/string/longest_common_prefix.py) — find the longest common prefix among strings - [longest_palindromic_substring](algorithms/string/longest_palindromic_substring.py) — find the longest palindromic substring - [make_sentence](algorithms/string/make_sentence.py) — break a string into valid dictionary words - [manacher](algorithms/string/manacher.py) — find the longest palindromic substring in O(n) time - [merge_string_checker](algorithms/string/merge_string_checker.py) — check if a string is a valid merge of two others - [min_distance](algorithms/string/min_distance.py) — minimum deletions to make two strings equal - [multiply_strings](algorithms/string/multiply_strings.py) — multiply two numbers represented as strings - [one_edit_distance](algorithms/string/one_edit_distance.py) — check if two strings are exactly one edit apart - [panagram](algorithms/string/panagram.py) — find missing letters to complete a pangram - [rabin_karp](algorithms/string/rabin_karp.py) — Rabin-Karp rolling-hash pattern matching - [repeat_string](algorithms/string/repeat_string.py) — minimum string repeats to contain a substring - [repeat_substring](algorithms/string/repeat_substring.py) — check if a string is built from a repeating pattern - [reverse_string](algorithms/string/reverse_string.py) — reverse a string in-place - [reverse_vowel](algorithms/string/reverse_vowel.py) — reverse only the vowels in a string - [reverse_words](algorithms/string/reverse_words.py) — reverse the order of words in a string - [roman_to_int](algorithms/string/roman_to_int.py) — convert a Roman numeral string to an integer - [rotate](algorithms/string/rotate.py) — rotate a string by k positions - [strip_url_params](algorithms/string/strip_url_params.py) — remove duplicate query parameters from a URL - [swap_characters](algorithms/string/swap_characters.py) — check if one character swap can make two strings equal - [strong_password](algorithms/string/strong_password.py) — check minimum changes needed for a strong password - [text_justification](algorithms/string/text_justification.py) — justify text lines to a specified width - [unique_morse](algorithms/string/unique_morse.py) — count unique Morse code representations of words - [validate_coordinates](algorithms/string/validate_coordinates.py) — validate geographic latitude/longitude coordinates - [word_squares](algorithms/string/word_squares.py) — find all valid word squares from a word list - [z_algorithm](algorithms/string/z_algorithm.py) — Z-array computation for linear-time pattern matching ### Tree - [bin_tree_to_list](algorithms/tree/bin_tree_to_list.py) — convert a binary tree to a doubly linked list - [binary_tree_paths](algorithms/tree/binary_tree_paths.py) — enumerate all root-to-leaf paths - [binary_tree_views](algorithms/tree/binary_tree_views.py) — left, right, top, and bottom views of a binary tree - [bst_array_to_bst](algorithms/tree/bst_array_to_bst.py) — convert a sorted array into a height-balanced BST - [bst_closest_value](algorithms/tree/bst_closest_value.py) — find the value closest to a target in a BST - [bst_count_left_node](algorithms/tree/bst_count_left_node.py) — count the number of left-child nodes - [bst_delete_node](algorithms/tree/bst_delete_node.py) — delete a node from a BST while preserving order - [bst_depth_sum](algorithms/tree/bst_depth_sum.py) — sum of node values weighted by their depth - [bst_height](algorithms/tree/bst_height.py) — calculate the height of a binary tree - [bst_is_bst](algorithms/tree/bst_is_bst.py) — validate the binary search tree property - [bst_iterator](algorithms/tree/bst_iterator.py) — lazy in-order iterator for a BST - [bst_kth_smallest](algorithms/tree/bst_kth_smallest.py) — find the k-th smallest element in a BST - [bst_lowest_common_ancestor](algorithms/tree/bst_lowest_common_ancestor.py) — lowest common ancestor exploiting BST ordering - [bst_num_empty](algorithms/tree/bst_num_empty.py) — count empty (null) branches in a tree - [bst_predecessor](algorithms/tree/bst_predecessor.py) — find the in-order predecessor of a BST node - [bst_serialize_deserialize](algorithms/tree/bst_serialize_deserialize.py) — serialize a BST to a string and back - [bst_successor](algorithms/tree/bst_successor.py) — find the in-order successor of a BST node - [bst_unique_bst](algorithms/tree/bst_unique_bst.py) — count structurally unique BSTs for n keys (Catalan number) - [bst_validate_bst](algorithms/tree/bst_validate_bst.py) — validate a BST using min/max range constraints - [construct_tree_postorder_preorder](algorithms/tree/construct_tree_postorder_preorder.py) — reconstruct a tree from pre-order and post-order traversals - [deepest_left](algorithms/tree/deepest_left.py) — find the deepest left leaf node - [invert_tree](algorithms/tree/invert_tree.py) — mirror a binary tree (swap all left/right children) - [is_balanced](algorithms/tree/is_balanced.py) — check if a tree is height-balanced - [is_subtree](algorithms/tree/is_subtree.py) — check if one tree is a subtree of another - [is_symmetric](algorithms/tree/is_symmetric.py) — check if a tree is a mirror of itself - [longest_consecutive](algorithms/tree/longest_consecutive.py) — longest consecutive-value sequence in a tree - [lowest_common_ancestor](algorithms/tree/lowest_common_ancestor.py) — find the lowest common ancestor of two nodes - [max_height](algorithms/tree/max_height.py) — maximum depth (height) of a binary tree - [max_path_sum](algorithms/tree/max_path_sum.py) — maximum sum along any path between two nodes - [min_height](algorithms/tree/min_height.py) — minimum depth from root to the nearest leaf - [path_sum](algorithms/tree/path_sum.py) — check if any root-to-leaf path sums to a target - [path_sum2](algorithms/tree/path_sum2.py) — find all root-to-leaf paths that sum to a target - [pretty_print](algorithms/tree/pretty_print.py) — pretty-print a binary tree to the console - [same_tree](algorithms/tree/same_tree.py) — check if two binary trees are structurally identical - [traversal_inorder](algorithms/tree/traversal_inorder.py) — in-order traversal (left, root, right) - [traversal_level_order](algorithms/tree/traversal_level_order.py) — level-order (breadth-first) traversal - [traversal_postorder](algorithms/tree/traversal_postorder.py) — post-order traversal (left, right, root) - [traversal_preorder](algorithms/tree/traversal_preorder.py) — pre-order traversal (root, left, right) - [traversal_zigzag](algorithms/tree/traversal_zigzag.py) — zigzag (alternating direction) level-order traversal - [trie_add_and_search](algorithms/tree/trie_add_and_search.py) — trie with wildcard `.` search support ## Contributing Thanks for your interest in contributing! There are many ways to get involved. See [CONTRIBUTING.md](CONTRIBUTING.md) for details. ## Maintainers - [Keon Kim](https://github.com/keon) ## Contributors Thanks to [all the contributors](https://github.com/keon/algorithms/graphs/contributors) who helped build this repo. ## License [MIT](LICENSE) ================================================ FILE: algorithms/__init__.py ================================================ """Pythonic data structures and algorithms for education. Shared types are available at the top level:: >>> from algorithms import TreeNode, ListNode, Graph >>> from algorithms.data_structures import BinaryHeap, HashTable >>> from algorithms.graph import dijkstra """ import algorithms.data_structures as data_structures # noqa: F401 from algorithms.common import Graph, ListNode, TreeNode __all__ = ["TreeNode", "ListNode", "Graph", "data_structures"] ================================================ FILE: algorithms/array/__init__.py ================================================ from .delete_nth import delete_nth, delete_nth_naive from .flatten import flatten, flatten_iter from .garage import garage from .josephus import josephus from .limit import limit from .longest_non_repeat import ( get_longest_non_repeat_v1, get_longest_non_repeat_v2, get_longest_non_repeat_v3, longest_non_repeat_v1, longest_non_repeat_v2, ) from .max_ones_index import max_ones_index from .merge_intervals import Interval, merge_intervals from .missing_ranges import missing_ranges from .move_zeros import move_zeros from .n_sum import n_sum from .plus_one import plus_one_v1, plus_one_v2, plus_one_v3 from .remove_duplicates import remove_duplicates from .rotate import rotate_v1, rotate_v2, rotate_v3 from .summarize_ranges import summarize_ranges from .three_sum import three_sum from .top_1 import top_1 from .trimmean import trimmean from .two_sum import two_sum __all__ = [ "delete_nth", "delete_nth_naive", "flatten", "flatten_iter", "garage", "josephus", "limit", "get_longest_non_repeat_v1", "get_longest_non_repeat_v2", "get_longest_non_repeat_v3", "longest_non_repeat_v1", "longest_non_repeat_v2", "max_ones_index", "Interval", "merge_intervals", "missing_ranges", "move_zeros", "n_sum", "plus_one_v1", "plus_one_v2", "plus_one_v3", "remove_duplicates", "rotate_v1", "rotate_v2", "rotate_v3", "summarize_ranges", "three_sum", "top_1", "trimmean", "two_sum", ] ================================================ FILE: algorithms/array/delete_nth.py ================================================ """ Delete Nth Occurrence Given a list and a number N, create a new list that contains each element of the original list at most N times, without reordering. Reference: https://www.geeksforgeeks.org/remove-duplicates-from-an-array/ Complexity: delete_nth_naive: Time: O(n^2) due to list.count() Space: O(n) delete_nth: Time: O(n) Space: O(n) """ from __future__ import annotations import collections def delete_nth_naive(array: list[int], n: int) -> list[int]: """Keep at most n copies of each element using naive counting. Args: array: Source list of integers. n: Maximum number of allowed occurrences per element. Returns: New list with each element appearing at most n times. Examples: >>> delete_nth_naive([1, 2, 3, 1, 2, 1, 2, 3], 2) [1, 2, 3, 1, 2, 3] """ result = [] for num in array: if result.count(num) < n: result.append(num) return result def delete_nth(array: list[int], n: int) -> list[int]: """Keep at most n copies of each element using a hash table. Args: array: Source list of integers. n: Maximum number of allowed occurrences per element. Returns: New list with each element appearing at most n times. Examples: >>> delete_nth([1, 2, 3, 1, 2, 1, 2, 3], 2) [1, 2, 3, 1, 2, 3] """ result = [] counts = collections.defaultdict(int) for element in array: if counts[element] < n: result.append(element) counts[element] += 1 return result ================================================ FILE: algorithms/array/flatten.py ================================================ """ Flatten Arrays Given an array that may contain nested arrays, produce a single flat resultant array. Reference: https://en.wikipedia.org/wiki/Flatten_(higher-order_function) Complexity: Time: O(n) where n is the total number of elements Space: O(n) """ from __future__ import annotations from collections.abc import Generator, Iterable from typing import Any def flatten(input_arr: Iterable[Any], output_arr: list[Any] | None = None) -> list[Any]: """Recursively flatten a nested iterable into a single list. Args: input_arr: A potentially nested iterable to flatten. output_arr: Accumulator list for recursive calls (internal use). Returns: A flat list containing all leaf elements. Examples: >>> flatten([2, 1, [3, [4, 5], 6], 7, [8]]) [2, 1, 3, 4, 5, 6, 7, 8] """ if output_arr is None: output_arr = [] for element in input_arr: if not isinstance(element, str) and isinstance(element, Iterable): flatten(element, output_arr) else: output_arr.append(element) return output_arr def flatten_iter(iterable: Iterable[Any]) -> Generator[Any, None, None]: """Lazily flatten a nested iterable, yielding one element at a time. Args: iterable: A potentially nested iterable to flatten. Returns: A generator producing one-dimensional output. Examples: >>> list(flatten_iter([2, 1, [3, [4, 5], 6], 7, [8]])) [2, 1, 3, 4, 5, 6, 7, 8] """ for element in iterable: if not isinstance(element, str) and isinstance(element, Iterable): yield from flatten_iter(element) else: yield element ================================================ FILE: algorithms/array/garage.py ================================================ """ Garage Parking Rearrangement There is a parking lot with only one empty spot (represented by 0). Given the initial and final states, find the minimum number of moves to rearrange the lot. Each move swaps a car into the empty spot. Reference: https://en.wikipedia.org/wiki/15_puzzle Complexity: Time: O(n^2) worst case Space: O(n) for storing the sequence """ from __future__ import annotations def garage(initial: list[int], final: list[int]) -> tuple[int, list[list[int]]]: """Find the minimum swaps to rearrange a parking lot from initial to final state. Args: initial: Starting arrangement where 0 represents the empty spot. final: Desired arrangement where 0 represents the empty spot. Returns: A tuple of (number_of_steps, sequence_of_states) showing each intermediate arrangement. Examples: >>> garage([1, 2, 3, 0, 4], [0, 3, 2, 1, 4]) (4, [[0, 2, 3, 1, 4], [2, 0, 3, 1, 4], [2, 3, 0, 1, 4], [0, 3, 2, 1, 4]]) """ current = initial[::] sequence = [] steps = 0 while current != final: zero_pos = current.index(0) if zero_pos != final.index(0): target_car = final[zero_pos] target_pos = current.index(target_car) current[zero_pos], current[target_pos] = ( current[target_pos], current[zero_pos], ) else: for i in range(len(current)): if current[i] != final[i]: current[zero_pos], current[i] = current[i], current[zero_pos] break sequence.append(current[::]) steps += 1 return steps, sequence ================================================ FILE: algorithms/array/josephus.py ================================================ """ Josephus Problem People sit in a circular fashion; every k-th person is eliminated until everyone has been removed. Yield the elimination order. Reference: https://en.wikipedia.org/wiki/Josephus_problem Complexity: Time: O(n^2) due to list.pop at arbitrary index Space: O(1) auxiliary (yields in-place) """ from __future__ import annotations from collections.abc import Generator from typing import Any def josephus(items: list[Any], skip: int) -> Generator[Any, None, None]: """Yield elements eliminated in Josephus-problem order. Args: items: List of participants arranged in a circle. skip: Every *skip*-th person is eliminated each round. Returns: A generator yielding eliminated elements in order. Examples: >>> list(josephus([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)) [3, 6, 9, 4, 8, 5, 2, 7, 1] """ skip = skip - 1 index = 0 remaining = len(items) while remaining > 0: index = (skip + index) % remaining yield items.pop(index) remaining -= 1 ================================================ FILE: algorithms/array/limit.py ================================================ """ Limit Array Values Filter an array to include only elements within a specified minimum and maximum range (inclusive). Reference: https://en.wikipedia.org/wiki/Clipping_(signal_processing) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def limit( array: list[int], min_lim: int | None = None, max_lim: int | None = None, ) -> list[int]: """Return elements of array that fall within [min_lim, max_lim]. Args: array: Source list of integers. min_lim: Minimum value (inclusive). Defaults to the array minimum. max_lim: Maximum value (inclusive). Defaults to the array maximum. Returns: A new list containing only values within the specified range. Examples: >>> limit([1, 2, 3, 4, 5], 2, 4) [2, 3, 4] """ if len(array) == 0: return array if min_lim is None: min_lim = min(array) if max_lim is None: max_lim = max(array) return list(filter(lambda x: min_lim <= x <= max_lim, array)) ================================================ FILE: algorithms/array/longest_non_repeat.py ================================================ """ Longest Substring Without Repeating Characters Given a string, find the length of the longest substring without repeating characters. Multiple algorithm variants are provided. Reference: https://leetcode.com/problems/longest-substring-without-repeating-characters/ Complexity: Time: O(n) for all variants Space: O(min(n, m)) where m is the charset size """ from __future__ import annotations def longest_non_repeat_v1(string: str) -> int: """Find the length of the longest substring without repeating characters. Args: string: Input string to search. Returns: Length of the longest non-repeating substring. Examples: >>> longest_non_repeat_v1("abcabcbb") 3 """ if string is None: return 0 char_index = {} max_length = 0 start = 0 for index in range(len(string)): if string[index] in char_index: start = max(char_index[string[index]], start) char_index[string[index]] = index + 1 max_length = max(max_length, index - start + 1) return max_length def longest_non_repeat_v2(string: str) -> int: """Find the length of the longest substring without repeating characters. Args: string: Input string to search. Returns: Length of the longest non-repeating substring. Examples: >>> longest_non_repeat_v2("abcabcbb") 3 """ if string is None: return 0 start, max_length = 0, 0 used_char = {} for index, char in enumerate(string): if char in used_char and start <= used_char[char]: start = used_char[char] + 1 else: max_length = max(max_length, index - start + 1) used_char[char] = index return max_length def get_longest_non_repeat_v1(string: str) -> tuple[int, str]: """Find the longest substring without repeating characters. Args: string: Input string to search. Returns: A tuple of (length, substring) for the longest non-repeating substring. Examples: >>> get_longest_non_repeat_v1("abcabcbb") (3, 'abc') """ if string is None: return 0, "" substring = "" char_index = {} max_length = 0 start = 0 for index in range(len(string)): if string[index] in char_index: start = max(char_index[string[index]], start) char_index[string[index]] = index + 1 if index - start + 1 > max_length: max_length = index - start + 1 substring = string[start : index + 1] return max_length, substring def get_longest_non_repeat_v2(string: str) -> tuple[int, str]: """Find the longest substring without repeating characters. Args: string: Input string to search. Returns: A tuple of (length, substring) for the longest non-repeating substring. Examples: >>> get_longest_non_repeat_v2("abcabcbb") (3, 'abc') """ if string is None: return 0, "" substring = "" start, max_length = 0, 0 used_char = {} for index, char in enumerate(string): if char in used_char and start <= used_char[char]: start = used_char[char] + 1 else: if index - start + 1 > max_length: max_length = index - start + 1 substring = string[start : index + 1] used_char[char] = index return max_length, substring def get_longest_non_repeat_v3(string: str) -> tuple[int, str]: """Find the longest substring without repeating characters using sliding window. Args: string: Input string to search. Returns: A tuple of (length, substring) for the longest non-repeating substring. Examples: >>> get_longest_non_repeat_v3("abcabcbb") (3, 'abc') """ longest_substring = "" seen = set() start_index = 0 for i in range(len(string)): while string[i] in seen: seen.remove(string[start_index]) start_index += 1 seen.add(string[i]) longest_substring = max(longest_substring, string[start_index : i + 1], key=len) return len(longest_substring), longest_substring ================================================ FILE: algorithms/array/max_ones_index.py ================================================ """ Max Ones Index Find the index of the 0 that, when replaced with 1, produces the longest continuous sequence of 1s in a binary array. Returns -1 if no 0 exists. Reference: https://www.geeksforgeeks.org/find-index-0-replaced-1-get-longest-continuous-sequence-1s-binary-array/ Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def max_ones_index(array: list[int]) -> int: """Find the index of 0 to replace with 1 for the longest run of 1s. Args: array: Binary array containing only 0s and 1s. Returns: Index of the 0 to flip, or -1 if no 0 exists. Examples: >>> max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1]) 3 """ length = len(array) max_count = 0 max_index = 0 prev_zero = -1 prev_prev_zero = -1 for current in range(length): if array[current] == 0: if current - prev_prev_zero > max_count: max_count = current - prev_prev_zero max_index = prev_zero prev_prev_zero = prev_zero prev_zero = current if length - prev_prev_zero > max_count: max_index = prev_zero return max_index ================================================ FILE: algorithms/array/merge_intervals.py ================================================ """ Merge Intervals Given a collection of intervals, merge all overlapping intervals into a consolidated set. Reference: https://en.wikipedia.org/wiki/Interval_(mathematics) Complexity: Time: O(n log n) due to sorting Space: O(n) """ from __future__ import annotations class Interval: """A numeric interval [start, end) with merge and comparison support. Args: start: Lower bound of the interval. end: Upper bound of the interval. """ def __init__(self, start: int = 0, end: int = 0) -> None: self.start = start self.end = end def __repr__(self) -> str: return f"Interval ({self.start}, {self.end})" def __iter__(self): return iter(range(self.start, self.end)) def __getitem__(self, index: int) -> int: if index < 0: return self.end + index return self.start + index def __len__(self) -> int: return self.end - self.start def __contains__(self, item: int) -> bool: return self.start >= item >= self.end def __eq__(self, other: object) -> bool: if not isinstance(other, Interval): return NotImplemented return self.start == other.start and self.end == other.end def as_list(self) -> list[int]: """Return interval as a list of integers. Returns: List of integers in the interval range. """ return list(self) @staticmethod def merge(intervals: list[Interval]) -> list[Interval]: """Merge overlapping intervals into a consolidated list. Args: intervals: List of Interval objects to merge. Returns: List of merged, non-overlapping Interval objects. Examples: >>> Interval.merge([Interval(1, 3), Interval(2, 6)]) [Interval (1, 6)] """ out = [] for interval in sorted(intervals, key=lambda i: i.start): if out and interval.start <= out[-1].end: out[-1].end = max(out[-1].end, interval.end) else: out += (interval,) return out @staticmethod def print_intervals(intervals: list[Interval]) -> str: """Format intervals as a string representation. Args: intervals: List of Interval objects to format. Returns: String representation of all intervals. Examples: >>> Interval.print_intervals([Interval(1, 3)]) 'Interval (1, 3)' """ result = [] for interval in intervals: result.append(repr(interval)) return "".join(result) def merge_intervals(intervals: list[list[int]]) -> list[list[int]] | None: """Merge overlapping intervals represented as nested lists. Args: intervals: List of [start, end] pairs to merge. Returns: List of merged [start, end] pairs, or None if input is None. Examples: >>> merge_intervals([[1, 3], [2, 6], [8, 10]]) [[1, 6], [8, 10]] """ if intervals is None: return None intervals.sort(key=lambda i: i[0]) out = [intervals.pop(0)] for interval in intervals: if out[-1][-1] >= interval[0]: out[-1][-1] = max(out[-1][-1], interval[-1]) else: out.append(interval) return out ================================================ FILE: algorithms/array/missing_ranges.py ================================================ """ Missing Ranges Find the ranges of numbers that are missing between a given low and high bound, given a sorted array of integers. Reference: https://leetcode.com/problems/missing-ranges/ Complexity: Time: O(n) Space: O(n) for the result list """ from __future__ import annotations def missing_ranges(array: list[int], low: int, high: int) -> list[tuple[int, int]]: """Find gaps between low and high not covered by elements in array. Args: array: Sorted list of integers within [low, high]. low: Lower bound of the expected range (inclusive). high: Upper bound of the expected range (inclusive). Returns: List of (start, end) tuples representing missing ranges. Examples: >>> missing_ranges([3, 5], 1, 10) [(1, 2), (4, 4), (6, 10)] """ result = [] start = low for num in array: if num == start: start += 1 elif num > start: result.append((start, num - 1)) start = num + 1 if start <= high: result.append((start, high)) return result ================================================ FILE: algorithms/array/move_zeros.py ================================================ """ Move Zeros Move all zeros in an array to the end while preserving the relative order of the non-zero (and non-integer-zero) elements. Reference: https://leetcode.com/problems/move-zeroes/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from typing import Any def move_zeros(array: list[Any]) -> list[Any]: """Move all integer zeros to the end, preserving order of other elements. Boolean False is not treated as zero. Args: array: Input list with mixed types. Returns: New list with all integer 0s moved to the end. Examples: >>> move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]) [False, 1, 1, 2, 1, 3, 'a', 0, 0] """ result = [] zeros = 0 for element in array: if element == 0 and type(element) is not bool: zeros += 1 else: result.append(element) result.extend([0] * zeros) return result ================================================ FILE: algorithms/array/n_sum.py ================================================ """ N-Sum Given an array of integers, find all unique n-tuples that sum to a target value. Supports custom sum, comparison, and equality closures for advanced use cases with non-integer elements. Reference: https://leetcode.com/problems/4sum/ Complexity: Time: O(n^(k-1)) where k is the tuple size Space: O(n^(k-1)) for storing results """ from __future__ import annotations from collections.abc import Callable from typing import Any def n_sum( n: int, nums: list[Any], target: Any, **kv: Callable[..., Any], ) -> list[list[Any]]: """Find all unique n-tuples in nums that sum to target. Args: n: Size of each tuple to find. nums: List of elements to search. target: Desired sum for each n-tuple. **kv: Optional closures: sum_closure(a, b) - returns sum of two elements. compare_closure(num, target) - returns -1, 0, or 1. same_closure(a, b) - returns True if elements are equal. Returns: Sorted list of unique n-tuples (as lists) that sum to target. Examples: >>> n_sum(3, [-1, 0, 1, 2, -1, -4], 0) [[-1, -1, 2], [-1, 0, 1]] """ def _sum_closure_default(a: Any, b: Any) -> Any: return a + b def _compare_closure_default(num: Any, target: Any) -> int: if num < target: return -1 elif num > target: return 1 else: return 0 def _same_closure_default(a: Any, b: Any) -> bool: return a == b def _n_sum_inner(n: int, nums: list[Any], target: Any) -> list[list[Any]]: if n == 2: results = _two_sum(nums, target) else: results = [] prev_num = None for index, num in enumerate(nums): if prev_num is not None and same_closure(prev_num, num): continue prev_num = num n_minus1_results = _n_sum_inner( n - 1, nums[index + 1 :], target - num, ) n_minus1_results = _append_elem_to_each_list(num, n_minus1_results) results += n_minus1_results return _union(results) def _two_sum(nums: list[Any], target: Any) -> list[list[Any]]: nums.sort() left = 0 right = len(nums) - 1 results = [] while left < right: current_sum = sum_closure(nums[left], nums[right]) flag = compare_closure(current_sum, target) if flag == -1: left += 1 elif flag == 1: right -= 1 else: results.append(sorted([nums[left], nums[right]])) left += 1 right -= 1 while left < len(nums) and same_closure(nums[left - 1], nums[left]): left += 1 while right >= 0 and same_closure(nums[right], nums[right + 1]): right -= 1 return results def _append_elem_to_each_list( elem: Any, container: list[list[Any]] ) -> list[list[Any]]: results = [] for elems in container: elems.append(elem) results.append(sorted(elems)) return results def _union( duplicate_results: list[list[Any]], ) -> list[list[Any]]: results = [] if len(duplicate_results) != 0: duplicate_results.sort() results.append(duplicate_results[0]) for result in duplicate_results[1:]: if results[-1] != result: results.append(result) return results sum_closure = kv.get("sum_closure", _sum_closure_default) same_closure = kv.get("same_closure", _same_closure_default) compare_closure = kv.get("compare_closure", _compare_closure_default) nums.sort() return _n_sum_inner(n, nums, target) ================================================ FILE: algorithms/array/plus_one.py ================================================ """ Plus One Given a non-negative number represented as an array of digits (big-endian), add one to the number and return the resulting digit array. Reference: https://leetcode.com/problems/plus-one/ Complexity: Time: O(n) Space: O(n) for v1, O(1) auxiliary for v2 and v3 """ from __future__ import annotations def plus_one_v1(digits: list[int]) -> list[int]: """Add one to a big-endian digit array using manual carry propagation. Args: digits: Non-empty list of digits representing a non-negative integer. Returns: New list of digits representing the input number plus one. Examples: >>> plus_one_v1([1, 2, 9]) [1, 3, 0] """ digits[-1] = digits[-1] + 1 result = [] carry = 0 index = len(digits) - 1 while index >= 0 or carry == 1: digit_sum = 0 if index >= 0: digit_sum += digits[index] if carry: digit_sum += 1 result.append(digit_sum % 10) carry = digit_sum // 10 index -= 1 return result[::-1] def plus_one_v2(digits: list[int]) -> list[int]: """Add one to a big-endian digit array by scanning from the right. Args: digits: Non-empty list of digits representing a non-negative integer. Returns: List of digits representing the input number plus one. Examples: >>> plus_one_v2([1, 2, 9]) [1, 3, 0] """ length = len(digits) for index in range(length - 1, -1, -1): if digits[index] < 9: digits[index] += 1 return digits digits[index] = 0 digits.insert(0, 1) return digits def plus_one_v3(num_arr: list[int]) -> list[int]: """Add one to a big-endian digit array using modular arithmetic. Args: num_arr: Non-empty list of digits representing a non-negative integer. Returns: List of digits representing the input number plus one. Examples: >>> plus_one_v3([1, 2, 9]) [1, 3, 0] """ for idx in reversed(list(enumerate(num_arr))): num_arr[idx[0]] = (num_arr[idx[0]] + 1) % 10 if num_arr[idx[0]]: return num_arr return [1] + num_arr ================================================ FILE: algorithms/array/remove_duplicates.py ================================================ """ Remove Duplicates Remove duplicate elements from an array while preserving the original order. Handles both hashable and unhashable items. Reference: https://en.wikipedia.org/wiki/Duplicate_code Complexity: Time: O(n) for hashable items / O(n^2) worst case for unhashable items Space: O(n) """ from __future__ import annotations from collections.abc import Hashable from typing import Any def remove_duplicates(array: list[Any]) -> list[Any]: """Remove duplicate elements from an array, preserving order. Uses a set for O(1) lookups on hashable items and falls back to linear search for unhashable items. Args: array: Input list potentially containing duplicates. Returns: New list with duplicates removed, original order preserved. Examples: >>> remove_duplicates([1, 1, 2, 2, 3]) [1, 2, 3] """ seen = set() unique_array = [] for item in array: if isinstance(item, Hashable): if item not in seen: seen.add(item) unique_array.append(item) else: if item not in unique_array: unique_array.append(item) return unique_array ================================================ FILE: algorithms/array/rotate.py ================================================ """ Rotate Array Rotate an array of n elements to the right by k steps. Three algorithm variants are provided with different time complexities. Reference: https://leetcode.com/problems/rotate-array/ Complexity: rotate_v1: Time O(n*k), Space O(n) rotate_v2: Time O(n), Space O(n) rotate_v3: Time O(n), Space O(n) """ from __future__ import annotations def rotate_v1(array: list[int], k: int) -> list[int]: """Rotate array to the right by k steps using repeated single shifts. Args: array: List of integers to rotate. k: Number of positions to rotate right. Returns: New rotated list. Examples: >>> rotate_v1([1, 2, 3, 4, 5, 6, 7], 3) [5, 6, 7, 1, 2, 3, 4] """ array = array[:] length = len(array) for _ in range(k): temp = array[length - 1] for position in range(length - 1, 0, -1): array[position] = array[position - 1] array[0] = temp return array def rotate_v2(array: list[int], k: int) -> list[int]: """Rotate array to the right by k steps using three reversals. Args: array: List of integers to rotate. k: Number of positions to rotate right. Returns: New rotated list. Examples: >>> rotate_v2([1, 2, 3, 4, 5, 6, 7], 3) [5, 6, 7, 1, 2, 3, 4] """ array = array[:] def _reverse(arr: list[int], left: int, right: int) -> None: while left < right: arr[left], arr[right] = arr[right], arr[left] left += 1 right -= 1 length = len(array) k = k % length _reverse(array, 0, length - k - 1) _reverse(array, length - k, length - 1) _reverse(array, 0, length - 1) return array def rotate_v3(array: list[int] | None, k: int) -> list[int] | None: """Rotate array to the right by k steps using slicing. Args: array: List of integers to rotate, or None. k: Number of positions to rotate right. Returns: New rotated list, or None if input is None. Examples: >>> rotate_v3([1, 2, 3, 4, 5, 6, 7], 3) [5, 6, 7, 1, 2, 3, 4] """ if array is None: return None length = len(array) k = k % length return array[length - k :] + array[: length - k] ================================================ FILE: algorithms/array/summarize_ranges.py ================================================ """ Summarize Ranges Given a sorted integer array without duplicates, return the summary of its ranges as a list of (start, end) tuples. Reference: https://leetcode.com/problems/summary-ranges/ Complexity: Time: O(n) Space: O(n) for the result list """ from __future__ import annotations def summarize_ranges(array: list[int]) -> list[tuple[int, ...]]: """Summarize consecutive runs in a sorted array as (start, end) tuples. Args: array: Sorted list of unique integers. Returns: List of (start, end) tuples for each consecutive range. Examples: >>> summarize_ranges([0, 1, 2, 4, 5, 7]) [(0, 2), (4, 5), (7, 7)] """ result = [] if len(array) == 0: return [] if len(array) == 1: return [(array[0], array[0])] iterator = iter(array) start = end = next(iterator) for num in iterator: if num - end == 1: end = num else: result.append((start, end)) start = end = num result.append((start, end)) return result ================================================ FILE: algorithms/array/three_sum.py ================================================ """ Three Sum Given an array of integers, find all unique triplets that sum to zero using the two-pointer technique. Reference: https://leetcode.com/problems/3sum/ Complexity: Time: O(n^2) Space: O(n) for the result set """ from __future__ import annotations def three_sum(array: list[int]) -> set[tuple[int, int, int]]: """Find all unique triplets in the array that sum to zero. Args: array: List of integers to search. Returns: Set of sorted tuples, each containing three integers summing to zero. Examples: >>> three_sum([-1, 0, 1, 2, -1, -4]) == {(-1, 0, 1), (-1, -1, 2)} True """ result = set() array.sort() for i in range(len(array) - 2): if i > 0 and array[i] == array[i - 1]: continue left, right = i + 1, len(array) - 1 while left < right: current_sum = array[i] + array[left] + array[right] if current_sum > 0: right -= 1 elif current_sum < 0: left += 1 else: result.add((array[i], array[left], array[right])) while left < right and array[left] == array[left + 1]: left += 1 while left < right and array[right] == array[right - 1]: right -= 1 left += 1 right -= 1 return result ================================================ FILE: algorithms/array/top_1.py ================================================ """ Top 1 (Mode) Find the most frequently occurring value(s) in an array. When multiple values share the highest frequency, all are returned. Reference: https://en.wikipedia.org/wiki/Mode_(statistics) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from typing import Any def top_1(array: list[Any]) -> list[Any]: """Find the statistical mode(s) of an array. Args: array: Input list of comparable elements. Returns: List of element(s) with the highest frequency. Examples: >>> top_1([1, 1, 2, 2, 3]) [1, 2] """ frequency = {} for element in array: if element in frequency: frequency[element] += 1 else: frequency[element] = 1 max_count = max(frequency.values()) result = [] for element, count in frequency.items(): if count == max_count: result.append(element) return result ================================================ FILE: algorithms/array/trimmean.py ================================================ """ Trimmed Mean Compute the mean of an array after discarding a given percentage of the highest and lowest values. Useful for robust averaging in scoring systems. Reference: https://en.wikipedia.org/wiki/Truncated_mean Complexity: Time: O(n log n) due to sorting Space: O(n) for the trimmed copy """ from __future__ import annotations def trimmean(array: list[float], percentage: float) -> float: """Calculate the trimmed mean of an array. Discards the top and bottom halves of the given percentage before computing the arithmetic mean. Args: array: List of numeric values. percentage: Total percentage to trim (split equally between top and bottom). E.g., 20 trims the top 10% and bottom 10%. Returns: The trimmed arithmetic mean. Examples: >>> trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 20) 5.5 """ ratio = percentage / 200 array.sort() trim_count = int(len(array) * ratio) trimmed = array[trim_count : len(array) - trim_count] total = 0 for value in trimmed: total += value return total / len(trimmed) ================================================ FILE: algorithms/array/two_sum.py ================================================ """ Two Sum Given an array of integers and a target sum, return the indices of the two numbers that add up to the target. Reference: https://leetcode.com/problems/two-sum/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def two_sum(array: list[int], target: int) -> tuple[int, int] | None: """Find two indices whose corresponding values sum to target. Args: array: List of integers to search. target: Desired sum of two elements. Returns: Tuple of (index1, index2) if a pair is found, or None otherwise. Examples: >>> two_sum([2, 7, 11, 15], 9) (0, 1) """ seen = {} for index, num in enumerate(array): if num in seen: return seen[num], index else: seen[target - num] = index return None ================================================ FILE: algorithms/backtracking/__init__.py ================================================ from .add_operators import add_operators from .anagram import anagram from .array_sum_combinations import ( array_sum_combinations, unique_array_sum_combinations, ) from .combination_sum import combination_sum from .factor_combinations import get_factors, recursive_get_factors from .find_words import find_words from .generate_abbreviations import generate_abbreviations from .generate_parenthesis import generate_parenthesis_v1, generate_parenthesis_v2 from .letter_combination import letter_combinations from .minimax import minimax from .palindrome_partitioning import ( palindromic_substrings, palindromic_substrings_iter, ) from .pattern_match import pattern_match from .permute import permute, permute_iter, permute_recursive from .permute_unique import permute_unique from .subsets import subsets, subsets_v2 from .subsets_unique import subsets_unique __all__ = [ "add_operators", "anagram", "array_sum_combinations", "unique_array_sum_combinations", "combination_sum", "get_factors", "recursive_get_factors", "find_words", "generate_abbreviations", "generate_parenthesis_v1", "generate_parenthesis_v2", "letter_combinations", "palindromic_substrings", "palindromic_substrings_iter", "pattern_match", "permute", "permute_iter", "permute_recursive", "permute_unique", "subsets", "subsets_v2", "subsets_unique", "minimax", ] ================================================ FILE: algorithms/backtracking/add_operators.py ================================================ """ Expression Add Operators Given a string of digits and a target value, return all possibilities to insert binary operators (+, -, *) between the digits so they evaluate to the target value. Reference: https://leetcode.com/problems/expression-add-operators/ Complexity: Time: O(4^n) worst Space: O(n) recursion depth """ from __future__ import annotations def add_operators(digits: str, target: int) -> list[str]: """Return all expressions formed by inserting +, -, * that equal target. Args: digits: A string containing only digits 0-9. target: The target integer value. Returns: A list of valid expression strings. Examples: >>> add_operators("123", 6) ['1+2+3', '1*2*3'] """ result: list[str] = [] if not digits: return result _dfs(result, "", digits, target, 0, 0, 0) return result def _dfs( result: list[str], path: str, digits: str, target: int, position: int, evaluated: int, multed: int, ) -> None: """Depth-first search helper that builds expressions recursively.""" if position == len(digits): if target == evaluated: result.append(path) return for i in range(position, len(digits)): if i != position and digits[position] == "0": break current = int(digits[position : i + 1]) if position == 0: _dfs(result, path + str(current), digits, target, i + 1, current, current) else: _dfs( result, path + "+" + str(current), digits, target, i + 1, evaluated + current, current, ) _dfs( result, path + "-" + str(current), digits, target, i + 1, evaluated - current, -current, ) _dfs( result, path + "*" + str(current), digits, target, i + 1, evaluated - multed + multed * current, multed * current, ) ================================================ FILE: algorithms/backtracking/anagram.py ================================================ """ Anagram Checker Given two strings, determine if they are anagrams of each other (i.e. one can be rearranged to form the other). Reference: https://en.wikipedia.org/wiki/Anagram Complexity: Time: O(n) where n is the length of the strings Space: O(1) fixed 26-character alphabet """ from __future__ import annotations def anagram(first: str, second: str) -> bool: """Check whether two strings are anagrams of each other. Args: first: The first string (lowercase letters only). second: The second string (lowercase letters only). Returns: True if the strings are anagrams, False otherwise. Examples: >>> anagram('apple', 'pleap') True >>> anagram('apple', 'cherry') False """ count_first = [0] * 26 count_second = [0] * 26 for char in first: index = ord(char) - ord("a") count_first[index] += 1 for char in second: index = ord(char) - ord("a") count_second[index] += 1 return count_first == count_second ================================================ FILE: algorithms/backtracking/array_sum_combinations.py ================================================ """ Array Sum Combinations Given three arrays and a target sum, find all three-element combinations (one element from each array) that add up to the target. Reference: https://en.wikipedia.org/wiki/Subset_sum_problem Complexity: Time: O(n^3) brute-force product of three arrays Space: O(k) where k is the number of valid combinations """ from __future__ import annotations import itertools from functools import partial def array_sum_combinations( array_a: list[int], array_b: list[int], array_c: list[int], target: int, ) -> list[list[int]]: """Find all combinations of one element per array that sum to target. Uses backtracking to enumerate valid combinations, allowing duplicates. Args: array_a: First array of integers. array_b: Second array of integers. array_c: Third array of integers. target: The desired sum. Returns: A list of three-element lists that sum to target. Examples: >>> array_sum_combinations([1], [2], [3], 6) [[1, 2, 3]] """ arrays = [array_a, array_b, array_c] def _is_complete(constructed_so_far: list[int]) -> tuple[bool, bool]: total = sum(constructed_so_far) should_stop = total >= target or len(constructed_so_far) >= 3 reached_target = total == target and len(constructed_so_far) == 3 return should_stop, reached_target def _get_candidates(constructed_so_far: list[int]) -> list[int]: return arrays[len(constructed_so_far)] def _backtrack( constructed_so_far: list[int] | None = None, result: list[list[int]] | None = None, ) -> None: if constructed_so_far is None: constructed_so_far = [] if result is None: result = [] should_stop, reached_target = _is_complete(constructed_so_far) if should_stop: if reached_target: result.append(constructed_so_far) return candidates = _get_candidates(constructed_so_far) for candidate in candidates: constructed_so_far.append(candidate) _backtrack(constructed_so_far[:], result) constructed_so_far.pop() result: list[list[int]] = [] _backtrack([], result) return result def unique_array_sum_combinations( array_a: list[int], array_b: list[int], array_c: list[int], target: int, ) -> list[tuple[int, ...]]: """Find unique combinations of one element per array that sum to target. Uses itertools.product and filters by target sum, returning only unique tuples. Args: array_a: First array of integers. array_b: Second array of integers. array_c: Third array of integers. target: The desired sum. Returns: A list of unique tuples that sum to target. Examples: >>> sorted(unique_array_sum_combinations([1, 2], [2, 3], [3, 4], 6)) [(1, 2, 3)] """ def _check_sum(expected: int, *nums: int) -> tuple[bool, tuple[int, ...]]: if sum(nums) == expected: return (True, nums) else: return (False, nums) product = itertools.product(array_a, array_b, array_c) func = partial(_check_sum, target) sums = list(itertools.starmap(func, product)) seen: set[tuple[int, ...]] = set() for is_match, values in sums: if is_match and values not in seen: seen.add(values) return list(seen) ================================================ FILE: algorithms/backtracking/combination_sum.py ================================================ """ Combination Sum Given a set of candidate numbers (without duplicates) and a target number, find all unique combinations where the candidate numbers sum to the target. The same number may be chosen an unlimited number of times. Reference: https://leetcode.com/problems/combination-sum/ Complexity: Time: O(n^(T/M)) where T is target, M is minimum candidate Space: O(T/M) recursion depth """ from __future__ import annotations def combination_sum(candidates: list[int], target: int) -> list[list[int]]: """Find all unique combinations of candidates that sum to target. Args: candidates: A list of distinct positive integers. target: The target sum. Returns: A list of lists, each containing a valid combination. Examples: >>> combination_sum([2, 3, 6, 7], 7) [[2, 2, 3], [7]] """ result: list[list[int]] = [] candidates.sort() _dfs(candidates, target, 0, [], result) return result def _dfs( nums: list[int], target: int, index: int, path: list[int], result: list[list[int]], ) -> None: """Depth-first search helper for building combinations.""" if target < 0: return if target == 0: result.append(path) return for i in range(index, len(nums)): _dfs(nums, target - nums[i], i, path + [nums[i]], result) ================================================ FILE: algorithms/backtracking/factor_combinations.py ================================================ """ Factor Combinations Given an integer n, return all possible combinations of its factors. Factors should be greater than 1 and less than n. Reference: https://leetcode.com/problems/factor-combinations/ Complexity: Time: O(n * log(n)) approximate Space: O(log(n)) recursion depth """ from __future__ import annotations def get_factors(number: int) -> list[list[int]]: """Return all factor combinations of number using iteration. Args: number: A positive integer. Returns: A list of lists, each containing a valid factorization. Examples: >>> get_factors(12) [[2, 6], [2, 2, 3], [3, 4]] """ todo: list[tuple[int, int, list[int]]] = [(number, 2, [])] combinations: list[list[int]] = [] while todo: remaining, divisor, partial = todo.pop() while divisor * divisor <= remaining: if remaining % divisor == 0: combinations.append(partial + [divisor, remaining // divisor]) todo.append((remaining // divisor, divisor, partial + [divisor])) divisor += 1 return combinations def recursive_get_factors(number: int) -> list[list[int]]: """Return all factor combinations of number using recursion. Args: number: A positive integer. Returns: A list of lists, each containing a valid factorization. Examples: >>> recursive_get_factors(12) [[2, 6], [2, 2, 3], [3, 4]] """ def _factor( remaining: int, divisor: int, partial: list[int], combinations: list[list[int]], ) -> list[list[int]]: while divisor * divisor <= remaining: if remaining % divisor == 0: combinations.append(partial + [divisor, remaining // divisor]) _factor( remaining // divisor, divisor, partial + [divisor], combinations ) divisor += 1 return combinations return _factor(number, 2, [], []) ================================================ FILE: algorithms/backtracking/find_words.py ================================================ """ Word Search II Given a board of characters and a list of words, find all words that can be constructed from adjacent cells (horizontally or vertically). Each cell may only be used once per word. Uses a trie for efficient prefix matching. Reference: https://leetcode.com/problems/word-search-ii/ Complexity: Time: O(M * N * 4^L) where M*N is board size, L is max word length Space: O(W * L) for the trie, where W is number of words """ from __future__ import annotations def find_words(board: list[list[str]], words: list[str]) -> list[str]: """Find all words from the list that exist on the board. Builds a trie from the word list, then uses backtracking to search the board for each word. Args: board: A 2D grid of characters. words: A list of words to search for. Returns: A list of words found on the board. Examples: >>> board = [['o','a','a','n'], ['e','t','a','e']] >>> sorted(find_words(board, ['eat', 'oath'])) ['eat'] """ trie: dict = {} for word in words: current_node = trie for char in word: if char not in current_node: current_node[char] = {} current_node = current_node[char] current_node["#"] = "#" found: set[str] = set() used = [[False] * len(board[0]) for _ in range(len(board))] if board else [] for row in range(len(board)): for col in range(len(board[0])): _backtrack(board, row, col, trie, "", used, found) return list(found) def _backtrack( board: list[list[str]], row: int, col: int, trie: dict, prefix: str, used: list[list[bool]], found: set[str], ) -> None: """Recursively search the board for words matching the trie.""" if "#" in trie: found.add(prefix) if row < 0 or row >= len(board) or col < 0 or col >= len(board[0]): return if not used[row][col] and board[row][col] in trie: used[row][col] = True next_char = board[row][col] _backtrack( board, row + 1, col, trie[next_char], prefix + next_char, used, found ) _backtrack( board, row, col + 1, trie[next_char], prefix + next_char, used, found ) _backtrack( board, row - 1, col, trie[next_char], prefix + next_char, used, found ) _backtrack( board, row, col - 1, trie[next_char], prefix + next_char, used, found ) used[row][col] = False ================================================ FILE: algorithms/backtracking/generate_abbreviations.py ================================================ """ Generalized Abbreviations Given a word, return all possible generalized abbreviations. Each abbreviation replaces contiguous substrings with their lengths. Reference: https://leetcode.com/problems/generalized-abbreviation/ Complexity: Time: O(2^n) where n is the length of the word Space: O(n) recursion depth """ from __future__ import annotations def generate_abbreviations(word: str) -> list[str]: """Generate all possible abbreviations of a word. Args: word: The input word to abbreviate. Returns: A list of all valid abbreviations. Examples: >>> sorted(generate_abbreviations("ab")) ['1b', '2', 'a1', 'ab'] """ result: list[str] = [] _backtrack(result, word, 0, 0, "") return result def _backtrack( result: list[str], word: str, position: int, count: int, current: str, ) -> None: """Recursively build abbreviations by including or skipping characters.""" if position == len(word): if count > 0: current += str(count) result.append(current) return if count > 0: _backtrack(result, word, position + 1, 0, current + str(count) + word[position]) else: _backtrack(result, word, position + 1, 0, current + word[position]) _backtrack(result, word, position + 1, count + 1, current) ================================================ FILE: algorithms/backtracking/generate_parenthesis.py ================================================ """ Generate Parentheses Given n pairs of parentheses, generate all combinations of well-formed parentheses. Reference: https://leetcode.com/problems/generate-parentheses/ Complexity: Time: O(4^n / sqrt(n)) — the n-th Catalan number Space: O(n) recursion depth """ from __future__ import annotations def generate_parenthesis_v1(count: int) -> list[str]: """Generate all valid parenthesis combinations (right-first variant). Builds combinations by tracking remaining left and right parentheses, trying to add a closing paren before an opening one. Args: count: The number of parenthesis pairs. Returns: A list of all valid parenthesis strings. Examples: >>> generate_parenthesis_v1(2) ['()()', '(())'] """ result: list[str] = [] _add_pair_v1(result, "", count, 0) return result def _add_pair_v1( result: list[str], current: str, left: int, right: int, ) -> None: """Recursive helper for v1: tries closing before opening.""" if left == 0 and right == 0: result.append(current) return if right > 0: _add_pair_v1(result, current + ")", left, right - 1) if left > 0: _add_pair_v1(result, current + "(", left - 1, right + 1) def generate_parenthesis_v2(count: int) -> list[str]: """Generate all valid parenthesis combinations (left-first variant). Builds combinations by tracking remaining left and right parentheses, trying to add an opening paren before a closing one. Args: count: The number of parenthesis pairs. Returns: A list of all valid parenthesis strings. Examples: >>> generate_parenthesis_v2(2) ['(())', '()()'] """ result: list[str] = [] _add_pair_v2(result, "", count, count) return result def _add_pair_v2( result: list[str], current: str, left: int, right: int, ) -> None: """Recursive helper for v2: tries opening before closing.""" if left == 0 and right == 0: result.append(current) if left > 0: _add_pair_v2(result, current + "(", left - 1, right) if right > 0 and left < right: _add_pair_v2(result, current + ")", left, right - 1) ================================================ FILE: algorithms/backtracking/letter_combination.py ================================================ """ Letter Combinations of a Phone Number Given a digit string, return all possible letter combinations that the number could represent using a telephone keypad mapping. Reference: https://leetcode.com/problems/letter-combinations-of-a-phone-number/ Complexity: Time: O(4^n) where n is the number of digits Space: O(4^n) for the result list """ from __future__ import annotations def letter_combinations(digits: str) -> list[str]: """Return all letter combinations for a digit string. Args: digits: A string of digits (2-9). Returns: A list of all possible letter combinations. Examples: >>> letter_combinations("23") ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'] """ if digits == "": return [] keypad_map = { "2": "abc", "3": "def", "4": "ghi", "5": "jkl", "6": "mno", "7": "pqrs", "8": "tuv", "9": "wxyz", } combinations: list[str] = [""] for digit in digits: expanded: list[str] = [] for existing in combinations: for char in keypad_map[digit]: expanded.append(existing + char) combinations = expanded return combinations ================================================ FILE: algorithms/backtracking/minimax.py ================================================ """Minimax — game-tree search with alpha-beta pruning. The minimax algorithm finds the optimal move for a two-player zero-sum game. Alpha-beta pruning reduces the search space by eliminating branches that cannot influence the final decision. Inspired by PR #860 (DD2480-group16). """ from __future__ import annotations import math def minimax( depth: int, is_maximizing: bool, scores: list[int], alpha: float = -math.inf, beta: float = math.inf, ) -> float: """Return the minimax value of a perfect binary game tree. *scores* contains the leaf values (length must be a power of 2). *depth* is the current depth (start with log2(len(scores))). >>> minimax(2, True, [3, 5, 2, 9]) 5 >>> minimax(3, True, [3, 5, 2, 9, 12, 5, 23, 23]) 12 """ if depth == 0: return scores[0] mid = len(scores) // 2 if is_maximizing: value = -math.inf value = max( value, minimax(depth - 1, False, scores[:mid], alpha, beta), ) alpha = max(alpha, value) if alpha < beta: value = max( value, minimax(depth - 1, False, scores[mid:], alpha, beta), ) return value else: value = math.inf value = min( value, minimax(depth - 1, True, scores[:mid], alpha, beta), ) beta = min(beta, value) if alpha < beta: value = min( value, minimax(depth - 1, True, scores[mid:], alpha, beta), ) return value ================================================ FILE: algorithms/backtracking/palindrome_partitioning.py ================================================ """ Palindrome Partitioning Given a string, find all ways to partition it into palindromic substrings. There is always at least one way since single characters are palindromes. Reference: https://leetcode.com/problems/palindrome-partitioning/ Complexity: Time: O(n * 2^n) where n is the string length Space: O(n) recursion depth """ from __future__ import annotations from collections.abc import Generator def palindromic_substrings(text: str) -> list[list[str]]: """Return all palindrome partitions of the input string. Args: text: The string to partition. Returns: A list of partitions, each being a list of palindromic substrings. Examples: >>> palindromic_substrings("abc") [['a', 'b', 'c']] """ if not text: return [[]] results: list[list[str]] = [] for i in range(len(text), 0, -1): substring = text[:i] if substring == substring[::-1]: for rest in palindromic_substrings(text[i:]): results.append([substring] + rest) return results def palindromic_substrings_iter(text: str) -> Generator[list[str], None, None]: """Yield all palindrome partitions of the input string via a generator. A slightly more Pythonic approach using a recursive generator. Args: text: The string to partition. Yields: Lists of palindromic substrings forming a valid partition. Examples: >>> list(palindromic_substrings_iter("abc")) [['a', 'b', 'c']] """ if not text: yield [] return for i in range(len(text), 0, -1): substring = text[:i] if substring == substring[::-1]: for rest in palindromic_substrings_iter(text[i:]): yield [substring] + rest ================================================ FILE: algorithms/backtracking/pattern_match.py ================================================ """ Pattern Matching Given a pattern and a string, determine if the string follows the same pattern. A full match means a bijection between each letter in the pattern and a non-empty substring in the string. Reference: https://leetcode.com/problems/word-pattern-ii/ Complexity: Time: O(n^m) where n is string length, m is pattern length Space: O(m) recursion depth plus mapping storage """ from __future__ import annotations def pattern_match(pattern: str, string: str) -> bool: """Check whether a string matches a given pattern via bijection. Args: pattern: A pattern string of lowercase letters. string: The string to match against (lowercase letters). Returns: True if the string follows the pattern, False otherwise. Examples: >>> pattern_match("abab", "redblueredblue") True >>> pattern_match("aabb", "xyzabcxzyabc") False """ return _backtrack(pattern, string, {}) def _backtrack( pattern: str, string: str, mapping: dict[str, str], ) -> bool: """Recursively attempt to match pattern to string using a mapping.""" if len(pattern) == 0 and len(string) > 0: return False if len(pattern) == 0 and len(string) == 0: return True for end in range(1, len(string) - len(pattern) + 2): if pattern[0] not in mapping and string[:end] not in mapping.values(): mapping[pattern[0]] = string[:end] if _backtrack(pattern[1:], string[end:], mapping): return True del mapping[pattern[0]] elif pattern[0] in mapping and mapping[pattern[0]] == string[:end]: if _backtrack(pattern[1:], string[end:], mapping): return True return False ================================================ FILE: algorithms/backtracking/permute.py ================================================ """ Permutations Given a collection of distinct elements, return all possible permutations. Reference: https://en.wikipedia.org/wiki/Permutation Complexity: Time: O(n * n!) where n is the number of elements Space: O(n * n!) to store all permutations """ from __future__ import annotations from collections.abc import Generator def permute(elements: list | str) -> list: """Return all permutations of the given elements. Args: elements: A list or string of distinct elements. Returns: A list of all permutations (same type as input elements). Examples: >>> permute([1, 2, 3]) [[1, 2, 3], [2, 1, 3], [2, 3, 1], [1, 3, 2], [3, 1, 2], [3, 2, 1]] """ if len(elements) <= 1: return [elements] result = [] for perm in permute(elements[1:]): for i in range(len(elements)): result.append(perm[:i] + elements[0:1] + perm[i:]) return result def permute_iter(elements: list | str) -> Generator: """Yield all permutations of the given elements one at a time. Args: elements: A list or string of distinct elements. Yields: One permutation at a time (same type as input elements). Examples: >>> list(permute_iter([1, 2])) [[1, 2], [2, 1]] """ if len(elements) <= 1: yield elements else: for perm in permute_iter(elements[1:]): for i in range(len(elements)): yield perm[:i] + elements[0:1] + perm[i:] def permute_recursive(nums: list[int]) -> list[list[int]]: """Return all permutations using DFS backtracking. Args: nums: A list of distinct integers. Returns: A list of all permutations. Examples: >>> sorted(permute_recursive([1, 2])) [[1, 2], [2, 1]] """ result: list[list[int]] = [] _dfs(result, nums, []) return result def _dfs( result: list[list[int]], nums: list[int], path: list[int], ) -> None: """DFS helper that builds permutations by choosing remaining elements.""" if not nums: result.append(path) for i in range(len(nums)): _dfs(result, nums[:i] + nums[i + 1 :], path + [nums[i]]) ================================================ FILE: algorithms/backtracking/permute_unique.py ================================================ """ Unique Permutations Given a collection of numbers that might contain duplicates, return all possible unique permutations. Reference: https://leetcode.com/problems/permutations-ii/ Complexity: Time: O(n * n!) worst case Space: O(n * n!) to store all unique permutations """ from __future__ import annotations def permute_unique(nums: list[int]) -> list[list[int]]: """Return all unique permutations of a list that may have duplicates. Args: nums: A list of integers, possibly with duplicates. Returns: A list of all unique permutations. Examples: >>> sorted(permute_unique([1, 1, 2])) [[1, 1, 2], [1, 2, 1], [2, 1, 1]] """ permutations: list[list[int]] = [[]] for number in nums: new_permutations: list[list[int]] = [] for existing in permutations: for i in range(len(existing) + 1): new_permutations.append(existing[:i] + [number] + existing[i:]) if i < len(existing) and existing[i] == number: break permutations = new_permutations return permutations ================================================ FILE: algorithms/backtracking/subsets.py ================================================ """ Subsets Given a set of distinct integers, return all possible subsets (the power set). The solution set must not contain duplicate subsets. Reference: https://en.wikipedia.org/wiki/Power_set Complexity: Time: O(2^n) where n is the number of elements Space: O(2^n) to store all subsets """ from __future__ import annotations def subsets(nums: list[int]) -> list[list[int]]: """Return all subsets of the given list using backtracking. Args: nums: A list of distinct integers. Returns: A list of all subsets. Examples: >>> sorted(subsets([1, 2]), key=str) [[], [1], [1, 2], [2]] """ result: list[list[int]] = [] _backtrack(result, nums, [], 0) return result def _backtrack( result: list[list[int]], nums: list[int], stack: list[int], position: int, ) -> None: """Recursive helper that includes or excludes each element.""" if position == len(nums): result.append(list(stack)) else: stack.append(nums[position]) _backtrack(result, nums, stack, position + 1) stack.pop() _backtrack(result, nums, stack, position + 1) def subsets_v2(nums: list[int]) -> list[list[int]]: """Return all subsets of the given list using iteration. Builds subsets iteratively by extending each existing subset with the next element. Args: nums: A list of distinct integers. Returns: A list of all subsets. Examples: >>> sorted(subsets_v2([1, 2]), key=str) [[], [1], [1, 2], [2]] """ result: list[list[int]] = [[]] for number in sorted(nums): result += [item + [number] for item in result] return result ================================================ FILE: algorithms/backtracking/subsets_unique.py ================================================ """ Unique Subsets Given a collection of integers that might contain duplicates, return all possible unique subsets (the power set without duplicates). Reference: https://leetcode.com/problems/subsets-ii/ Complexity: Time: O(2^n) where n is the number of elements Space: O(2^n) to store all subsets """ from __future__ import annotations def subsets_unique(nums: list[int]) -> list[tuple[int, ...]]: """Return all unique subsets of a list that may have duplicates. Args: nums: A list of integers, possibly with duplicates. Returns: A list of unique tuples representing each subset. Examples: >>> sorted(subsets_unique([1, 2, 2])) [(), (1,), (1, 2), (1, 2, 2), (2,), (2, 2)] """ found: set[tuple[int, ...]] = set() _backtrack(found, nums, [], 0) return list(found) def _backtrack( found: set[tuple[int, ...]], nums: list[int], stack: list[int], position: int, ) -> None: """Recursive helper that includes or excludes each element.""" if position == len(nums): found.add(tuple(stack)) else: stack.append(nums[position]) _backtrack(found, nums, stack, position + 1) stack.pop() _backtrack(found, nums, stack, position + 1) ================================================ FILE: algorithms/bit_manipulation/__init__.py ================================================ from .add_bitwise_operator import add_bitwise_operator from .binary_gap import binary_gap from .bit_operation import clear_bit, get_bit, set_bit, update_bit from .bytes_int_conversion import ( bytes_big_endian_to_int, bytes_little_endian_to_int, int_to_bytes_big_endian, int_to_bytes_little_endian, ) from .count_flips_to_convert import count_flips_to_convert from .count_ones import count_ones_iter, count_ones_recur from .find_difference import find_difference from .find_missing_number import find_missing_number, find_missing_number2 from .flip_bit_longest_sequence import flip_bit_longest_seq from .gray_code import gray_code, gray_to_binary from .has_alternative_bit import has_alternative_bit, has_alternative_bit_fast from .insert_bit import insert_mult_bits, insert_one_bit from .power_of_two import is_power_of_two from .remove_bit import remove_bit from .reverse_bits import reverse_bits from .single_number import single_number from .single_number2 import single_number2 from .single_number3 import single_number3 from .subsets import subsets from .swap_pair import swap_pair __all__ = [ "add_bitwise_operator", "binary_gap", "bytes_big_endian_to_int", "bytes_little_endian_to_int", "clear_bit", "count_flips_to_convert", "count_ones_iter", "count_ones_recur", "find_difference", "find_missing_number", "find_missing_number2", "flip_bit_longest_seq", "get_bit", "has_alternative_bit", "has_alternative_bit_fast", "insert_mult_bits", "insert_one_bit", "int_to_bytes_big_endian", "int_to_bytes_little_endian", "is_power_of_two", "remove_bit", "reverse_bits", "set_bit", "single_number", "single_number2", "single_number3", "subsets", "swap_pair", "update_bit", "gray_code", "gray_to_binary", ] ================================================ FILE: algorithms/bit_manipulation/add_bitwise_operator.py ================================================ """ Add Bitwise Operator Add two positive integers without using the '+' operator, using only bitwise operations (AND, XOR, shift). Reference: https://en.wikipedia.org/wiki/Adder_(electronics) Complexity: Time: O(log n) where n is the larger of the two inputs Space: O(1) """ from __future__ import annotations def add_bitwise_operator(first: int, second: int) -> int: """Add two non-negative integers using only bitwise operations. Args: first: First non-negative integer operand. second: Second non-negative integer operand. Returns: The sum of first and second. Examples: >>> add_bitwise_operator(2, 3) 5 >>> add_bitwise_operator(0, 0) 0 """ while second: carry = first & second first = first ^ second second = carry << 1 return first ================================================ FILE: algorithms/bit_manipulation/binary_gap.py ================================================ """ Binary Gap Given a positive integer N, find and return the longest distance between two consecutive 1-bits in the binary representation of N. If there are not two consecutive 1-bits, return 0. Reference: https://en.wikipedia.org/wiki/Hamming_distance Complexity: Time: O(log n) where n is the input integer Space: O(1) """ from __future__ import annotations def binary_gap(number: int) -> int: """Find the longest distance between consecutive 1-bits in binary. Args: number: A positive integer to examine. Returns: The longest gap between consecutive 1-bits, or 0 if fewer than two 1-bits exist. Examples: >>> binary_gap(22) 2 >>> binary_gap(8) 0 """ last_one_position = None longest_gap = 0 index = 0 while number != 0: if number & 1: if last_one_position is not None: longest_gap = max(longest_gap, index - last_one_position) last_one_position = index index += 1 number >>= 1 return longest_gap ================================================ FILE: algorithms/bit_manipulation/bit_operation.py ================================================ """ Fundamental Bit Operations Basic bit manipulation operations: get, set, clear, and update individual bits at a specific position in an integer. Reference: https://en.wikipedia.org/wiki/Bit_manipulation Complexity: Time: O(1) for all operations Space: O(1) """ from __future__ import annotations def get_bit(number: int, position: int) -> int: """Get the bit value at a specific position. Shifts 1 over by *position* bits and ANDs with *number* to isolate the target bit. Args: number: The integer to inspect. position: Zero-based bit index (0 is the least significant bit). Returns: 1 if the bit at *position* is set, 0 otherwise. Examples: >>> get_bit(22, 2) 1 >>> get_bit(22, 3) 0 """ return (number & (1 << position)) != 0 def set_bit(number: int, position: int) -> int: """Set the bit at a specific position to 1. Shifts 1 over by *position* bits and ORs with *number* so that only the bit at *position* is turned on. Args: number: The integer to modify. position: Zero-based bit index to set. Returns: The integer with the bit at *position* set to 1. Examples: >>> set_bit(22, 3) 30 """ return number | (1 << position) def clear_bit(number: int, position: int) -> int: """Clear the bit at a specific position to 0. Creates a mask with all bits set except at *position*, then ANDs with *number*. Args: number: The integer to modify. position: Zero-based bit index to clear. Returns: The integer with the bit at *position* cleared to 0. Examples: >>> clear_bit(22, 2) 18 """ mask = ~(1 << position) return number & mask def update_bit(number: int, position: int, bit: int) -> int: """Update the bit at a specific position to a given value. First clears the bit at *position*, then ORs in the new *bit* value shifted to that position. Args: number: The integer to modify. position: Zero-based bit index to update. bit: The new bit value (0 or 1). Returns: The integer with the bit at *position* set to *bit*. Examples: >>> update_bit(22, 3, 1) 30 >>> update_bit(22, 2, 0) 18 """ mask = ~(1 << position) return (number & mask) | (bit << position) ================================================ FILE: algorithms/bit_manipulation/bytes_int_conversion.py ================================================ """ Bytes-Integer Conversion Convert between Python integers and raw byte sequences in both big-endian and little-endian byte orders. Reference: https://en.wikipedia.org/wiki/Endianness Complexity: Time: O(b) where b is the number of bytes in the representation Space: O(b) """ from __future__ import annotations from collections import deque def int_to_bytes_big_endian(number: int) -> bytes: """Convert a non-negative integer to bytes in big-endian order. Args: number: A non-negative integer to convert. Returns: A bytes object with the most significant byte first. Examples: >>> int_to_bytes_big_endian(17) b'\\x11' """ byte_buffer: deque[int] = deque() while number > 0: byte_buffer.appendleft(number & 0xFF) number >>= 8 return bytes(byte_buffer) def int_to_bytes_little_endian(number: int) -> bytes: """Convert a non-negative integer to bytes in little-endian order. Args: number: A non-negative integer to convert. Returns: A bytes object with the least significant byte first. Examples: >>> int_to_bytes_little_endian(17) b'\\x11' """ byte_buffer: list[int] = [] while number > 0: byte_buffer.append(number & 0xFF) number >>= 8 return bytes(byte_buffer) def bytes_big_endian_to_int(byte_string: bytes) -> int: """Convert a big-endian byte sequence to an integer. Args: byte_string: Bytes with the most significant byte first. Returns: The decoded integer value. Examples: >>> bytes_big_endian_to_int(b'\\x11') 17 """ number = 0 for byte in byte_string: number <<= 8 number += byte return number def bytes_little_endian_to_int(byte_string: bytes) -> int: """Convert a little-endian byte sequence to an integer. Args: byte_string: Bytes with the least significant byte first. Returns: The decoded integer value. Examples: >>> bytes_little_endian_to_int(b'\\x11') 17 """ number = 0 exponent = 0 for byte in byte_string: number += byte << exponent exponent += 8 return number ================================================ FILE: algorithms/bit_manipulation/count_flips_to_convert.py ================================================ """ Count Flips to Convert Determine the minimal number of bits you would need to flip to convert integer A to integer B. Uses XOR to find differing bits and Brian Kernighan's algorithm to count them. Reference: https://en.wikipedia.org/wiki/Hamming_distance Complexity: Time: O(k) where k is the number of differing bits Space: O(1) """ from __future__ import annotations def count_flips_to_convert(first: int, second: int) -> int: """Count the number of bit flips needed to convert one integer to another. Args: first: The source integer. second: The target integer. Returns: The number of bits that differ between *first* and *second*. Examples: >>> count_flips_to_convert(29, 15) 2 >>> count_flips_to_convert(34, 34) 0 """ diff = first ^ second count = 0 while diff: diff &= diff - 1 count += 1 return count ================================================ FILE: algorithms/bit_manipulation/count_ones.py ================================================ """ Count Ones (Hamming Weight) Count the number of 1-bits in the binary representation of an unsigned integer using Brian Kernighan's algorithm. Reference: https://en.wikipedia.org/wiki/Hamming_weight Complexity: Time: O(k) where k is the number of set bits Space: O(1) iterative / O(k) recursive (call stack) """ from __future__ import annotations def count_ones_recur(number: int) -> int: """Count set bits using Brian Kernighan's algorithm (recursive). Args: number: A non-negative integer. Returns: The number of 1-bits in the binary representation. Examples: >>> count_ones_recur(8) 1 >>> count_ones_recur(63) 6 """ if not number: return 0 return 1 + count_ones_recur(number & (number - 1)) def count_ones_iter(number: int) -> int: """Count set bits using Brian Kernighan's algorithm (iterative). Args: number: A non-negative integer. Returns: The number of 1-bits in the binary representation. Examples: >>> count_ones_iter(8) 1 >>> count_ones_iter(63) 6 """ count = 0 while number: number &= number - 1 count += 1 return count ================================================ FILE: algorithms/bit_manipulation/find_difference.py ================================================ """ Find the Difference Given two strings where the second is generated by shuffling the first and adding one extra letter, find the added letter using XOR. Reference: https://en.wikipedia.org/wiki/Exclusive_or Complexity: Time: O(n) where n is the length of the longer string Space: O(1) """ from __future__ import annotations def find_difference(original: str, shuffled: str) -> str: """Find the single character added to a shuffled copy of a string. Uses XOR on all character code points; paired characters cancel out, leaving only the extra character. Args: original: The original string. shuffled: The shuffled string with one extra character. Returns: The single character that was added. Examples: >>> find_difference("abcd", "abecd") 'e' """ xor_result = 0 for character in original + shuffled: xor_result ^= ord(character) return chr(xor_result) ================================================ FILE: algorithms/bit_manipulation/find_missing_number.py ================================================ """ Find Missing Number Given a sequence of unique integers in the range [0..n] with one value missing, find and return that missing number. Two approaches are provided: XOR-based and summation-based. Reference: https://en.wikipedia.org/wiki/Exclusive_or Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def find_missing_number(nums: list[int]) -> int: """Find the missing number using XOR. XORs every element with its expected index so that all paired values cancel out, leaving only the missing number. Args: nums: A list of unique integers from 0..n with one missing. Returns: The missing integer. Examples: >>> find_missing_number([4, 1, 3, 0, 6, 5, 2]) 7 >>> find_missing_number([0]) 1 """ missing = 0 for index, number in enumerate(nums): missing ^= number missing ^= index + 1 return missing def find_missing_number2(nums: list[int]) -> int: """Find the missing number using arithmetic summation. Computes the expected sum of 0..n and subtracts the actual sum of the list to isolate the missing value. Args: nums: A list of unique integers from 0..n with one missing. Returns: The missing integer. Examples: >>> find_missing_number2([4, 1, 3, 0, 6, 5, 2]) 7 >>> find_missing_number2([0]) 1 """ total = sum(nums) length = len(nums) expected_total = length * (length + 1) // 2 return expected_total - total ================================================ FILE: algorithms/bit_manipulation/flip_bit_longest_sequence.py ================================================ """ Flip Bit Longest Sequence Given an integer, find the length of the longest sequence of 1-bits you can create by flipping exactly one 0-bit to a 1-bit. Reference: https://en.wikipedia.org/wiki/Bit_manipulation Complexity: Time: O(b) where b is the number of bits in the integer Space: O(1) """ from __future__ import annotations def flip_bit_longest_seq(number: int) -> int: """Find the longest 1-bit run achievable by flipping a single 0-bit. Tracks the current run length and the previous run length to determine the best sequence that can be formed by bridging two runs with a single flipped bit. Args: number: A non-negative integer. Returns: The length of the longest sequence of 1-bits after one flip. Examples: >>> flip_bit_longest_seq(1775) 8 >>> flip_bit_longest_seq(0) 1 """ current_length = 0 previous_length = 0 max_length = 0 while number: if number & 1 == 1: current_length += 1 elif number & 1 == 0: previous_length = 0 if number & 2 == 0 else current_length current_length = 0 max_length = max(max_length, previous_length + current_length) number >>= 1 return max_length + 1 ================================================ FILE: algorithms/bit_manipulation/gray_code.py ================================================ """Gray code — generate n-bit Gray code sequences. A Gray code is an ordering of binary numbers such that successive values differ in exactly one bit. Used in error correction and rotary encoders. Inspired by PR #932 (Simranstha045). """ from __future__ import annotations def gray_code(n: int) -> list[int]: """Return the n-bit Gray code sequence as a list of integers. Uses the reflection (mirror) construction: gray(i) = i ^ (i >> 1) >>> gray_code(2) [0, 1, 3, 2] >>> gray_code(3) [0, 1, 3, 2, 6, 7, 5, 4] """ return [i ^ (i >> 1) for i in range(1 << n)] def gray_to_binary(gray: int) -> int: """Convert a Gray-coded integer back to standard binary.""" mask = gray >> 1 while mask: gray ^= mask mask >>= 1 return gray ================================================ FILE: algorithms/bit_manipulation/has_alternative_bit.py ================================================ """ Has Alternating Bits Check whether a positive integer has alternating bits, meaning no two adjacent bits share the same value. Reference: https://en.wikipedia.org/wiki/Bit_manipulation Complexity: has_alternative_bit: O(number of bits) has_alternative_bit_fast: O(1) """ from __future__ import annotations def has_alternative_bit(number: int) -> bool: """Check for alternating bits by scanning each pair of adjacent bits. Args: number: A positive integer to check. Returns: True if every pair of adjacent bits differs, False otherwise. Examples: >>> has_alternative_bit(5) True >>> has_alternative_bit(7) False """ first_bit = 0 second_bit = 0 while number: first_bit = number & 1 if number >> 1: second_bit = (number >> 1) & 1 if not first_bit ^ second_bit: return False else: return True number >>= 1 return True def has_alternative_bit_fast(number: int) -> bool: """Check for alternating bits using O(1) bitmask arithmetic. Args: number: A positive integer to check. Returns: True if every pair of adjacent bits differs, False otherwise. Examples: >>> has_alternative_bit_fast(5) True >>> has_alternative_bit_fast(7) False """ mask_even_bits = int("aaaaaaaa", 16) # ...10101010 mask_odd_bits = int("55555555", 16) # ...01010101 return mask_even_bits == (number + (number ^ mask_even_bits)) or mask_odd_bits == ( number + (number ^ mask_odd_bits) ) ================================================ FILE: algorithms/bit_manipulation/insert_bit.py ================================================ """ Insert Bit Insert one or more bits into an integer at a specific bit position. Reference: https://en.wikipedia.org/wiki/Bit_manipulation Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations def insert_one_bit(number: int, bit: int, position: int) -> int: """Insert a single bit at a specific position in an integer. Splits the number at *position*, shifts the upper part left by one to make room, inserts *bit*, and merges with the lower part. Args: number: The integer to modify. bit: The bit value to insert (0 or 1). position: Zero-based index at which to insert the bit. Returns: The resulting integer after insertion. Examples: >>> insert_one_bit(21, 1, 2) 45 >>> insert_one_bit(21, 0, 2) 41 """ upper = number >> position upper = (upper << 1) | bit upper = upper << position lower = ((1 << position) - 1) & number return lower | upper def insert_mult_bits(number: int, bits: int, length: int, position: int) -> int: """Insert multiple bits at a specific position in an integer. Splits the number at *position*, shifts the upper part left by *length* positions, inserts the *bits* value, and merges with the lower part. Args: number: The integer to modify. bits: The bit pattern to insert. length: The number of bits in the pattern. position: Zero-based index at which to insert. Returns: The resulting integer after insertion. Examples: >>> insert_mult_bits(5, 7, 3, 1) 47 >>> insert_mult_bits(5, 7, 3, 3) 61 """ upper = number >> position upper = (upper << length) | bits upper = upper << position lower = ((1 << position) - 1) & number return lower | upper ================================================ FILE: algorithms/bit_manipulation/power_of_two.py ================================================ """ Power of Two Determine whether a given integer is a power of two using bit manipulation. A power of two has exactly one set bit, so ``n & (n - 1)`` clears that bit and yields zero. Reference: https://en.wikipedia.org/wiki/Power_of_two Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations def is_power_of_two(number: int) -> bool: """Check whether an integer is a power of two. Args: number: The integer to test. Returns: True if *number* is a positive power of two, False otherwise. Examples: >>> is_power_of_two(64) True >>> is_power_of_two(91) False >>> is_power_of_two(0) False """ return number > 0 and not number & (number - 1) ================================================ FILE: algorithms/bit_manipulation/remove_bit.py ================================================ """ Remove Bit Remove a single bit at a specific position from an integer, shifting higher bits down to fill the gap. Reference: https://en.wikipedia.org/wiki/Bit_manipulation Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations def remove_bit(number: int, position: int) -> int: """Remove the bit at a specific position from an integer. Splits the number around *position*, shifts the upper part right by one to collapse the gap, and merges with the lower part. Args: number: The integer to modify. position: Zero-based index of the bit to remove. Returns: The resulting integer after removal. Examples: >>> remove_bit(21, 2) 9 >>> remove_bit(21, 4) 5 >>> remove_bit(21, 0) 10 """ upper = number >> (position + 1) upper = upper << position lower = ((1 << position) - 1) & number return upper | lower ================================================ FILE: algorithms/bit_manipulation/reverse_bits.py ================================================ """ Reverse Bits Reverse the bits of a 32-bit unsigned integer. Reference: https://en.wikipedia.org/wiki/Bit_reversal Complexity: Time: O(1) -- always iterates exactly 32 times Space: O(1) """ from __future__ import annotations def reverse_bits(number: int) -> int: """Reverse all 32 bits of an unsigned integer. Args: number: A 32-bit unsigned integer (0 to 2**32 - 1). Returns: The integer formed by reversing the bit order. Examples: >>> reverse_bits(43261596) 964176192 >>> reverse_bits(0) 0 """ result = 0 for _ in range(32): result = (result << 1) + (number & 1) number >>= 1 return result ================================================ FILE: algorithms/bit_manipulation/single_number.py ================================================ """ Single Number Given an array of integers where every element appears twice except for one, find the unique element using XOR. Reference: https://en.wikipedia.org/wiki/Exclusive_or Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def single_number(nums: list[int]) -> int: """Find the element that appears only once (all others appear twice). XORs all values together; paired values cancel to zero, leaving the unique value. Args: nums: A list of integers where every element except one appears an even number of times. Returns: The single element, or 0 if all elements are paired. Examples: >>> single_number([1, 0, 2, 1, 2, 3, 3]) 0 >>> single_number([101]) 101 """ result = 0 for number in nums: result ^= number return result ================================================ FILE: algorithms/bit_manipulation/single_number2.py ================================================ """ Single Number 2 Given an array of integers where every element appears three times except for one (which appears exactly once), find that unique element using constant space and linear time. Reference: https://en.wikipedia.org/wiki/Exclusive_or Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def single_number2(nums: list[int]) -> int: """Find the element that appears once (all others appear three times). Uses two accumulators (*ones* and *twos*) to track bits that have appeared once and twice respectively; bits appearing a third time are cleared from both accumulators. Args: nums: A list of integers where every element except one appears exactly three times. Returns: The single element that appears only once. Examples: >>> single_number2([4, 2, 3, 2, 1, 1, 4, 2, 4, 1]) 3 """ ones, twos = 0, 0 for index in range(len(nums)): ones = (ones ^ nums[index]) & ~twos twos = (twos ^ nums[index]) & ~ones return ones ================================================ FILE: algorithms/bit_manipulation/single_number3.py ================================================ """ Single Number 3 Given an array where exactly two elements appear once and all others appear exactly twice, find those two unique elements in O(n) time and O(1) space. Reference: https://en.wikipedia.org/wiki/Exclusive_or Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def single_number3(nums: list[int]) -> list[int]: """Find the two elements that each appear exactly once. Uses XOR to isolate the combined signature of the two unique values, then partitions all numbers by a distinguishing bit to separate them. Args: nums: A list of integers where exactly two elements appear once and all others appear exactly twice. Returns: A list containing the two unique elements. Examples: >>> sorted(single_number3([1, 2, 1, 3, 2, 5])) [3, 5] """ xor_both = 0 for number in nums: xor_both ^= number rightmost_set_bit = xor_both & (-xor_both) first, second = 0, 0 for number in nums: if number & rightmost_set_bit: first ^= number else: second ^= number return [first, second] ================================================ FILE: algorithms/bit_manipulation/subsets.py ================================================ """ Subsets via Bit Manipulation Generate all possible subsets of a set of distinct integers using bitmask enumeration. Each integer from 0 to 2^n - 1 represents a unique subset. Reference: https://en.wikipedia.org/wiki/Power_set Complexity: Time: O(n * 2^n) Space: O(n * 2^n) """ from __future__ import annotations def subsets(nums: list[int]) -> set[tuple[int, ...]]: """Return all subsets of the given list as a set of tuples. Uses bitmask enumeration: for each number from 0 to 2^n - 1, the set bits indicate which elements are included in that subset. Args: nums: A list of distinct integers. Returns: A set of tuples, each representing one subset. Examples: >>> sorted(subsets([1, 2, 3])) [(), (1,), (1, 2), (1, 2, 3), (1, 3), (2,), (2, 3), (3,)] """ length = len(nums) total = 1 << length result: set[tuple[int, ...]] = set() for mask in range(total): subset = tuple( number for bit_index, number in enumerate(nums) if mask & 1 << bit_index ) result.add(subset) return result ================================================ FILE: algorithms/bit_manipulation/swap_pair.py ================================================ """ Swap Pair Swap odd and even bits of an integer using bitmask operations. Bit 0 is swapped with bit 1, bit 2 with bit 3, and so on. Reference: https://en.wikipedia.org/wiki/Bit_manipulation Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations def swap_pair(number: int) -> int: """Swap every pair of adjacent bits in an integer. Masks odd-positioned bits (0xAAAAAAAA), shifts them right by one, masks even-positioned bits (0x55555555), shifts them left by one, and merges the results. Args: number: A non-negative integer. Returns: The integer with each adjacent pair of bits swapped. Examples: >>> swap_pair(22) 41 >>> swap_pair(10) 5 """ odd_bits = (number & int("AAAAAAAA", 16)) >> 1 even_bits = (number & int("55555555", 16)) << 1 return odd_bits | even_bits ================================================ FILE: algorithms/common/__init__.py ================================================ """Shared data types used across all algorithm categories. These types are the connective tissue of the library — every algorithm accepts and returns these types, making them composable. >>> from algorithms.common import TreeNode, ListNode, Graph """ from algorithms.common.graph import Graph from algorithms.common.list_node import ListNode from algorithms.common.tree_node import TreeNode __all__ = ["TreeNode", "ListNode", "Graph"] ================================================ FILE: algorithms/common/graph.py ================================================ """Graph type shared across all graph algorithms. This module provides the universal Graph used by every graph algorithm in this library. Using a single shared type means you can compose algorithms: build a graph, run BFS to check connectivity, then run Dijkstra for shortest paths — all on the same object. """ from __future__ import annotations from dataclasses import dataclass, field @dataclass class Graph: """A weighted directed graph using adjacency dict representation. Supports weighted/unweighted and directed/undirected graphs. The graph is stored as ``{node: {neighbor: weight}}``. For unweighted graphs, use the :meth:`unweighted` factory or set weights to 1. Attributes: adj: Adjacency dict mapping each node to ``{neighbor: weight}``. directed: Whether this is a directed graph. Examples: >>> g = Graph({"a": {"b": 1, "c": 4}, "b": {"c": 2}}) >>> g.adj["a"] {'b': 1, 'c': 4} >>> g = Graph.unweighted({"a": ["b", "c"], "b": ["c"]}) >>> g.adj["a"] {'b': 1, 'c': 1} """ adj: dict[str, dict[str, float]] = field(default_factory=dict) directed: bool = True @classmethod def unweighted(cls, adj: dict[str, list[str]], directed: bool = True) -> Graph: """Create a graph from an unweighted adjacency list. Args: adj: Mapping of node to list of neighbors. directed: Whether edges are one-directional. Returns: A Graph with all edge weights set to 1. Examples: >>> g = Graph.unweighted({"a": ["b", "c"], "b": ["c"]}) >>> g.adj["b"] {'c': 1} """ weighted = { node: {neighbor: 1 for neighbor in neighbors} for node, neighbors in adj.items() } return cls(adj=weighted, directed=directed) def nodes(self) -> set[str]: """Return all nodes in the graph. Returns: Set of all node identifiers, including nodes that only appear as neighbors. Examples: >>> g = Graph({"a": {"b": 1}}) >>> sorted(g.nodes()) ['a', 'b'] """ all_nodes: set[str] = set(self.adj.keys()) for neighbors in self.adj.values(): all_nodes.update(neighbors.keys()) return all_nodes def add_edge(self, source: str, target: str, weight: float = 1) -> None: """Add an edge to the graph. For undirected graphs, the reverse edge is also added. Args: source: Source node. target: Target node. weight: Edge weight (default 1). Examples: >>> g = Graph() >>> g.add_edge("a", "b", 5) >>> g.adj["a"]["b"] 5 """ if source not in self.adj: self.adj[source] = {} self.adj[source][target] = weight if not self.directed: if target not in self.adj: self.adj[target] = {} self.adj[target][source] = weight ================================================ FILE: algorithms/common/list_node.py ================================================ """Singly linked list node shared across all linked list algorithms. This module provides the universal ListNode used by every linked list algorithm in this library. Using a single shared type means you can compose algorithms: merge two lists, reverse the result, check if it's a palindrome. """ from __future__ import annotations from dataclasses import dataclass @dataclass class ListNode: """A node in a singly linked list. Attributes: val: The value stored in this node. next: Reference to the next node. Examples: >>> head = ListNode(1, ListNode(2, ListNode(3))) >>> head.val 1 >>> head.next.val 2 """ val: int = 0 next: ListNode | None = None ================================================ FILE: algorithms/common/tree_node.py ================================================ """Binary tree node shared across all tree algorithms. This module provides the universal TreeNode used by every tree algorithm in this library. Using a single shared type means you can compose algorithms freely: build a BST, invert it, then traverse it. """ from __future__ import annotations from dataclasses import dataclass @dataclass class TreeNode: """A node in a binary tree. Attributes: val: The value stored in this node. left: Reference to the left child. right: Reference to the right child. Examples: >>> root = TreeNode(1, TreeNode(2), TreeNode(3)) >>> root.left.val 2 >>> root.right.val 3 """ val: int = 0 left: TreeNode | None = None right: TreeNode | None = None ================================================ FILE: algorithms/compression/__init__.py ================================================ from .elias import elias_delta, elias_gamma from .huffman_coding import HuffmanCoding from .rle_compression import decode_rle, encode_rle __all__ = [ "HuffmanCoding", "decode_rle", "elias_delta", "elias_gamma", "encode_rle", ] ================================================ FILE: algorithms/compression/elias.py ================================================ """ Elias Gamma and Delta Coding Universal codes for encoding positive integers. Elias gamma code uses a unary prefix followed by a binary suffix. Elias delta code nests gamma coding for the length prefix. Both were developed by Peter Elias. Reference: https://en.wikipedia.org/wiki/Elias_gamma_coding Complexity: Time: O(log n) per encoded integer Space: O(log n) """ from __future__ import annotations from math import log def _log2(x: int | float) -> float: """Compute log base 2. Args: x: A positive number. Returns: The base-2 logarithm of x. """ return log(x, 2) def _binary(x: int, length: int = 1) -> str: """Return the binary representation of x zero-padded to length digits. Args: x: A non-negative integer. length: Minimum number of binary digits. Returns: A binary string. """ fmt = f"{{0:0{length}b}}" return fmt.format(x) def _unary(x: int) -> str: """Return the unary representation of x. Args: x: A positive integer. Returns: A unary-coded string (x-1 ones followed by a zero). """ return (x - 1) * "1" + "0" def _elias_generic( length_encoding: callable, x: int, ) -> str: """Generic Elias encoding using a pluggable length-encoding function. Args: length_encoding: A function to encode the length prefix. x: A non-negative integer to encode. Returns: The Elias-coded bit string. """ if x == 0: return "0" first_part = 1 + int(_log2(x)) remainder = x - 2 ** int(_log2(x)) bit_count = int(_log2(x)) return length_encoding(first_part) + _binary(remainder, bit_count) def elias_gamma(x: int) -> str: """Encode a positive integer using Elias gamma coding. Args: x: A non-negative integer. Returns: The Elias gamma coded bit string. Examples: >>> elias_gamma(1) '0' >>> elias_gamma(5) '00101' """ return _elias_generic(_unary, x) def elias_delta(x: int) -> str: """Encode a positive integer using Elias delta coding. Args: x: A non-negative integer. Returns: The Elias delta coded bit string. Examples: >>> elias_delta(1) '0' >>> elias_delta(5) '01101' """ return _elias_generic(elias_gamma, x) ================================================ FILE: algorithms/compression/huffman_coding.py ================================================ """ Huffman Coding An efficient method of lossless data compression. Symbols appearing more frequently are encoded with shorter bit strings while less frequent symbols receive longer codes. Reference: https://en.wikipedia.org/wiki/Huffman_coding Complexity: Time: O(n log n) for encoding (heap operations) Space: O(n) for the code table """ from __future__ import annotations import heapq from collections import defaultdict, deque class Node: """A node in the Huffman tree.""" def __init__( self, frequency: int = 0, sign: int | None = None, left: Node | None = None, right: Node | None = None, ) -> None: self.frequency = frequency self.sign = sign self.left = left self.right = right def __lt__(self, other: Node) -> bool: return self.frequency < other.frequency def __gt__(self, other: Node) -> bool: return self.frequency > other.frequency def __eq__(self, other: object) -> bool: if not isinstance(other, Node): return NotImplemented return self.frequency == other.frequency def __str__(self) -> str: return f"" def __repr__(self) -> str: return f"" class HuffmanReader: """Reads Huffman-encoded binary data from a file.""" def __init__(self, file: object) -> None: self.file = file self.buffer: list[str] = [] self.is_last_byte: bool = False def get_number_of_additional_bits_in_the_last_byte(self) -> int: """Read the 3-bit header indicating extra padding bits. Returns: The number of additional padding bits in the last byte. """ bin_num = self.get_bit() + self.get_bit() + self.get_bit() return int(bin_num, 2) def load_tree(self) -> Node: """Reconstruct the Huffman tree from the file header. Returns: The root node of the reconstructed tree. """ node_stack: deque[Node] = deque() queue_leaves: deque[Node] = deque() root = Node() current_node = root is_end_of_tree = False while not is_end_of_tree: current_bit = self.get_bit() if current_bit == "0": current_node.left = Node() current_node.right = Node() node_stack.append(current_node.right) current_node = current_node.left else: queue_leaves.append(current_node) if node_stack: current_node = node_stack.pop() else: is_end_of_tree = True self._fill_tree(queue_leaves) return root def _fill_tree(self, leaves_queue: deque[Node]) -> None: """Load character values into leaf nodes. Args: leaves_queue: Queue of leaf nodes to populate. """ leaves_queue.reverse() while leaves_queue: node = leaves_queue.pop() char_value = int(self.get_byte(), 2) node.sign = char_value def _load_byte(self, buff_limit: int = 8) -> bool: """Load the next byte into the buffer if needed. Args: buff_limit: Minimum buffer size before loading. Returns: True if enough bits are available for reading. """ if len(self.buffer) <= buff_limit: byte = self.file.read(1) if not byte: return False integer = int.from_bytes(byte, "big") self.buffer.extend(list(f"{integer:08b}")) return True def get_bit(self, buff_limit: int = 8) -> str | int: """Read a single bit from the buffer. Args: buff_limit: Minimum buffer size before loading. Returns: A '0' or '1' character, or -1 if at end of file. """ if self._load_byte(buff_limit): return self.buffer.pop(0) return -1 def get_byte(self) -> str | int: """Read eight bits from the buffer. Returns: An 8-character binary string, or -1 if at end of file. """ if self._load_byte(): byte_list = self.buffer[:8] self.buffer = self.buffer[8:] return "".join(byte_list) return -1 class HuffmanWriter: """Writes Huffman-encoded binary data to a file.""" def __init__(self, file: object) -> None: self.file = file self.buffer: str = "" self.saved_bits: int = 0 def write_char(self, char: str) -> None: """Write a character as its 8-bit ordinal value. Args: char: A single character to write. """ self.write_int(ord(char)) def write_int(self, num: int) -> None: """Write an integer as an 8-bit binary value. Args: num: An integer (0-255) to write. """ bin_int = f"{num:08b}" self.write_bits(bin_int) def write_bits(self, bits: str) -> None: """Write a string of bits, flushing complete bytes. Args: bits: A string of '0' and '1' characters. """ self.saved_bits += len(bits) self.buffer += bits while len(self.buffer) >= 8: integer = int(self.buffer[:8], 2) self.file.write(bytes([integer])) self.buffer = self.buffer[8:] def save_tree(self, tree: Node) -> None: """Serialize the Huffman tree structure to the file. Args: tree: The root node of the Huffman tree. """ signs: list[int] = [] tree_code = "" def _get_code_tree(node: Node) -> None: nonlocal tree_code if node.sign is not None: signs.append(node.sign) if node.left: tree_code += "0" _get_code_tree(node.left) if node.right: tree_code += "1" _get_code_tree(node.right) _get_code_tree(tree) self.write_bits(tree_code + "1") for int_sign in signs: self.write_int(int_sign) def _save_information_about_additional_bits(self, additional_bits: int) -> None: """Overwrite the first three bits to record padding count. Args: additional_bits: The number of padding bits appended. """ self.file.seek(0) first_byte_raw = self.file.read(1) self.file.seek(0) first_byte = "{:08b}".format(int.from_bytes(first_byte_raw, "big")) first_byte = f"{additional_bits:03b}" + first_byte[3:] self.write_bits(first_byte) def close(self) -> None: """Flush remaining bits with padding and finalize the file.""" additional_bits = 8 - len(self.buffer) if additional_bits != 8: self.write_bits("0" * additional_bits) self._save_information_about_additional_bits(additional_bits) class TreeFinder: """Traverses a Huffman tree to decode individual symbols.""" def __init__(self, tree: Node) -> None: self.root = tree self.current_node = tree self.found: int | str | None = None def find(self, bit: str) -> bool: """Advance one step in the tree and check for a decoded symbol. Args: bit: '0' for left, '1' for right. Returns: True if a symbol was found at the current node. """ if bit == "0": self.current_node = self.current_node.left elif bit == "1": self.current_node = self.current_node.right else: self._reset() return True if self.current_node.sign is not None: self._reset(self.current_node.sign) return True return False def _reset(self, found: int | str = "") -> None: """Reset traversal to the root after finding a symbol. Args: found: The decoded symbol value. """ self.found = found self.current_node = self.root class HuffmanCoding: """Provides static methods for Huffman file encoding and decoding.""" def __init__(self) -> None: pass @staticmethod def decode_file(file_in_name: str, file_out_name: str) -> None: """Decode a Huffman-encoded file. Args: file_in_name: Path to the encoded input file. file_out_name: Path to the decoded output file. """ with open(file_in_name, "rb") as file_in, open(file_out_name, "wb") as file_out: reader = HuffmanReader(file_in) additional_bits = reader.get_number_of_additional_bits_in_the_last_byte() tree = reader.load_tree() HuffmanCoding._decode_and_write_signs_to_file( file_out, reader, tree, additional_bits ) @staticmethod def _decode_and_write_signs_to_file( file: object, reader: HuffmanReader, tree: Node, additional_bits: int, ) -> None: """Decode bits from reader and write decoded bytes to file. Args: file: The output file object. reader: The HuffmanReader providing encoded bits. tree: The root of the Huffman tree. additional_bits: Number of padding bits in the last byte. """ tree_finder = TreeFinder(tree) is_end_of_file = False while not is_end_of_file: bit = reader.get_bit() if bit != -1: while not tree_finder.find(bit): bit = reader.get_bit(0) file.write(bytes([tree_finder.found])) else: is_end_of_file = True last_byte = reader.buffer last_byte = last_byte[:-additional_bits] for bit in last_byte: if tree_finder.find(bit): file.write(bytes([tree_finder.found])) @staticmethod def encode_file(file_in_name: str, file_out_name: str) -> None: """Encode a file using Huffman coding. Args: file_in_name: Path to the raw input file. file_out_name: Path to the encoded output file. """ with ( open(file_in_name, "rb") as file_in, open(file_out_name, mode="wb+") as file_out, ): signs_frequency = HuffmanCoding._get_char_frequency(file_in) file_in.seek(0) tree = HuffmanCoding._create_tree(signs_frequency) codes = HuffmanCoding._generate_codes(tree) writer = HuffmanWriter(file_out) writer.write_bits("000") writer.save_tree(tree) HuffmanCoding._encode_and_write_signs_to_file(file_in, writer, codes) writer.close() @staticmethod def _encode_and_write_signs_to_file( file: object, writer: HuffmanWriter, codes: dict[int, str] ) -> None: """Read bytes from file and write their Huffman codes. Args: file: The input file object. writer: The HuffmanWriter for output. codes: Mapping of byte values to Huffman code strings. """ sign = file.read(1) while sign: int_char = int.from_bytes(sign, "big") writer.write_bits(codes[int_char]) sign = file.read(1) @staticmethod def _get_char_frequency(file: object) -> dict[int, int]: """Count byte frequencies in a file. Args: file: The input file object. Returns: A dict mapping byte values to their frequencies. """ is_end_of_file = False signs_frequency: dict[int, int] = defaultdict(lambda: 0) while not is_end_of_file: prev_pos = file.tell() sign = file.read(1) curr_pos = file.tell() if prev_pos == curr_pos: is_end_of_file = True else: signs_frequency[int.from_bytes(sign, "big")] += 1 return signs_frequency @staticmethod def _generate_codes(tree: Node) -> dict[int, str]: """Generate Huffman codes from the tree. Args: tree: The root of the Huffman tree. Returns: A dict mapping byte values to their binary code strings. """ codes: dict[int, str] = {} HuffmanCoding._go_through_tree_and_create_codes(tree, "", codes) return codes @staticmethod def _create_tree(signs_frequency: dict[int, int]) -> Node: """Build a Huffman tree from character frequencies. Args: signs_frequency: Mapping of byte values to frequencies. Returns: The root node of the constructed Huffman tree. """ nodes = [ Node(frequency=frequency, sign=char_int) for char_int, frequency in signs_frequency.items() ] heapq.heapify(nodes) while len(nodes) > 1: left = heapq.heappop(nodes) right = heapq.heappop(nodes) new_node = Node( frequency=left.frequency + right.frequency, left=left, right=right, ) heapq.heappush(nodes, new_node) return nodes[0] @staticmethod def _go_through_tree_and_create_codes( tree: Node, code: str, dict_codes: dict[int, str] ) -> None: """Recursively traverse the tree to build code mappings. Args: tree: The current node being visited. code: The accumulated bit string for this path. dict_codes: The output dict to populate. """ if tree.sign is not None: dict_codes[tree.sign] = code if tree.left: HuffmanCoding._go_through_tree_and_create_codes( tree.left, code + "0", dict_codes ) if tree.right: HuffmanCoding._go_through_tree_and_create_codes( tree.right, code + "1", dict_codes ) ================================================ FILE: algorithms/compression/rle_compression.py ================================================ """ Run-Length Encoding (RLE) A simple lossless compression algorithm that encodes consecutive repeated characters as a count followed by the character. Decompression fully recovers the original data. Reference: https://en.wikipedia.org/wiki/Run-length_encoding Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def encode_rle(data: str) -> str: """Compress a string using run-length encoding. Args: data: The input string to compress. Returns: The RLE-encoded string. Examples: >>> encode_rle("aaabbc") '3a2b1c' >>> encode_rle("") '' """ if not data: return "" encoded: str = "" prev_char: str = "" count: int = 1 for char in data: if char != prev_char: if prev_char: encoded += str(count) + prev_char count = 1 prev_char = char else: count += 1 return encoded + str(count) + prev_char def decode_rle(data: str) -> str: """Decompress a run-length encoded string. Args: data: The RLE-encoded string. Returns: The decoded original string. Examples: >>> decode_rle("3a2b1c") 'aaabbc' >>> decode_rle("") '' """ decoded: str = "" count: str = "" for char in data: if not char.isdigit(): decoded += char * int(count) count = "" else: count += char return decoded ================================================ FILE: algorithms/data_structures/__init__.py ================================================ """Reusable data structure implementations. This package contains the core data structures used throughout the library. Each module provides a self-contained implementation suitable for study. >>> from algorithms.data_structures import BinaryHeap, ArrayStack """ # Tree data structures (moved from tree/ subdirectories in Phase 8) from algorithms.data_structures.avl_tree import AvlTree from algorithms.data_structures.b_tree import BTree from algorithms.data_structures.bst import BST from algorithms.data_structures.fenwick_tree import Fenwick_Tree from algorithms.data_structures.graph import DirectedEdge, DirectedGraph, Node from algorithms.data_structures.hash_table import HashTable, ResizableHashTable from algorithms.data_structures.heap import AbstractHeap, BinaryHeap from algorithms.data_structures.iterative_segment_tree import SegmentTree from algorithms.data_structures.kd_tree import KDTree from algorithms.data_structures.linked_list import ( DoublyLinkedListNode, SinglyLinkedListNode, ) from algorithms.data_structures.priority_queue import PriorityQueue, PriorityQueueNode from algorithms.data_structures.queue import ( AbstractQueue, ArrayQueue, LinkedListQueue, QueueNode, ) from algorithms.data_structures.red_black_tree import RBTree from algorithms.data_structures.segment_tree import SegmentTree as SegmentTreeRecursive from algorithms.data_structures.separate_chaining_hash_table import ( SeparateChainingHashTable, ) from algorithms.data_structures.sqrt_decomposition import SqrtDecomposition from algorithms.data_structures.stack import ( AbstractStack, ArrayStack, LinkedListStack, StackNode, ) from algorithms.data_structures.trie import Trie from algorithms.data_structures.union_find import Union __all__ = [ "AbstractHeap", "AbstractQueue", "AbstractStack", "ArrayQueue", "ArrayStack", "BinaryHeap", "DirectedEdge", "DirectedGraph", "DoublyLinkedListNode", "HashTable", "LinkedListQueue", "LinkedListStack", "Node", "PriorityQueue", "PriorityQueueNode", "QueueNode", "ResizableHashTable", "SeparateChainingHashTable", "SinglyLinkedListNode", "StackNode", "Union", # Tree data structures "AvlTree", "BTree", "BST", "Fenwick_Tree", "RBTree", "SegmentTree", "SegmentTreeRecursive", "Trie", "KDTree", "SqrtDecomposition", ] ================================================ FILE: algorithms/data_structures/avl_tree.py ================================================ """Imports TreeNodes""" from algorithms.common.tree_node import TreeNode class AvlTree: """ An avl tree. """ def __init__(self): # Root node of the tree. self.node = None self.height = -1 self.balance = 0 def insert(self, key): """ Insert new key into node """ # Create new node node = TreeNode(key) if not self.node: self.node = node self.node.left = AvlTree() self.node.right = AvlTree() elif key < self.node.val: self.node.left.insert(key) elif key > self.node.val: self.node.right.insert(key) self.re_balance() def re_balance(self): """ Re balance tree. After inserting or deleting a node, """ self.update_heights(recursive=False) self.update_balances(False) while self.balance < -1 or self.balance > 1: if self.balance > 1: if self.node.left.balance < 0: self.node.left.rotate_left() self.update_heights() self.update_balances() self.rotate_right() self.update_heights() self.update_balances() if self.balance < -1: if self.node.right.balance > 0: self.node.right.rotate_right() self.update_heights() self.update_balances() self.rotate_left() self.update_heights() self.update_balances() def update_heights(self, recursive=True): """ Update tree height """ if self.node: if recursive: if self.node.left: self.node.left.update_heights() if self.node.right: self.node.right.update_heights() self.height = 1 + max(self.node.left.height, self.node.right.height) else: self.height = -1 def update_balances(self, recursive=True): """ Calculate tree balance factor """ if self.node: if recursive: if self.node.left: self.node.left.update_balances() if self.node.right: self.node.right.update_balances() self.balance = self.node.left.height - self.node.right.height else: self.balance = 0 def rotate_right(self): """ Right rotation """ new_root = self.node.left.node new_left_sub = new_root.right.node old_root = self.node self.node = new_root old_root.left.node = new_left_sub new_root.right.node = old_root def rotate_left(self): """ Left rotation """ new_root = self.node.right.node new_left_sub = new_root.left.node old_root = self.node self.node = new_root old_root.right.node = new_left_sub new_root.left.node = old_root def in_order_traverse(self): """ In-order traversal of the tree """ result = [] if not self.node: return result result.extend(self.node.left.in_order_traverse()) result.append(self.node.val) result.extend(self.node.right.in_order_traverse()) return result ================================================ FILE: algorithms/data_structures/b_tree.py ================================================ """ B-Tree A self-balancing tree data structure optimized for disk operations. Each node (except root) contains at least t-1 keys and at most 2t-1 keys, where t is the minimum degree. The tree grows upward from the root. Reference: https://en.wikipedia.org/wiki/B-tree Complexity: Time: O(log n) for search, insert, and delete Space: O(n) """ from __future__ import annotations class Node: """A node in a B-tree containing keys and child pointers. Examples: >>> node = Node() >>> node.keys [] """ def __init__(self) -> None: self.keys: list = [] self.children: list[Node] = [] def __repr__(self) -> str: """Return a string representation of the node. Returns: A string showing the node's keys. """ return f"" @property def is_leaf(self) -> bool: """Check whether this node is a leaf. Returns: True if the node has no children, False otherwise. """ return len(self.children) == 0 class BTree: """A B-tree data structure supporting search, insertion, and deletion. Args: t_val: The minimum degree of the B-tree. Examples: >>> bt = BTree(2) >>> bt.insert_key(10) >>> bt.find(10) True """ def __init__(self, t_val: int = 2) -> None: self.min_numbers_of_keys = t_val - 1 self.max_number_of_keys = 2 * t_val - 1 self.root = Node() def _split_child(self, parent: Node, child_index: int) -> None: """Split a full child node into two nodes. Args: parent: The parent node whose child is being split. child_index: The index of the child to split. """ new_right_child = Node() half_max = self.max_number_of_keys // 2 child = parent.children[child_index] middle_key = child.keys[half_max] new_right_child.keys = child.keys[half_max + 1 :] child.keys = child.keys[:half_max] if not child.is_leaf: new_right_child.children = child.children[half_max + 1 :] child.children = child.children[: half_max + 1] parent.keys.insert(child_index, middle_key) parent.children.insert(child_index + 1, new_right_child) def insert_key(self, key: int) -> None: """Insert a key into the B-tree. Args: key: The key to insert. """ if len(self.root.keys) >= self.max_number_of_keys: new_root = Node() new_root.children.append(self.root) self.root = new_root self._split_child(new_root, 0) self._insert_to_nonfull_node(self.root, key) else: self._insert_to_nonfull_node(self.root, key) def _insert_to_nonfull_node(self, node: Node, key: int) -> None: """Insert a key into a non-full node. Args: node: The non-full node to insert into. key: The key to insert. """ i = len(node.keys) - 1 while i >= 0 and node.keys[i] >= key: i -= 1 if node.is_leaf: node.keys.insert(i + 1, key) else: if len(node.children[i + 1].keys) >= self.max_number_of_keys: self._split_child(node, i + 1) if node.keys[i + 1] < key: i += 1 self._insert_to_nonfull_node(node.children[i + 1], key) def find(self, key: int) -> bool: """Search for a key in the B-tree. Args: key: The key to search for. Returns: True if the key is found, False otherwise. Examples: >>> bt = BTree(2) >>> bt.insert_key(5) >>> bt.find(5) True >>> bt.find(3) False """ current_node = self.root while True: i = len(current_node.keys) - 1 while i >= 0 and current_node.keys[i] > key: i -= 1 if i >= 0 and current_node.keys[i] == key: return True if current_node.is_leaf: return False current_node = current_node.children[i + 1] def remove_key(self, key: int) -> None: """Remove a key from the B-tree. Args: key: The key to remove. """ self._remove_key(self.root, key) def _remove_key(self, node: Node, key: int) -> bool: """Recursively remove a key from the subtree rooted at node. Args: node: The root of the subtree to remove from. key: The key to remove. Returns: True if the key was found and removed, False otherwise. """ try: key_index = node.keys.index(key) if node.is_leaf: node.keys.remove(key) else: self._remove_from_nonleaf_node(node, key_index) return True except ValueError: if node.is_leaf: return False else: i = 0 number_of_keys = len(node.keys) while i < number_of_keys and key > node.keys[i]: i += 1 action_performed = self._repair_tree(node, i) if action_performed: return self._remove_key(node, key) else: return self._remove_key(node.children[i], key) def _repair_tree(self, node: Node, child_index: int) -> bool: """Repair the tree after a deletion to maintain B-tree properties. Args: node: The parent node of the child that may need repair. child_index: The index of the child to check. Returns: True if a structural repair was performed, False otherwise. """ child = node.children[child_index] if self.min_numbers_of_keys < len(child.keys) <= self.max_number_of_keys: return False if ( child_index > 0 and len(node.children[child_index - 1].keys) > self.min_numbers_of_keys ): self._rotate_right(node, child_index) return True if ( child_index < len(node.children) - 1 and len(node.children[child_index + 1].keys) > self.min_numbers_of_keys ): self._rotate_left(node, child_index) return True if child_index > 0: self._merge(node, child_index - 1, child_index) else: self._merge(node, child_index, child_index + 1) return True def _rotate_left(self, parent_node: Node, child_index: int) -> None: """Take a key from the right sibling and transfer it to the child. Args: parent_node: The parent node. child_index: The index of the child receiving the key. """ new_child_key = parent_node.keys[child_index] new_parent_key = parent_node.children[child_index + 1].keys.pop(0) parent_node.children[child_index].keys.append(new_child_key) parent_node.keys[child_index] = new_parent_key if not parent_node.children[child_index + 1].is_leaf: ownerless_child = parent_node.children[child_index + 1].children.pop(0) parent_node.children[child_index].children.append(ownerless_child) def _rotate_right(self, parent_node: Node, child_index: int) -> None: """Take a key from the left sibling and transfer it to the child. Args: parent_node: The parent node. child_index: The index of the child receiving the key. """ parent_key = parent_node.keys[child_index - 1] new_parent_key = parent_node.children[child_index - 1].keys.pop() parent_node.children[child_index].keys.insert(0, parent_key) parent_node.keys[child_index - 1] = new_parent_key if not parent_node.children[child_index - 1].is_leaf: ownerless_child = parent_node.children[child_index - 1].children.pop() parent_node.children[child_index].children.insert(0, ownerless_child) def _merge( self, parent_node: Node, to_merge_index: int, transferred_child_index: int ) -> None: """Merge two child nodes and a parent key into a single node. Args: parent_node: The parent node. to_merge_index: Index of the child that receives the merged data. transferred_child_index: Index of the child being merged in. """ from_merge_node = parent_node.children.pop(transferred_child_index) parent_key_to_merge = parent_node.keys.pop(to_merge_index) to_merge_node = parent_node.children[to_merge_index] to_merge_node.keys.append(parent_key_to_merge) to_merge_node.keys.extend(from_merge_node.keys) if not to_merge_node.is_leaf: to_merge_node.children.extend(from_merge_node.children) if parent_node == self.root and not parent_node.keys: self.root = to_merge_node def _remove_from_nonleaf_node( self, node: Node, key_index: int ) -> None: """Remove a key from a non-leaf node by replacing with predecessor/successor. Args: node: The non-leaf node containing the key. key_index: The index of the key to remove. """ key = node.keys[key_index] left_subtree = node.children[key_index] if len(left_subtree.keys) > self.min_numbers_of_keys: largest_key = self._find_largest_and_delete_in_left_subtree(left_subtree) elif len(node.children[key_index + 1].keys) > self.min_numbers_of_keys: largest_key = self._find_largest_and_delete_in_right_subtree( node.children[key_index + 1] ) else: self._merge(node, key_index, key_index + 1) return self._remove_key(node, key) node.keys[key_index] = largest_key def _find_largest_and_delete_in_left_subtree(self, node: Node) -> int: """Find and remove the largest key in the left subtree. Args: node: The root of the subtree. Returns: The largest key that was removed. """ if node.is_leaf: return node.keys.pop() else: ch_index = len(node.children) - 1 self._repair_tree(node, ch_index) largest_key_in_subtree = self._find_largest_and_delete_in_left_subtree( node.children[len(node.children) - 1] ) return largest_key_in_subtree def _find_largest_and_delete_in_right_subtree(self, node: Node) -> int: """Find and remove the smallest key in the right subtree. Args: node: The root of the subtree. Returns: The smallest key that was removed. """ if node.is_leaf: return node.keys.pop(0) else: ch_index = 0 self._repair_tree(node, ch_index) largest_key_in_subtree = self._find_largest_and_delete_in_right_subtree( node.children[0] ) return largest_key_in_subtree def traverse_tree(self) -> list: """Traverse the B-tree in order and return all keys. Returns: A list of all keys in sorted order. Examples: >>> bt = BTree(2) >>> for k in [3, 1, 2]: bt.insert_key(k) >>> bt.traverse_tree() [1, 2, 3] """ result: list = [] self._traverse_tree(self.root, result) return result def _traverse_tree(self, node: Node, result: list) -> None: """Recursively traverse the subtree and collect keys. Args: node: The root of the subtree to traverse. result: The list to append keys to. """ if node.is_leaf: result.extend(node.keys) else: for i, key in enumerate(node.keys): self._traverse_tree(node.children[i], result) result.append(key) self._traverse_tree(node.children[-1], result) ================================================ FILE: algorithms/data_structures/bst.py ================================================ """Binary Search Tree implementation. A BST is a node-based binary tree where each node's left subtree contains only nodes with data less than the node's data, and the right subtree contains only nodes with data greater than the node's data. Operations and complexities (n = number of nodes): - insert: O(log n) average, O(n) worst case - search: O(log n) average, O(n) worst case - size: O(n) - preorder: O(n) - inorder: O(n) - postorder: O(n) """ from __future__ import annotations class Node: def __init__(self, data: int) -> None: self.data: int = data self.left: Node | None = None self.right: Node | None = None class BST: def __init__(self) -> None: self.root: Node | None = None def get_root(self) -> Node | None: return self.root def size(self) -> int: """Return the number of nodes in the tree. Complexity: O(n).""" return self._recur_size(self.root) def _recur_size(self, root: Node | None) -> int: if root is None: return 0 return 1 + self._recur_size(root.left) + self._recur_size(root.right) def search(self, data: int) -> bool: """Return True if data exists in the tree. Complexity: O(log n) average.""" return self._recur_search(self.root, data) def _recur_search(self, root: Node | None, data: int) -> bool: if root is None: return False if root.data == data: return True elif data > root.data: return self._recur_search(root.right, data) else: return self._recur_search(root.left, data) def insert(self, data: int) -> bool: """Insert data into the tree. Return False if data already exists. Complexity: O(log n) average. """ if self.root: return self._recur_insert(self.root, data) else: self.root = Node(data) return True def _recur_insert(self, root: Node, data: int) -> bool: if root.data == data: return False elif data < root.data: if root.left: return self._recur_insert(root.left, data) else: root.left = Node(data) return True else: if root.right: return self._recur_insert(root.right, data) else: root.right = Node(data) return True def preorder(self, root: Node | None) -> list[int]: """Return list of node values in preorder (root, left, right).""" result: list[int] = [] if root: result.append(root.data) result.extend(self.preorder(root.left)) result.extend(self.preorder(root.right)) return result def inorder(self, root: Node | None) -> list[int]: """Return list of node values in inorder (left, root, right).""" result: list[int] = [] if root: result.extend(self.inorder(root.left)) result.append(root.data) result.extend(self.inorder(root.right)) return result def postorder(self, root: Node | None) -> list[int]: """Return list of node values in postorder (left, right, root).""" result: list[int] = [] if root: result.extend(self.postorder(root.left)) result.extend(self.postorder(root.right)) result.append(root.data) return result ================================================ FILE: algorithms/data_structures/fenwick_tree.py ================================================ """ Fenwick Tree / Binary Indexed Tree Consider we have an array arr[0 . . . n-1]. We would like to 1. Compute the sum of the first i elements. 2. Modify the value of a specified element of the array arr[i] = x where 0 <= i <= n-1. A simple solution is to run a loop from 0 to i-1 and calculate the sum of the elements. To update a value, simply do arr[i] = x. The first operation takes O(n) time and the second operation takes O(1) time. Another simple solution is to create an extra array and store the sum of the first i-th elements at the i-th index in this new array. The sum of a given range can now be calculated in O(1) time, but the update operation takes O(n) time now. This works well if there are a large number of query operations but a very few number of update operations. There are two solutions that can perform both the query and update operations in O(logn) time. 1. Fenwick Tree 2. Segment Tree Compared with Segment Tree, Binary Indexed Tree requires less space and is easier to implement. """ class Fenwick_Tree: # noqa: N801 def __init__(self, freq): self.arr = freq self.n = len(freq) def get_sum(self, bit_tree, i): """ Returns sum of arr[0..index]. This function assumes that the array is preprocessed and partial sums of array elements are stored in bit_tree[]. """ s = 0 # index in bit_tree[] is 1 more than the index in arr[] i = i + 1 # Traverse ancestors of bit_tree[index] while i > 0: # Add current element of bit_tree to sum s += bit_tree[i] # Move index to parent node in getSum View i -= i & (-i) return s def update_bit(self, bit_tree, i, v): """ Updates a node in Binary Index Tree (bit_tree) at given index in bit_tree. The given value 'val' is added to bit_tree[i] and all of its ancestors in tree. """ # index in bit_ree[] is 1 more than the index in arr[] i += 1 # Traverse all ancestors and add 'val' while i <= self.n: # Add 'val' to current node of bit_tree bit_tree[i] += v # Update index to that of parent in update View i += i & (-i) def construct(self): """ Constructs and returns a Binary Indexed Tree for given array of size n. """ # Create and initialize bit_ree[] as 0 bit_tree = [0] * (self.n + 1) # Store the actual values in bit_ree[] using update() for i in range(self.n): self.update_bit(bit_tree, i, self.arr[i]) return bit_tree ================================================ FILE: algorithms/data_structures/graph.py ================================================ """ Graph Data Structures Reusable classes for representing nodes, directed edges and directed graphs. These can be shared across graph algorithms. """ from __future__ import annotations class Node: """A node (vertex) in a graph.""" def __init__(self, name: str) -> None: self.name = name @staticmethod def get_name(obj: object) -> str: """Return the name of *obj* whether it is a Node or a string. Args: obj: A Node instance or a string. Returns: The string name. """ if isinstance(obj, Node): return obj.name if isinstance(obj, str): return obj return "" def __eq__(self, obj: object) -> bool: return self.name == self.get_name(obj) def __repr__(self) -> str: return self.name def __hash__(self) -> int: return hash(self.name) def __ne__(self, obj: object) -> bool: return self.name != self.get_name(obj) def __lt__(self, obj: object) -> bool: return self.name < self.get_name(obj) def __le__(self, obj: object) -> bool: return self.name <= self.get_name(obj) def __gt__(self, obj: object) -> bool: return self.name > self.get_name(obj) def __ge__(self, obj: object) -> bool: return self.name >= self.get_name(obj) def __bool__(self) -> bool: return bool(self.name) class DirectedEdge: """A directed edge connecting two nodes.""" def __init__(self, node_from: Node, node_to: Node) -> None: self.source = node_from self.target = node_to def __eq__(self, obj: object) -> bool: if isinstance(obj, DirectedEdge): return obj.source == self.source and obj.target == self.target return False def __repr__(self) -> str: return f"({self.source} -> {self.target})" class DirectedGraph: """A directed graph storing nodes, edges and an adjacency list.""" def __init__(self, load_dict: dict[str, list[str]] | None = None) -> None: """Build a directed graph, optionally from a dictionary. Args: load_dict: Optional adjacency dict ``{vertex: [neighbours]}``. """ if load_dict is None: load_dict = {} self.nodes: list[Node] = [] self.edges: list[DirectedEdge] = [] self.adjacency_list: dict[Node, list[Node]] = {} if load_dict and isinstance(load_dict, dict): for vertex in load_dict: node_from = self.add_node(vertex) self.adjacency_list[node_from] = [] for neighbor in load_dict[vertex]: node_to = self.add_node(neighbor) self.adjacency_list[node_from].append(node_to) self.add_edge(vertex, neighbor) def add_node(self, node_name: str) -> Node: """Add a named node (or return it if it already exists). Args: node_name: Name of the node. Returns: The Node instance. """ try: return self.nodes[self.nodes.index(node_name)] except ValueError: node = Node(node_name) self.nodes.append(node) return node def add_edge(self, node_name_from: str, node_name_to: str) -> None: """Add a directed edge between two named nodes. Args: node_name_from: Source node name. node_name_to: Target node name. """ try: node_from = self.nodes[self.nodes.index(node_name_from)] node_to = self.nodes[self.nodes.index(node_name_to)] self.edges.append(DirectedEdge(node_from, node_to)) except ValueError: pass ================================================ FILE: algorithms/data_structures/hash_table.py ================================================ """ Hash Table (Open Addressing) Hash map implementation using open addressing with linear probing for collision resolution. Includes a resizable variant that doubles capacity when the load factor reaches two-thirds. Reference: https://en.wikipedia.org/wiki/Open_addressing Complexity: Time: O(1) average for put/get/del, O(n) worst case Space: O(n) """ from __future__ import annotations class HashTable: """Hash table using open addressing with linear probing. Examples: >>> ht = HashTable(10) >>> ht.put(1, 'one') >>> ht.get(1) 'one' """ _empty = object() _deleted = object() def __init__(self, size: int = 11) -> None: """Initialize the hash table. Args: size: Number of slots in the underlying array. """ self.size = size self._len = 0 self._keys: list[object] = [self._empty] * size self._values: list[object] = [self._empty] * size def put(self, key: int, value: object) -> None: """Insert or update a key-value pair. Args: key: The key to insert. value: The value associated with the key. Raises: ValueError: If the table is full. """ initial_hash = hash_ = self.hash(key) while True: if self._keys[hash_] is self._empty or self._keys[hash_] is self._deleted: self._keys[hash_] = key self._values[hash_] = value self._len += 1 return elif self._keys[hash_] == key: self._keys[hash_] = key self._values[hash_] = value return hash_ = self._rehash(hash_) if initial_hash == hash_: raise ValueError("Table is full") def get(self, key: int) -> object | None: """Retrieve the value for a given key. Args: key: The key to look up. Returns: The value associated with the key, or None if not found. """ initial_hash = hash_ = self.hash(key) while True: if self._keys[hash_] is self._empty: return None elif self._keys[hash_] == key: return self._values[hash_] hash_ = self._rehash(hash_) if initial_hash == hash_: return None def del_(self, key: int) -> None: """Delete a key-value pair. Args: key: The key to delete. """ initial_hash = hash_ = self.hash(key) while True: if self._keys[hash_] is self._empty: return None elif self._keys[hash_] == key: self._keys[hash_] = self._deleted self._values[hash_] = self._deleted self._len -= 1 return hash_ = self._rehash(hash_) if initial_hash == hash_: return None def hash(self, key: int) -> int: """Compute the hash index for a key. Args: key: The key to hash. Returns: Index into the internal array. """ return key % self.size def _rehash(self, old_hash: int) -> int: """Linear probing rehash. Args: old_hash: The previous hash index. Returns: Next index to probe. """ return (old_hash + 1) % self.size def __getitem__(self, key: int) -> object | None: return self.get(key) def __delitem__(self, key: int) -> None: return self.del_(key) def __setitem__(self, key: int, value: object) -> None: self.put(key, value) def __len__(self) -> int: return self._len class ResizableHashTable(HashTable): """Hash table that automatically doubles in size when load exceeds 2/3. Examples: >>> rht = ResizableHashTable() >>> rht.put(1, 'a') >>> rht.get(1) 'a' """ MIN_SIZE = 8 def __init__(self) -> None: super().__init__(self.MIN_SIZE) def put(self, key: int, value: object) -> None: """Insert or update, resizing if load factor exceeds two-thirds. Args: key: The key to insert. value: The value associated with the key. """ super().put(key, value) if len(self) >= (self.size * 2) / 3: self._resize() def _resize(self) -> None: """Double the table size and rehash all existing entries.""" keys, values = self._keys, self._values self.size *= 2 self._len = 0 self._keys = [self._empty] * self.size self._values = [self._empty] * self.size for key, value in zip(keys, values, strict=False): if key is not self._empty and key is not self._deleted: self.put(key, value) ================================================ FILE: algorithms/data_structures/heap.py ================================================ r""" Binary Heap A min heap is a complete binary tree where each node is smaller than its children. The root is the minimum element. Uses an array representation with index 0 as a sentinel. Reference: https://en.wikipedia.org/wiki/Binary_heap Complexity: Time: O(log n) for insert and remove_min Space: O(n) """ from __future__ import annotations from abc import ABCMeta, abstractmethod class AbstractHeap(metaclass=ABCMeta): """Abstract base class for binary heap implementations.""" def __init__(self) -> None: # noqa: B027 """Initialize the abstract heap.""" @abstractmethod def perc_up(self, index: int) -> None: """Percolate element up to restore heap property. Args: index: Index of the element to percolate up. """ @abstractmethod def insert(self, val: int) -> None: """Insert a value into the heap. Args: val: The value to insert. """ @abstractmethod def perc_down(self, index: int) -> None: """Percolate element down to restore heap property. Args: index: Index of the element to percolate down. """ @abstractmethod def min_child(self, index: int) -> int: """Return the index of the smaller child. Args: index: Index of the parent node. Returns: Index of the smaller child. """ @abstractmethod def remove_min(self) -> int: """Remove and return the minimum element. Returns: The minimum value in the heap. """ class BinaryHeap(AbstractHeap): """Min binary heap using array representation. Examples: >>> heap = BinaryHeap() >>> heap.insert(5) >>> heap.insert(3) >>> heap.remove_min() 3 """ def __init__(self) -> None: """Initialize the binary heap with a sentinel at index 0.""" self.current_size: int = 0 self.heap: list[int] = [0] def perc_up(self, index: int) -> None: """Percolate element up to maintain the min-heap invariant. Args: index: Index of the element to percolate up. """ while index // 2 > 0: if self.heap[index] < self.heap[index // 2]: self.heap[index], self.heap[index // 2] = ( self.heap[index // 2], self.heap[index], ) index = index // 2 else: break def insert(self, val: int) -> None: """Insert a value into the heap. Args: val: The value to insert. """ self.heap.append(val) self.current_size = self.current_size + 1 self.perc_up(self.current_size) def min_child(self, index: int) -> int: """Return the index of the smaller child of a parent node. Args: index: Index of the parent node. Returns: Index of the smaller child. """ if 2 * index + 1 > self.current_size: return 2 * index if self.heap[2 * index] > self.heap[2 * index + 1]: return 2 * index + 1 return 2 * index def perc_down(self, index: int) -> None: """Percolate element down to maintain the min-heap invariant. Args: index: Index of the element to percolate down. """ while 2 * index <= self.current_size: smaller_child = self.min_child(index) if self.heap[smaller_child] < self.heap[index]: self.heap[smaller_child], self.heap[index] = ( self.heap[index], self.heap[smaller_child], ) index = smaller_child def remove_min(self) -> int: """Remove and return the minimum element from the heap. Returns: The minimum value. """ ret = self.heap[1] self.heap[1] = self.heap[self.current_size] self.current_size = self.current_size - 1 self.heap.pop() self.perc_down(1) return ret ================================================ FILE: algorithms/data_structures/iterative_segment_tree.py ================================================ """ SegmentTree creates a segment tree with a given array and a "commutative" function, this non-recursive version uses less memory than the recursive version and include: 1. range queries in log(N) time 2. update an element in log(N) time the function should be commutative and takes 2 values and returns the same type value Examples - mytree = SegmentTree([2, 4, 5, 3, 4],max) print(mytree.query(2, 4)) mytree.update(3, 6) print(mytree.query(0, 3)) ... mytree = SegmentTree([4, 5, 2, 3, 4, 43, 3], lambda a, b: a + b) print(mytree.query(0, 6)) mytree.update(2, -10) print(mytree.query(0, 6)) ... mytree = SegmentTree([(1, 2), (4, 6), (4, 5)], lambda a, b: (a[0] + b[0], a[1] + b[1])) print(mytree.query(0, 2)) mytree.update(2, (-1, 2)) print(mytree.query(0, 2)) ... """ class SegmentTree: def __init__(self, arr, function): self.tree = [None for _ in range(len(arr))] + arr self.size = len(arr) self.fn = function self.build_tree() def build_tree(self): for i in range(self.size - 1, 0, -1): self.tree[i] = self.fn(self.tree[i * 2], self.tree[i * 2 + 1]) def update(self, p, v): p += self.size self.tree[p] = v while p > 1: p = p // 2 self.tree[p] = self.fn(self.tree[p * 2], self.tree[p * 2 + 1]) def query(self, left, r): left, r = left + self.size, r + self.size res = None while left <= r: if left % 2 == 1: res = self.tree[left] if res is None else self.fn(res, self.tree[left]) if r % 2 == 0: res = self.tree[r] if res is None else self.fn(res, self.tree[r]) left, r = (left + 1) // 2, (r - 1) // 2 return res ================================================ FILE: algorithms/data_structures/kd_tree.py ================================================ """KD-tree — a space-partitioning tree for k-dimensional points. Supports efficient nearest-neighbour and range queries. Inspired by PR #915 (gjones1077). """ from __future__ import annotations import math from typing import Any class KDNode: """A single node in a KD-tree.""" __slots__ = ("point", "left", "right", "axis") def __init__( self, point: tuple[float, ...], left: KDNode | None = None, right: KDNode | None = None, axis: int = 0, ) -> None: self.point = point self.left = left self.right = right self.axis = axis class KDTree: """A k-dimensional tree built from a list of points. >>> tree = KDTree([(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)]) >>> tree.nearest((9, 2)) (8, 1) """ def __init__(self, points: list[tuple[float, ...]]) -> None: self.k = len(points[0]) if points else 0 self.root = self._build(list(points), depth=0) def _build(self, points: list[tuple[float, ...]], depth: int) -> KDNode | None: if not points: return None axis = depth % self.k points.sort(key=lambda p: p[axis]) mid = len(points) // 2 return KDNode( point=points[mid], left=self._build(points[:mid], depth + 1), right=self._build(points[mid + 1 :], depth + 1), axis=axis, ) def nearest(self, target: tuple[float, ...]) -> tuple[float, ...]: """Return the point closest to *target*.""" best: list[Any] = [None, math.inf] self._nearest(self.root, target, best) return best[0] def _nearest( self, node: KDNode | None, target: tuple[float, ...], best: list[Any], ) -> None: if node is None: return dist = _sq_dist(node.point, target) if dist < best[1]: best[0], best[1] = node.point, dist axis = node.axis diff = target[axis] - node.point[axis] close, away = (node.left, node.right) if diff <= 0 else (node.right, node.left) self._nearest(close, target, best) if diff * diff < best[1]: self._nearest(away, target, best) def _sq_dist(a: tuple[float, ...], b: tuple[float, ...]) -> float: return sum((x - y) ** 2 for x, y in zip(a, b, strict=False)) ================================================ FILE: algorithms/data_structures/linked_list.py ================================================ """ Linked List Node Definitions Basic node classes for singly and doubly linked lists, serving as foundational building blocks for linked list algorithms. Reference: https://en.wikipedia.org/wiki/Linked_list Complexity: Time: O(1) for node creation Space: O(1) per node """ from __future__ import annotations class SinglyLinkedListNode: """A node in a singly linked list. Attributes: value: The value stored in the node. next: Reference to the next node, or None. """ def __init__(self, value: object) -> None: self.value = value self.next: SinglyLinkedListNode | None = None class DoublyLinkedListNode: """A node in a doubly linked list. Attributes: value: The value stored in the node. next: Reference to the next node, or None. prev: Reference to the previous node, or None. """ def __init__(self, value: object) -> None: self.value = value self.next: DoublyLinkedListNode | None = None self.prev: DoublyLinkedListNode | None = None ================================================ FILE: algorithms/data_structures/priority_queue.py ================================================ """ Priority Queue (Linear Array) A priority queue implementation using a sorted linear array. Elements are inserted in order so that extraction of the minimum is O(1). Reference: https://en.wikipedia.org/wiki/Priority_queue Complexity: Time: O(n) for push, O(1) for pop Space: O(n) """ from __future__ import annotations import itertools from collections.abc import Iterable from typing import Any class PriorityQueueNode: """A node holding data and its priority. Args: data: The stored value. priority: The priority of this node. """ def __init__(self, data: Any, priority: Any) -> None: self.data = data self.priority = priority def __repr__(self) -> str: """Return a string representation of the node. Returns: Formatted string with data and priority. """ return f"{self.data}: {self.priority}" class PriorityQueue: """Priority queue backed by a sorted linear array. Examples: >>> pq = PriorityQueue([3, 1, 2]) >>> pq.pop() 1 >>> pq.size() 2 """ def __init__( self, items: Iterable[Any] | None = None, priorities: Iterable[Any] | None = None, ) -> None: """Create a priority queue, optionally from items and priorities. Args: items: Initial items to insert. priorities: Corresponding priorities; defaults to item values. """ self.priority_queue_list: list[PriorityQueueNode] = [] if items is None: return if priorities is None: priorities = itertools.repeat(None) for item, priority in zip(items, priorities, strict=False): self.push(item, priority=priority) def __repr__(self) -> str: """Return a string representation of the priority queue. Returns: Formatted string. """ return f"PriorityQueue({self.priority_queue_list!r})" def size(self) -> int: """Return the number of elements in the queue. Returns: The queue size. """ return len(self.priority_queue_list) def push(self, item: Any, priority: Any = None) -> None: """Insert an item with the given priority. Args: item: The value to insert. priority: Priority value; defaults to the item itself. """ priority = item if priority is None else priority node = PriorityQueueNode(item, priority) for index, current in enumerate(self.priority_queue_list): if current.priority < node.priority: self.priority_queue_list.insert(index, node) return self.priority_queue_list.append(node) def pop(self) -> Any: """Remove and return the item with the lowest priority. Returns: The data of the lowest-priority node. """ return self.priority_queue_list.pop().data ================================================ FILE: algorithms/data_structures/queue.py ================================================ """ Queue Abstract Data Type Implementations of the queue ADT using both a fixed-size array and a linked list. Both support enqueue, dequeue, peek, is_empty, len, and iter. Reference: https://en.wikipedia.org/wiki/Queue_(abstract_data_type) Complexity: Time: O(1) for enqueue/dequeue/peek (amortized for ArrayQueue) Space: O(n) """ from __future__ import annotations from abc import ABCMeta, abstractmethod from collections.abc import Iterator class AbstractQueue(metaclass=ABCMeta): """Abstract base class for queue implementations.""" def __init__(self) -> None: self._size = 0 def __len__(self) -> int: return self._size def is_empty(self) -> bool: """Check if the queue is empty. Returns: True if the queue has no elements. """ return self._size == 0 @abstractmethod def enqueue(self, value: object) -> None: pass @abstractmethod def dequeue(self) -> object: pass @abstractmethod def peek(self) -> object: pass @abstractmethod def __iter__(self) -> Iterator[object]: pass class ArrayQueue(AbstractQueue): """Queue implemented with a dynamic array. Examples: >>> q = ArrayQueue() >>> q.enqueue(1) >>> q.dequeue() 1 """ def __init__(self, capacity: int = 10) -> None: """Initialize with a fixed-capacity array. Args: capacity: Initial capacity of the underlying array. """ super().__init__() self._array: list[object | None] = [None] * capacity self._front = 0 self._rear = 0 def __iter__(self) -> Iterator[object]: probe = self._front while True: if probe == self._rear: return yield self._array[probe] probe += 1 def enqueue(self, value: object) -> None: """Add an item to the rear of the queue. Args: value: The value to enqueue. """ if self._rear == len(self._array): self._expand() self._array[self._rear] = value self._rear += 1 self._size += 1 def dequeue(self) -> object: """Remove and return the front item. Returns: The front element. Raises: IndexError: If the queue is empty. """ if self.is_empty(): raise IndexError("Queue is empty") value = self._array[self._front] self._array[self._front] = None self._front += 1 self._size -= 1 return value def peek(self) -> object: """Return the front element without removing it. Returns: The front element. Raises: IndexError: If the queue is empty. """ if self.is_empty(): raise IndexError("Queue is empty") return self._array[self._front] def _expand(self) -> None: """Double the size of the underlying array.""" self._array += [None] * len(self._array) class QueueNode: """A single node in a linked-list-based queue.""" def __init__(self, value: object) -> None: self.value = value self.next: QueueNode | None = None class LinkedListQueue(AbstractQueue): """Queue implemented with a singly linked list. Examples: >>> q = LinkedListQueue() >>> q.enqueue(1) >>> q.dequeue() 1 """ def __init__(self) -> None: super().__init__() self._front: QueueNode | None = None self._rear: QueueNode | None = None def __iter__(self) -> Iterator[object]: probe = self._front while True: if probe is None: return yield probe.value probe = probe.next def enqueue(self, value: object) -> None: """Add an item to the rear of the queue. Args: value: The value to enqueue. """ node = QueueNode(value) if self._front is None: self._front = node self._rear = node else: self._rear.next = node self._rear = node self._size += 1 def dequeue(self) -> object: """Remove and return the front item. Returns: The front element. Raises: IndexError: If the queue is empty. """ if self.is_empty(): raise IndexError("Queue is empty") value = self._front.value if self._front is self._rear: self._front = None self._rear = None else: self._front = self._front.next self._size -= 1 return value def peek(self) -> object: """Return the front element without removing it. Returns: The front element. Raises: IndexError: If the queue is empty. """ if self.is_empty(): raise IndexError("Queue is empty") return self._front.value ================================================ FILE: algorithms/data_structures/red_black_tree.py ================================================ """ Implementation of Red-Black tree. """ class RBNode: def __init__(self, val, is_red, parent=None, left=None, right=None): self.val = val self.parent = parent self.left = left self.right = right self.color = is_red class RBTree: def __init__(self): self.root = None def left_rotate(self, node): # set the node as the left child node of the current node's right node right_node = node.right if right_node is None: return else: # right node's left node become the right node of current node node.right = right_node.left if right_node.left is not None: right_node.left.parent = node right_node.parent = node.parent # check the parent case if node.parent is None: self.root = right_node elif node is node.parent.left: node.parent.left = right_node else: node.parent.right = right_node right_node.left = node node.parent = right_node def right_rotate(self, node): # set the node as the right child node of the current node's left node left_node = node.left if left_node is None: return else: # left node's right node become the left node of current node node.left = left_node.right if left_node.right is not None: left_node.right.parent = node left_node.parent = node.parent # check the parent case if node.parent is None: self.root = left_node elif node is node.parent.left: node.parent.left = left_node else: node.parent.right = left_node left_node.right = node node.parent = left_node def insert(self, node): # the inserted node's color is default is red root = self.root insert_node_parent = None # find the position of inserted node while root is not None: insert_node_parent = root root = root.right if insert_node_parent.val < node.val else root.left # set the n ode's parent node node.parent = insert_node_parent if insert_node_parent is None: # case 1 inserted tree is null self.root = node elif insert_node_parent.val > node.val: # case 2 not null and find left or right insert_node_parent.left = node else: insert_node_parent.right = node node.left = None node.right = None node.color = 1 # fix the tree to self.fix_insert(node) def fix_insert(self, node): # case 1 the parent is null, then set the inserted node as root and color = 0 if node.parent is None: node.color = 0 self.root = node return # case 2 the parent color is black, do nothing # case 3 the parent color is red while node.parent and node.parent.color == 1: if node.parent is node.parent.parent.left: uncle_node = node.parent.parent.right if uncle_node and uncle_node.color == 1: # case 3.1 the uncle node is red # then set parent and uncle color is black and grandparent is red # then node => node.parent node.parent.color = 0 node.parent.parent.right.color = 0 node.parent.parent.color = 1 node = node.parent.parent continue elif node is node.parent.right: # case 3.2 the uncle node is black or null, # and the node is right of parent # then set his parent node is current node # left rotate the node and continue the next node = node.parent self.left_rotate(node) # case 3.3 the uncle node is black and parent node is left # then parent node set black and grandparent set red node.parent.color = 0 node.parent.parent.color = 1 self.right_rotate(node.parent.parent) else: uncle_node = node.parent.parent.left if uncle_node and uncle_node.color == 1: # case 3.1 the uncle node is red # then set parent and uncle color is black and grandparent is red # then node => node.parent node.parent.color = 0 node.parent.parent.left.color = 0 node.parent.parent.color = 1 node = node.parent.parent continue elif node is node.parent.left: # case 3.2 the uncle node is black or null, # and the node is right of parent # then set his parent node is current node # left rotate the node and continue the next node = node.parent self.right_rotate(node) # case 3.3 the uncle node is black and parent node is left # then parent node set black and grandparent set red node.parent.color = 0 node.parent.parent.color = 1 self.left_rotate(node.parent.parent) self.root.color = 0 def transplant(self, node_u, node_v): """ replace u with v :param node_u: replaced node :param node_v: :return: None """ if node_u.parent is None: self.root = node_v elif node_u is node_u.parent.left: node_u.parent.left = node_v elif node_u is node_u.parent.right: node_u.parent.right = node_v # check is node_v is None if node_v: node_v.parent = node_u.parent def maximum(self, node): """ find the max node when node regard as a root node :param node: :return: max node """ temp_node = node while temp_node.right is not None: temp_node = temp_node.right return temp_node def minimum(self, node): """ find the minimum node when node regard as a root node :param node: :return: minimum node """ temp_node = node while temp_node.left: temp_node = temp_node.left return temp_node def delete(self, node): # find the node position node_color = node.color if node.left is None: temp_node = node.right self.transplant(node, node.right) elif node.right is None: temp_node = node.left self.transplant(node, node.left) else: # both child exits ,and find minimum child of right child node_min = self.minimum(node.right) node_color = node_min.color temp_node = node_min.right ## if node_min.parent is not node: self.transplant(node_min, node_min.right) node_min.right = node.right node_min.right.parent = node_min self.transplant(node, node_min) node_min.left = node.left node_min.left.parent = node_min node_min.color = node.color # when node is black, then need to fix it with 4 cases if node_color == 0: self.delete_fixup(temp_node) def delete_fixup(self, node): # 4 cases while node is not self.root and node.color == 0: # node is not root and color is black if node is node.parent.left: # node is left node node_brother = node.parent.right # case 1: node's red, can not get black node # set brother is black and parent is red if node_brother.color == 1: node_brother.color = 0 node.parent.color = 1 self.left_rotate(node.parent) node_brother = node.parent.right # case 2: brother node is black, and its children node is both black if (node_brother.left is None or node_brother.left.color == 0) and ( node_brother.right is None or node_brother.right.color == 0 ): node_brother.color = 1 node = node.parent else: # case 3: brother node is black, and its # left child node is red and right is black if node_brother.right is None or node_brother.right.color == 0: node_brother.color = 1 node_brother.left.color = 0 self.right_rotate(node_brother) node_brother = node.parent.right # case 4: brother node is black, and right # is red, and left is any color node_brother.color = node.parent.color node.parent.color = 0 node_brother.right.color = 0 self.left_rotate(node.parent) node = self.root else: node_brother = node.parent.left if node_brother.color == 1: node_brother.color = 0 node.parent.color = 1 self.left_rotate(node.parent) node_brother = node.parent.right if (node_brother.left is None or node_brother.left.color == 0) and ( node_brother.right is None or node_brother.right.color == 0 ): node_brother.color = 1 node = node.parent else: if node_brother.left is None or node_brother.left.color == 0: node_brother.color = 1 node_brother.right.color = 0 self.left_rotate(node_brother) node_brother = node.parent.left node_brother.color = node.parent.color node.parent.color = 0 node_brother.left.color = 0 self.right_rotate(node.parent) node = self.root node.color = 0 def inorder(self): res = [] if not self.root: return res stack = [] root = self.root while root or stack: while root: stack.append(root) root = root.left root = stack.pop() res.append({"val": root.val, "color": root.color}) root = root.right return res if __name__ == "__main__": rb = RBTree() children = [11, 2, 14, 1, 7, 15, 5, 8, 4] for child in children: node = RBNode(child, 1) print(child) rb.insert(node) print(rb.inorder()) ================================================ FILE: algorithms/data_structures/segment_tree.py ================================================ """ Segment_tree creates a segment tree with a given array and function, allowing queries to be done later in log(N) time function takes 2 values and returns a same type value """ class SegmentTree: def __init__(self, arr, function): self.segment = [0 for x in range(3 * len(arr) + 3)] self.arr = arr self.fn = function self.make_tree(0, 0, len(arr) - 1) def make_tree(self, i, left, r): if left == r: self.segment[i] = self.arr[left] elif left < r: self.make_tree(2 * i + 1, left, int((left + r) / 2)) self.make_tree(2 * i + 2, int((left + r) / 2) + 1, r) self.segment[i] = self.fn( self.segment[2 * i + 1], self.segment[2 * i + 2] ) def __query(self, i, low, high, left, r): if left > high or r < low or low > high or left > r: return None if left <= low and r >= high: return self.segment[i] val1 = self.__query(2 * i + 1, low, int((low + high) / 2), left, r) val2 = self.__query( 2 * i + 2, int((low + high + 2) / 2), high, left, r ) print(low, high, " returned ", val1, val2) if val1 is not None: if val2 is not None: return self.fn(val1, val2) return val1 return val2 def query(self, low, high): return self.__query(0, 0, len(self.arr) - 1, low, high) """ Example - mytree = SegmentTree([2,4,5,3,4],max) mytree.query(2,4) mytree.query(0,3) ... mytree = SegmentTree([4,5,2,3,4,43,3],sum) mytree.query(1,8) ... """ ================================================ FILE: algorithms/data_structures/separate_chaining_hash_table.py ================================================ """ Separate Chaining Hash Table Hash table implementation using separate chaining (linked lists) for collision resolution. Reference: https://en.wikipedia.org/wiki/Hash_table#Separate_chaining Complexity: Time: O(1) average for put/get/del, O(n) worst case Space: O(n) """ from __future__ import annotations class _Node: """Internal linked list node for chaining. Args: key: The key stored in this node. value: The value stored in this node. next_node: Reference to the next node in the chain. """ def __init__( self, key: object = None, value: object = None, next_node: _Node | None = None, ) -> None: self.key = key self.value = value self.next = next_node class SeparateChainingHashTable: """Hash table using separate chaining for collision resolution. Examples: >>> table = SeparateChainingHashTable() >>> table.put('hello', 'world') >>> len(table) 1 >>> table.get('hello') 'world' >>> del table['hello'] >>> table.get('hello') is None True """ _empty = None def __init__(self, size: int = 11) -> None: """Initialize the hash table. Args: size: Number of buckets. """ self.size = size self._len = 0 self._table: list[_Node | None] = [self._empty] * size def put(self, key: object, value: object) -> None: """Insert or update a key-value pair. Args: key: The key to insert. value: The value associated with the key. """ hash_ = self.hash(key) node_ = self._table[hash_] if node_ is self._empty: self._table[hash_] = _Node(key, value) else: while node_.next is not None: if node_.key == key: node_.value = value return node_ = node_.next node_.next = _Node(key, value) self._len += 1 def get(self, key: object) -> object | None: """Retrieve the value for a given key. Args: key: The key to look up. Returns: The associated value, or None if the key is not found. """ hash_ = self.hash(key) node_ = self._table[hash_] while node_ is not self._empty: if node_.key == key: return node_.value node_ = node_.next return None def del_(self, key: object) -> None: """Delete a key-value pair. Args: key: The key to delete. """ hash_ = self.hash(key) node_ = self._table[hash_] previous_node = None while node_ is not None: if node_.key == key: if previous_node is None: self._table[hash_] = node_.next else: previous_node.next = node_.next self._len -= 1 previous_node = node_ node_ = node_.next def hash(self, key: object) -> int: """Compute the bucket index for a key. Args: key: The key to hash. Returns: Bucket index. """ return hash(key) % self.size def __len__(self) -> int: return self._len def __getitem__(self, key: object) -> object | None: return self.get(key) def __delitem__(self, key: object) -> None: return self.del_(key) def __setitem__(self, key: object, value: object) -> None: self.put(key, value) ================================================ FILE: algorithms/data_structures/sqrt_decomposition.py ================================================ """ Square Root (Sqrt) Decomposition Divides an array into blocks of size √n to allow O(√n) range queries and point updates — a simple alternative to segment trees for range-aggregate problems. Supports: - **Range sum queries** in O(√n). - **Point updates** in O(1). Reference: https://cp-algorithms.com/data_structures/sqrt_decomposition.html Complexity: Build: O(n) Query: O(√n) Update: O(1) Space: O(n) """ from __future__ import annotations import math class SqrtDecomposition: """Square root decomposition for range sum queries. Attributes: data: The underlying array. block_size: Size of each block (⌈√n⌉). blocks: Precomputed block sums. Examples: >>> sd = SqrtDecomposition([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> sd.query(0, 8) 45 >>> sd.query(2, 5) 18 >>> sd.update(4, 10) >>> sd.query(0, 8) 50 """ def __init__(self, arr: list[int | float]) -> None: """Build the sqrt decomposition from *arr*. Args: arr: Input array of numbers. """ self.data = list(arr) n = len(self.data) self.block_size = max(1, math.isqrt(n)) num_blocks = (n + self.block_size - 1) // self.block_size self.blocks: list[int | float] = [0] * num_blocks for i, val in enumerate(self.data): self.blocks[i // self.block_size] += val def update(self, index: int, value: int | float) -> None: """Set ``data[index]`` to *value* and update the block sum. Args: index: Array index to update. value: New value. Raises: IndexError: If *index* is out of range. Examples: >>> sd = SqrtDecomposition([1, 2, 3]) >>> sd.update(1, 10) >>> sd.query(0, 2) 14 """ if index < 0 or index >= len(self.data): msg = f"index {index} out of range for length {len(self.data)}" raise IndexError(msg) block = index // self.block_size self.blocks[block] += value - self.data[index] self.data[index] = value def query(self, left: int, right: int) -> int | float: """Return the sum of elements from *left* to *right* inclusive. Args: left: Start index (inclusive). right: End index (inclusive). Returns: Sum of ``data[left..right]``. Raises: IndexError: If indices are out of range. Examples: >>> sd = SqrtDecomposition([1, 2, 3, 4, 5]) >>> sd.query(1, 3) 9 """ if left < 0 or right >= len(self.data) or left > right: msg = f"invalid range [{left}, {right}] for length {len(self.data)}" raise IndexError(msg) total: int | float = 0 block_left = left // self.block_size block_right = right // self.block_size if block_left == block_right: # Same block — iterate directly for i in range(left, right + 1): total += self.data[i] else: # Partial left block for i in range(left, (block_left + 1) * self.block_size): total += self.data[i] # Full middle blocks for b in range(block_left + 1, block_right): total += self.blocks[b] # Partial right block for i in range(block_right * self.block_size, right + 1): total += self.data[i] return total ================================================ FILE: algorithms/data_structures/stack.py ================================================ """ Stack Abstract Data Type Implementations of the stack ADT using both a fixed-size array and a linked list. Both support push, pop, peek, is_empty, len, iter, and str. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(1) for push/pop/peek (amortized for ArrayStack) Space: O(n) """ from __future__ import annotations from abc import ABCMeta, abstractmethod from collections.abc import Iterator class AbstractStack(metaclass=ABCMeta): """Abstract base class for stack implementations.""" def __init__(self) -> None: self._top = -1 def __len__(self) -> int: return self._top + 1 def __str__(self) -> str: result = " ".join(map(str, self)) return "Top-> " + result def is_empty(self) -> bool: """Check if the stack is empty. Returns: True if the stack has no elements. """ return self._top == -1 @abstractmethod def __iter__(self) -> Iterator[object]: pass @abstractmethod def push(self, value: object) -> None: pass @abstractmethod def pop(self) -> object: pass @abstractmethod def peek(self) -> object: pass class ArrayStack(AbstractStack): """Stack implemented with a dynamic array. Examples: >>> s = ArrayStack() >>> s.push(1) >>> s.pop() 1 """ def __init__(self, size: int = 10) -> None: """Initialize with a fixed-size array. Args: size: Initial capacity of the underlying array. """ super().__init__() self._array: list[object | None] = [None] * size def __iter__(self) -> Iterator[object]: probe = self._top while True: if probe == -1: return yield self._array[probe] probe -= 1 def push(self, value: object) -> None: """Push a value onto the stack. Args: value: The value to push. """ self._top += 1 if self._top == len(self._array): self._expand() self._array[self._top] = value def pop(self) -> object: """Remove and return the top element. Returns: The top element. Raises: IndexError: If the stack is empty. """ if self.is_empty(): raise IndexError("Stack is empty") value = self._array[self._top] self._top -= 1 return value def peek(self) -> object: """Return the top element without removing it. Returns: The top element. Raises: IndexError: If the stack is empty. """ if self.is_empty(): raise IndexError("Stack is empty") return self._array[self._top] def _expand(self) -> None: """Double the size of the underlying array.""" self._array += [None] * len(self._array) class StackNode: """A single node in a linked-list-based stack.""" def __init__(self, value: object) -> None: self.value = value self.next: StackNode | None = None class LinkedListStack(AbstractStack): """Stack implemented with a singly linked list. Examples: >>> s = LinkedListStack() >>> s.push(1) >>> s.pop() 1 """ def __init__(self) -> None: super().__init__() self.head: StackNode | None = None def __iter__(self) -> Iterator[object]: probe = self.head while True: if probe is None: return yield probe.value probe = probe.next def push(self, value: object) -> None: """Push a value onto the stack. Args: value: The value to push. """ node = StackNode(value) node.next = self.head self.head = node self._top += 1 def pop(self) -> object: """Remove and return the top element. Returns: The top element. Raises: IndexError: If the stack is empty. """ if self.is_empty(): raise IndexError("Stack is empty") value = self.head.value self.head = self.head.next self._top -= 1 return value def peek(self) -> object: """Return the top element without removing it. Returns: The top element. Raises: IndexError: If the stack is empty. """ if self.is_empty(): raise IndexError("Stack is empty") return self.head.value ================================================ FILE: algorithms/data_structures/trie.py ================================================ """ Implement a trie with insert, search, and startsWith methods. Note: You may assume that all inputs are consist of lowercase letters a-z. """ import collections class TrieNode: def __init__(self): self.children = collections.defaultdict(TrieNode) self.is_word = False class Trie: def __init__(self): self.root = TrieNode() def insert(self, word): current = self.root for letter in word: current = current.children[letter] current.is_word = True def search(self, word): current = self.root for letter in word: current = current.children.get(letter) if current is None: return False return current.is_word def starts_with(self, prefix): current = self.root for letter in prefix: current = current.children.get(letter) if current is None: return False return True ================================================ FILE: algorithms/data_structures/union_find.py ================================================ """ Union-Find (Disjoint Set) Data Structure A Union-Find data structure supporting add, find (root), and unite operations. Uses union by size and path compression for near-constant amortized time. Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure Complexity: Time: O(alpha(n)) amortized per operation (inverse Ackermann) Space: O(n) """ from __future__ import annotations class Union: """A Union-Find (Disjoint Set) data structure. Supports adding elements, finding set representatives, and merging sets. Uses union by size and path compression for near-constant amortized time. Examples: >>> uf = Union() >>> uf.add(1); uf.add(2); uf.add(3) >>> uf.unite(1, 2) >>> uf.root(1) == uf.root(2) True >>> uf.root(1) == uf.root(3) False """ def __init__(self) -> None: self.parents: dict[object, object] = {} self.size: dict[object, int] = {} self.count: int = 0 def add(self, element: object) -> None: """Add a new singleton set containing the given element. Args: element: The element to add. """ self.parents[element] = element self.size[element] = 1 self.count += 1 def root(self, element: object) -> object: """Find the root representative of the set containing element. Args: element: The element whose root to find. Returns: The root representative of the element's set. """ while element != self.parents[element]: self.parents[element] = self.parents[self.parents[element]] element = self.parents[element] return element def unite(self, element1: object, element2: object) -> None: """Merge the sets containing the two elements. Args: element1: An element in the first set. element2: An element in the second set. """ root1, root2 = self.root(element1), self.root(element2) if root1 == root2: return if self.size[root1] > self.size[root2]: root1, root2 = root2, root1 self.parents[root1] = root2 self.size[root2] += self.size[root1] self.count -= 1 ================================================ FILE: algorithms/data_structures/veb_tree.py ================================================ """ Van Emde Boas Tree (vEB Tree) / van Emde Boas priority queue Reference: https://en.wikipedia.org/wiki/Van_Emde_Boas_tree A van Emde Boas tree is a recursive data structure for storing integers from a fixed universe [0, u - 1], where u is a power of 2. Time complexity: insert / delete / successor / member : O(log log u) min / max : O(1) Space complexity: O(u) """ import math class VEBTree: """ Van Emde Boas tree supporting fast predecessor/successor queries. Attributes: u (int): Universe size (power of 2) min (int | None): Minimum element in the tree max (int | None): Maximum element in the tree summary (VEBTree | None): Summary tree cluster (list[VEBTree] | None): Array of clusters """ def __init__(self, universe_size): """ Initialize a Van Emde Boas tree. Args: universe_size (int): Size of the universe; must be a power of 2 and > 0. Raises: TypeError: If universe_size is not an integer. ValueError: If universe_size <= 0 or not a power of 2. """ if not isinstance(universe_size, int): raise TypeError("universe_size must be an integer.") if not universe_size > 0: raise ValueError("universe_size must be greater than 0.") if not (universe_size & (universe_size - 1)) == 0: raise ValueError("universe_size must be a power of 2.") self.u = universe_size self.min = None self.max = None if universe_size <= 2: self.summary = None self.cluster = None else: self.lower_sqrt = 2 ** (math.floor(math.log2(universe_size) / 2)) self.upper_sqrt = 2 ** (math.ceil(math.log2(universe_size) / 2)) self.summary = VEBTree(self.upper_sqrt) self.cluster = [VEBTree(self.lower_sqrt) for _ in range(self.upper_sqrt)] def _validate_key(self, x): """ Check if x is within the universe range. Args: x (int): Element to validate. Raises: ValueError: If x is not in the range [0, u-1]. """ if not (0 <= x < self.u): raise ValueError(f"Key {x} out of universe range [0, {self.u - 1}]") def high(self, x): """ Return the high part (cluster index) of element x. Args: x (int): Element to split. Returns: int: Cluster index corresponding to x. """ return x // self.lower_sqrt def low(self, x): """ Return the low part (position within cluster) of element x. Args: x (int): Element to split. Returns: int: Position within cluster corresponding to x. """ return x % self.lower_sqrt def index(self, high, low): """ Combine high and low parts to get original element. Args: high (int): Cluster index. low (int): Position within cluster. Returns: int: Original element corresponding to high and low. """ return high * self.lower_sqrt + low def empty_insert(self, x): """ Insert x into an empty vEB tree (sets min and max). Args: x (int): Element to insert. """ self.min = self.max = x def insert(self, x): """ Insert an element into the Van Emde Boas tree. Args: x (int): Element to insert; must be in the universe [0, u-1]. Raises: ValueError: If x is outside the universe. """ self._validate_key(x) if self.min is None: self.empty_insert(x) return if x < self.min: x, self.min = self.min, x if self.u > 2: high = self.high(x) low = self.low(x) if self.cluster[high].min is None: self.summary.insert(high) self.cluster[high].empty_insert(low) else: self.cluster[high].insert(low) if x > self.max: self.max = x def member(self, x): """ Check whether element x exists in the tree. Args: x (int): Element to check. Returns: bool: True if x exists, False otherwise. Raises: ValueError: If x is outside the universe. """ self._validate_key(x) if x == self.min or x == self.max: return True elif self.u == 2: return False else: return self.cluster[self.high(x)].member(self.low(x)) def successor(self, x): """ Return the smallest element greater than x in the tree. Args: x (int): Element to find successor for. Returns: int | None: Successor of x if exists, otherwise None. Raises: ValueError: If x is outside the universe. """ self._validate_key(x) if self.u == 2: if x == 0 and self.max == 1: return 1 return None if self.min is not None and x < self.min: return self.min high = self.high(x) low = self.low(x) max_low = self.cluster[high].max if max_low is not None and low < max_low: offset = self.cluster[high].successor(low) return self.index(high, offset) else: succ_cluster = self.summary.successor(high) if succ_cluster is None: return None offset = self.cluster[succ_cluster].min return self.index(succ_cluster, offset) def delete(self, x): """ Remove element x from the Van Emde Boas tree. Args: x (int): Element to delete. Raises: ValueError: If x is outside the universe. """ self._validate_key(x) if self.min == self.max: self.min = self.max = None return if self.u == 2: if x == 0: self.min = 1 else: self.min = 0 self.max = self.min return if x == self.min: first_cluster = self.summary.min x = self.index(first_cluster, self.cluster[first_cluster].min) self.min = x high = self.high(x) low = self.low(x) self.cluster[high].delete(low) if self.cluster[high].min is None: self.summary.delete(high) if x == self.max: summary_max = self.summary.max if summary_max is None: self.max = self.min else: self.max = self.index(summary_max, self.cluster[summary_max].max) elif x == self.max: self.max = self.index(high, self.cluster[high].max) def minimum(self): """ Get the minimum element in the tree. Returns: int | None: Minimum element, or None if tree is empty. """ return self.min def maximum(self): """ Get the maximum element in the tree. Returns: int | None: Maximum element, or None if tree is empty. """ return self.max ================================================ FILE: algorithms/dynamic_programming/__init__.py ================================================ """ Dynamic Programming Algorithms A collection of dynamic programming algorithm implementations. """ from . import regex_matching from .bitmask import tsp from .buy_sell_stock import max_profit_naive, max_profit_optimized from .climbing_stairs import climb_stairs, climb_stairs_optimized from .coin_change import count from .combination_sum import combination_sum_bottom_up, combination_sum_topdown from .count_paths_dp import count_paths_dp, count_paths_memo, count_paths_recursive from .edit_distance import edit_distance from .egg_drop import egg_drop from .fib import fib_iter, fib_list, fib_recursive from .hosoya_triangle import hosoya, hosoya_testing from .house_robber import house_robber from .int_divide import int_divide from .job_scheduling import Job, schedule from .k_factor import find_k_factor from .knapsack import Item, get_maximum_value from .longest_common_subsequence import longest_common_subsequence from .longest_increasing import ( longest_increasing_subsequence, longest_increasing_subsequence_optimized, longest_increasing_subsequence_optimized2, ) from .matrix_chain_order import matrix_chain_order from .max_product_subarray import max_product, subarray_with_max_product from .max_subarray import max_subarray from .min_cost_path import min_cost from .num_decodings import num_decodings, num_decodings2 from .planting_trees import planting_trees from .regex_matching import is_match from .rod_cut import cut_rod from .word_break import word_break __all__ = [ # buy_sell_stock "max_profit_naive", "max_profit_optimized", # climbing_stairs "climb_stairs", "climb_stairs_optimized", # coin_change "count", # combination_sum "combination_sum_topdown", "combination_sum_bottom_up", # edit_distance "edit_distance", # egg_drop "egg_drop", # fib "fib_recursive", "fib_list", "fib_iter", # hosoya_triangle "hosoya", "hosoya_testing", # house_robber "house_robber", # int_divide "int_divide", # job_scheduling "Job", "schedule", # k_factor "find_k_factor", # knapsack "Item", "get_maximum_value", # longest_common_subsequence "longest_common_subsequence", # longest_increasing "longest_increasing_subsequence", "longest_increasing_subsequence_optimized", "longest_increasing_subsequence_optimized2", # matrix_chain_order "matrix_chain_order", # max_product_subarray "max_product", "subarray_with_max_product", # max_subarray "max_subarray", # min_cost_path "min_cost", # num_decodings "num_decodings", "num_decodings2", # planting_trees "planting_trees", # regex_matching "regex_matching", "is_match", # rod_cut "cut_rod", # word_break "word_break", # bitmask "tsp", # count_paths_dp "count_paths_dp", "count_paths_memo", "count_paths_recursive", ] ================================================ FILE: algorithms/dynamic_programming/bitmask.py ================================================ """Bitmask dynamic programming — Travelling Salesman Problem (TSP). Uses DP with bitmask to find the minimum-cost Hamiltonian cycle in a weighted graph. The state (visited_mask, current_city) encodes which cities have been visited and where we are now. Time: O(2^n * n^2). Space: O(2^n * n). Inspired by PR #855 (AmandaStromdahl). """ from __future__ import annotations import math def tsp(dist: list[list[float]]) -> float: """Return the minimum cost of a Hamiltonian cycle. *dist* is an n x n distance matrix. >>> tsp([[0, 10, 15, 20], ... [10, 0, 35, 25], ... [15, 35, 0, 30], ... [20, 25, 30, 0]]) 80 """ n = len(dist) full_mask = (1 << n) - 1 dp: dict[tuple[int, int], float] = {} def solve(mask: int, pos: int) -> float: if mask == full_mask: return dist[pos][0] key = (mask, pos) if key in dp: return dp[key] ans = math.inf for city in range(n): if not (mask & (1 << city)): ans = min(ans, dist[pos][city] + solve(mask | (1 << city), city)) dp[key] = ans return ans return solve(1, 0) ================================================ FILE: algorithms/dynamic_programming/buy_sell_stock.py ================================================ """ Best Time to Buy and Sell Stock Given an array of stock prices, find the maximum profit from a single buy-sell transaction (buy before sell). Reference: https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ Complexity: max_profit_naive: Time: O(n^2) Space: O(1) max_profit_optimized: Time: O(n) Space: O(1) """ from __future__ import annotations def max_profit_naive(prices: list[int]) -> int: """Find maximum profit by checking all pairs of buy/sell days. Args: prices: List of stock prices per day. Returns: Maximum achievable profit (0 if no profitable trade exists). Examples: >>> max_profit_naive([7, 1, 5, 3, 6, 4]) 5 >>> max_profit_naive([7, 6, 4, 3, 1]) 0 """ max_so_far = 0 for i in range(0, len(prices) - 1): for j in range(i + 1, len(prices)): max_so_far = max(max_so_far, prices[j] - prices[i]) return max_so_far def max_profit_optimized(prices: list[int]) -> int: """Find maximum profit using Kadane-style single pass. Args: prices: List of stock prices per day. Returns: Maximum achievable profit (0 if no profitable trade exists). Examples: >>> max_profit_optimized([7, 1, 5, 3, 6, 4]) 5 >>> max_profit_optimized([7, 6, 4, 3, 1]) 0 """ cur_max, max_so_far = 0, 0 for i in range(1, len(prices)): cur_max = max(0, cur_max + prices[i] - prices[i - 1]) max_so_far = max(max_so_far, cur_max) return max_so_far ================================================ FILE: algorithms/dynamic_programming/climbing_stairs.py ================================================ """ Climbing Stairs Count the number of distinct ways to climb a staircase of n steps, where each move is either 1 or 2 steps. Reference: https://leetcode.com/problems/climbing-stairs/ Complexity: climb_stairs: Time: O(n) Space: O(n) climb_stairs_optimized: Time: O(n) Space: O(1) """ from __future__ import annotations def climb_stairs(steps: int) -> int: """Count distinct ways to climb n steps using a list-based DP approach. Args: steps: Number of steps in the staircase (positive integer). Returns: Number of distinct ways to reach the top. Examples: >>> climb_stairs(2) 2 >>> climb_stairs(10) 89 """ arr = [1, 1] for _ in range(1, steps): arr.append(arr[-1] + arr[-2]) return arr[-1] def climb_stairs_optimized(steps: int) -> int: """Count distinct ways to climb n steps using constant space. Args: steps: Number of steps in the staircase (positive integer). Returns: Number of distinct ways to reach the top. Examples: >>> climb_stairs_optimized(2) 2 >>> climb_stairs_optimized(10) 89 """ a_steps = b_steps = 1 for _ in range(steps): a_steps, b_steps = b_steps, a_steps + b_steps return a_steps ================================================ FILE: algorithms/dynamic_programming/coin_change.py ================================================ """ Coin Change (Number of Ways) Given a value and a set of coin denominations, count how many distinct combinations of coins sum to the given value. Reference: https://leetcode.com/problems/coin-change-ii/ Complexity: Time: O(n * m) where n is the value and m is the number of coins Space: O(n) """ from __future__ import annotations def count(coins: list[int], value: int) -> int: """Find the number of coin combinations that add up to value. Args: coins: List of coin denominations. value: Target sum. Returns: Number of distinct combinations that sum to value. Examples: >>> count([1, 2, 3], 4) 4 >>> count([2, 5, 3, 6], 10) 5 """ dp_array = [1] + [0] * value for coin in coins: for i in range(coin, value + 1): dp_array[i] += dp_array[i - coin] return dp_array[value] ================================================ FILE: algorithms/dynamic_programming/combination_sum.py ================================================ """ Combination Sum IV Given an array of distinct positive integers and a target, find the number of possible combinations (order matters) that add up to the target. Reference: https://leetcode.com/problems/combination-sum-iv/ Complexity: combination_sum_topdown: Time: O(target * n) Space: O(target) combination_sum_bottom_up: Time: O(target * n) Space: O(target) """ from __future__ import annotations def _helper_topdown(nums: list[int], target: int, dp: list[int]) -> int: """Recursive helper that fills the dp table top-down. Args: nums: Positive integer array without duplicates. target: Remaining target value. dp: Memoisation table. Returns: Number of combinations that sum to target. """ if dp[target] != -1: return dp[target] result = 0 for num in nums: if target >= num: result += _helper_topdown(nums, target - num, dp) dp[target] = result return result def combination_sum_topdown(nums: list[int], target: int) -> int: """Find number of combinations that add up to target (top-down DP). Args: nums: Positive integer array without duplicates. target: Target sum. Returns: Number of ordered combinations that sum to target. Examples: >>> combination_sum_topdown([1, 2, 3], 4) 7 """ dp = [-1] * (target + 1) dp[0] = 1 return _helper_topdown(nums, target, dp) def combination_sum_bottom_up(nums: list[int], target: int) -> int: """Find number of combinations that add up to target (bottom-up DP). Args: nums: Positive integer array without duplicates. target: Target sum. Returns: Number of ordered combinations that sum to target. Examples: >>> combination_sum_bottom_up([1, 2, 3], 4) 7 """ combs = [0] * (target + 1) combs[0] = 1 for i in range(0, len(combs)): for num in nums: if i - num >= 0: combs[i] += combs[i - num] return combs[target] ================================================ FILE: algorithms/dynamic_programming/count_paths_dp.py ================================================ """Count paths in a grid — recursive, memoized, and bottom-up DP. Count the number of unique paths from the top-left to the bottom-right of an m x n grid, moving only right or down. Inspired by PR #857 (c-cret). """ from __future__ import annotations from functools import cache def count_paths_recursive(m: int, n: int) -> int: """Naive recursive solution — O(2^(m+n)).""" if m == 1 or n == 1: return 1 return count_paths_recursive(m - 1, n) + count_paths_recursive(m, n - 1) def count_paths_memo(m: int, n: int) -> int: """Top-down DP with memoization — O(m*n).""" @cache def helper(i: int, j: int) -> int: if i == 1 or j == 1: return 1 return helper(i - 1, j) + helper(i, j - 1) return helper(m, n) def count_paths_dp(m: int, n: int) -> int: """Bottom-up DP — O(m*n) time, O(n) space. >>> count_paths_dp(3, 7) 28 >>> count_paths_dp(3, 3) 6 """ row = [1] * n for _ in range(1, m): for j in range(1, n): row[j] += row[j - 1] return row[n - 1] ================================================ FILE: algorithms/dynamic_programming/edit_distance.py ================================================ """ Edit Distance (Levenshtein Distance) Find the minimum number of insertions, deletions, and substitutions required to transform one word into another. Reference: https://en.wikipedia.org/wiki/Levenshtein_distance Complexity: Time: O(m * n) where m, n are the lengths of the two words Space: O(m * n) """ from __future__ import annotations def edit_distance(word_a: str, word_b: str) -> int: """Compute the edit distance between two words. Args: word_a: First word. word_b: Second word. Returns: Minimum number of single-character edits to transform word_a into word_b. Examples: >>> edit_distance('food', 'money') 4 >>> edit_distance('horse', 'ros') 3 """ length_a, length_b = len(word_a) + 1, len(word_b) + 1 edit = [[0 for _ in range(length_b)] for _ in range(length_a)] for i in range(1, length_a): edit[i][0] = i for j in range(1, length_b): edit[0][j] = j for i in range(1, length_a): for j in range(1, length_b): cost = 0 if word_a[i - 1] == word_b[j - 1] else 1 edit[i][j] = min( edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + cost, ) return edit[-1][-1] ================================================ FILE: algorithms/dynamic_programming/egg_drop.py ================================================ """ Egg Drop Problem Given K eggs and a building with N floors, determine the minimum number of moves needed to find the critical floor F in the worst case. Reference: https://en.wikipedia.org/wiki/Dynamic_programming#Egg_dropping_puzzle Complexity: Time: O(n * k^2) Space: O(n * k) """ from __future__ import annotations _INT_MAX = 32767 def egg_drop(n: int, k: int) -> int: """Find the minimum number of trials to identify the critical floor. Args: n: Number of eggs. k: Number of floors. Returns: Minimum number of trials in the worst case. Examples: >>> egg_drop(1, 2) 2 >>> egg_drop(2, 6) 3 """ egg_floor = [[0 for _ in range(k + 1)] for _ in range(n + 1)] for i in range(1, n + 1): egg_floor[i][1] = 1 egg_floor[i][0] = 0 for j in range(1, k + 1): egg_floor[1][j] = j for i in range(2, n + 1): for j in range(2, k + 1): egg_floor[i][j] = _INT_MAX for x in range(1, j + 1): res = 1 + max(egg_floor[i - 1][x - 1], egg_floor[i][j - x]) if res < egg_floor[i][j]: egg_floor[i][j] = res return egg_floor[n][k] ================================================ FILE: algorithms/dynamic_programming/fib.py ================================================ """ Fibonacci Number Compute the n-th Fibonacci number using three different approaches: recursive, list-based DP, and iterative. Reference: https://en.wikipedia.org/wiki/Fibonacci_number Complexity: fib_recursive: Time: O(2^n) Space: O(n) (call stack) fib_list: Time: O(n) Space: O(n) fib_iter: Time: O(n) Space: O(1) """ from __future__ import annotations def fib_recursive(n: int) -> int: """Compute the n-th Fibonacci number recursively. Args: n: Non-negative integer index into the Fibonacci sequence. Returns: The n-th Fibonacci number. Examples: >>> fib_recursive(10) 55 """ assert n >= 0, "n must be a positive integer" if n <= 1: return n return fib_recursive(n - 1) + fib_recursive(n - 2) def fib_list(n: int) -> int: """Compute the n-th Fibonacci number using a list-based DP table. Args: n: Non-negative integer index into the Fibonacci sequence. Returns: The n-th Fibonacci number. Examples: >>> fib_list(10) 55 """ assert n >= 0, "n must be a positive integer" list_results = [0, 1] for i in range(2, n + 1): list_results.append(list_results[i - 1] + list_results[i - 2]) return list_results[n] def fib_iter(n: int) -> int: """Compute the n-th Fibonacci number iteratively with constant space. Args: n: Non-negative integer index into the Fibonacci sequence. Returns: The n-th Fibonacci number. Examples: >>> fib_iter(10) 55 """ assert n >= 0, "n must be positive integer" fib_1 = 0 fib_2 = 1 res = 0 if n <= 1: return n for _ in range(n - 1): res = fib_1 + fib_2 fib_1 = fib_2 fib_2 = res return res ================================================ FILE: algorithms/dynamic_programming/hosoya_triangle.py ================================================ """ Hosoya Triangle The Hosoya triangle (originally Fibonacci triangle) is a triangular arrangement of numbers where each entry is the sum of two entries above it. Reference: https://en.wikipedia.org/wiki/Hosoya%27s_triangle Complexity: Time: O(n^3) (naive recursive per entry) Space: O(n) (call stack depth) """ from __future__ import annotations def hosoya(height: int, width: int) -> int: """Compute a single entry in the Hosoya triangle. Args: height: Row index (0-based). width: Column index (0-based). Returns: The value at position (height, width) in the Hosoya triangle. Examples: >>> hosoya(4, 2) 4 """ if (width == 0) and (height in (0, 1)): return 1 if (width == 1) and (height in (1, 2)): return 1 if height > width: return hosoya(height - 1, width) + hosoya(height - 2, width) if width == height: return hosoya(height - 1, width - 1) + hosoya(height - 2, width - 2) return 0 def hosoya_testing(height: int) -> list[int]: """Generate a flat list of all Hosoya triangle values up to given height. Args: height: Number of rows to generate. Returns: Flat list of triangle values row by row. Examples: >>> hosoya_testing(1) [1] """ res = [] for i in range(height): for j in range(i + 1): res.append(hosoya(i, j)) return res ================================================ FILE: algorithms/dynamic_programming/house_robber.py ================================================ """ House Robber Determine the maximum amount of money that can be robbed from a row of houses without robbing two adjacent houses. Reference: https://leetcode.com/problems/house-robber/ Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def house_robber(houses: list[int]) -> int: """Compute the maximum robbery amount without hitting adjacent houses. Args: houses: List of non-negative integers representing money in each house. Returns: Maximum amount that can be robbed. Examples: >>> house_robber([1, 2, 16, 3, 15, 3, 12, 1]) 44 """ last, now = 0, 0 for house in houses: last, now = now, max(last + house, now) return now ================================================ FILE: algorithms/dynamic_programming/int_divide.py ================================================ """ Integer Partition Count the number of ways a positive integer can be represented as a sum of positive integers (order does not matter). Reference: https://en.wikipedia.org/wiki/Partition_(number_theory) Complexity: Time: O(n^2) Space: O(n^2) """ from __future__ import annotations def int_divide(decompose: int) -> int: """Count the number of partitions of a positive integer. Args: decompose: The positive integer to partition. Returns: Number of distinct partitions. Examples: >>> int_divide(4) 5 >>> int_divide(7) 15 """ arr = [[0 for i in range(decompose + 1)] for j in range(decompose + 1)] arr[1][1] = 1 for i in range(1, decompose + 1): for j in range(1, decompose + 1): if i < j: arr[i][j] = arr[i][i] elif i == j: arr[i][j] = 1 + arr[i][j - 1] else: arr[i][j] = arr[i][j - 1] + arr[i - j][j] return arr[decompose][decompose] ================================================ FILE: algorithms/dynamic_programming/job_scheduling.py ================================================ """ Weighted Job Scheduling Given a set of jobs with start times, finish times, and profits, find the maximum profit subset such that no two jobs overlap. Reference: https://en.wikipedia.org/wiki/Job-shop_scheduling Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations class Job: """Represents a job with start time, finish time, and profit.""" def __init__(self, start: int, finish: int, profit: int) -> None: self.start = start self.finish = finish self.profit = profit def _binary_search(job: list[Job], start_index: int) -> int: """Find the latest non-conflicting job before start_index. Args: job: List of jobs sorted by finish time. start_index: Index of the current job. Returns: Index of the latest compatible job, or -1 if none exists. """ left = 0 right = start_index - 1 while left <= right: mid = (left + right) // 2 if job[mid].finish <= job[start_index].start: if job[mid + 1].finish <= job[start_index].start: left = mid + 1 else: return mid else: right = mid - 1 return -1 def schedule(job: list[Job]) -> int: """Find the maximum profit from non-overlapping jobs. Args: job: List of Job objects. Returns: Maximum achievable profit. Examples: >>> schedule([Job(1, 3, 2), Job(2, 3, 4)]) 4 """ job = sorted(job, key=lambda j: j.finish) length = len(job) table = [0 for _ in range(length)] table[0] = job[0].profit for i in range(1, length): incl_prof = job[i].profit pos = _binary_search(job, i) if pos != -1: incl_prof += table[pos] table[i] = max(incl_prof, table[i - 1]) return table[length - 1] ================================================ FILE: algorithms/dynamic_programming/k_factor.py ================================================ """ K-Factor of a String The K factor of a string is the number of times 'abba' appears as a substring. Given a length and a k_factor, count the number of strings of that length whose K factor equals k_factor. Reference: https://en.wikipedia.org/wiki/Dynamic_programming Complexity: Time: O(length^2) Space: O(length^2) """ from __future__ import annotations def find_k_factor(length: int, k_factor: int) -> int: """Count strings of given length with exactly k_factor 'abba' substrings. Args: length: Length of the strings to consider. k_factor: Required number of 'abba' substrings. Returns: Number of strings satisfying the K-factor constraint. Examples: >>> find_k_factor(4, 1) 1 >>> find_k_factor(7, 1) 70302 """ mat = [ [[0 for i in range(4)] for j in range((length - 1) // 3 + 2)] for k in range(length + 1) ] if 3 * k_factor + 1 > length: return 0 mat[1][0][0] = 1 mat[1][0][1] = 0 mat[1][0][2] = 0 mat[1][0][3] = 25 for i in range(2, length + 1): for j in range((length - 1) // 3 + 2): if j == 0: mat[i][j][0] = mat[i - 1][j][0] + mat[i - 1][j][1] + mat[i - 1][j][3] mat[i][j][1] = mat[i - 1][j][0] mat[i][j][2] = mat[i - 1][j][1] mat[i][j][3] = ( mat[i - 1][j][0] * 24 + mat[i - 1][j][1] * 24 + mat[i - 1][j][2] * 25 + mat[i - 1][j][3] * 25 ) elif 3 * j + 1 < i: mat[i][j][0] = ( mat[i - 1][j][0] + mat[i - 1][j][1] + mat[i - 1][j][3] + mat[i - 1][j - 1][2] ) mat[i][j][1] = mat[i - 1][j][0] mat[i][j][2] = mat[i - 1][j][1] mat[i][j][3] = ( mat[i - 1][j][0] * 24 + mat[i - 1][j][1] * 24 + mat[i - 1][j][2] * 25 + mat[i - 1][j][3] * 25 ) elif 3 * j + 1 == i: mat[i][j][0] = 1 mat[i][j][1] = 0 mat[i][j][2] = 0 mat[i][j][3] = 0 else: mat[i][j][0] = 0 mat[i][j][1] = 0 mat[i][j][2] = 0 mat[i][j][3] = 0 return sum(mat[length][k_factor]) ================================================ FILE: algorithms/dynamic_programming/knapsack.py ================================================ """ 0/1 Knapsack Problem Given items with values and weights, and a knapsack capacity, find the maximum total value that fits in the knapsack. Reference: https://en.wikipedia.org/wiki/Knapsack_problem Complexity: Time: O(n * m) where n is the number of items and m is the capacity Space: O(m) """ from __future__ import annotations class Item: """Represents an item with a value and weight.""" def __init__(self, value: int, weight: int) -> None: self.value = value self.weight = weight def get_maximum_value(items: list[Item], capacity: int) -> int: """Compute the maximum value achievable within the knapsack capacity. Args: items: List of Item objects with value and weight attributes. capacity: Maximum weight the knapsack can hold. Returns: Maximum total value that fits in the knapsack. Examples: >>> get_maximum_value([Item(60, 5), Item(50, 3), Item(70, 4), Item(30, 2)], 5) 80 """ dp = [0] * (capacity + 1) for item in items: for cur_weight in reversed(range(item.weight, capacity + 1)): dp[cur_weight] = max( dp[cur_weight], item.value + dp[cur_weight - item.weight] ) return dp[capacity] ================================================ FILE: algorithms/dynamic_programming/longest_common_subsequence.py ================================================ """ Longest Common Subsequence Find the length of the longest subsequence common to two strings. Reference: https://en.wikipedia.org/wiki/Longest_common_subsequence Complexity: Time: O(m * n) Space: O(m * n) """ from __future__ import annotations def longest_common_subsequence(s_1: str, s_2: str) -> int: """Compute the length of the longest common subsequence of two strings. Args: s_1: First string. s_2: Second string. Returns: Length of the longest common subsequence. Examples: >>> longest_common_subsequence('abcdgh', 'aedfhr') 3 """ m = len(s_1) n = len(s_2) mat = [[0] * (n + 1) for i in range(m + 1)] for i in range(m + 1): for j in range(n + 1): if i == 0 or j == 0: mat[i][j] = 0 elif s_1[i - 1] == s_2[j - 1]: mat[i][j] = mat[i - 1][j - 1] + 1 else: mat[i][j] = max(mat[i - 1][j], mat[i][j - 1]) return mat[m][n] ================================================ FILE: algorithms/dynamic_programming/longest_increasing.py ================================================ """ Longest Increasing Subsequence Find the length of the longest strictly increasing subsequence in an array. Reference: https://en.wikipedia.org/wiki/Longest_increasing_subsequence Complexity: longest_increasing_subsequence: Time: O(n^2) Space: O(n) longest_increasing_subsequence_optimized: Time: O(n * log(x)) where x is the max element Space: O(x) longest_increasing_subsequence_optimized2: Time: O(n * log(n)) Space: O(n) """ from __future__ import annotations def longest_increasing_subsequence(sequence: list[int]) -> int: """Find length of the longest increasing subsequence using O(n^2) DP. Args: sequence: List of integers. Returns: Length of the longest strictly increasing subsequence. Examples: >>> longest_increasing_subsequence([10, 9, 2, 5, 3, 7, 101, 18]) 4 """ length = len(sequence) counts = [1 for _ in range(length)] for i in range(1, length): for j in range(0, i): if sequence[i] > sequence[j]: counts[i] = max(counts[i], counts[j] + 1) return max(counts) def longest_increasing_subsequence_optimized(sequence: list[int]) -> int: """Find length of LIS using a segment tree for O(n*log(x)) time. Args: sequence: List of integers. Returns: Length of the longest strictly increasing subsequence. Examples: >>> longest_increasing_subsequence_optimized([10, 9, 2, 5, 3, 7, 101, 18]) 4 """ max_val = max(sequence) tree = [0] * (max_val << 2) def _update(pos: int, left: int, right: int, target: int, vertex: int) -> None: if left == right: tree[pos] = vertex return mid = (left + right) >> 1 if target <= mid: _update(pos << 1, left, mid, target, vertex) else: _update((pos << 1) | 1, mid + 1, right, target, vertex) tree[pos] = max(tree[pos << 1], tree[(pos << 1) | 1]) def _get_max(pos: int, left: int, right: int, start: int, end: int) -> int: if left > end or right < start: return 0 if left >= start and right <= end: return tree[pos] mid = (left + right) >> 1 return max( _get_max(pos << 1, left, mid, start, end), _get_max((pos << 1) | 1, mid + 1, right, start, end), ) ans = 0 for element in sequence: cur = _get_max(1, 0, max_val, 0, element - 1) + 1 ans = max(ans, cur) _update(1, 0, max_val, element, cur) return ans def longest_increasing_subsequence_optimized2(sequence: list[int]) -> int: """Find length of LIS using coordinate-compressed segment tree for O(n*log(n)). Args: sequence: List of integers. Returns: Length of the longest strictly increasing subsequence. Examples: >>> longest_increasing_subsequence_optimized2([10, 9, 2, 5, 3, 7, 101, 18]) 4 """ length = len(sequence) tree = [0] * (length << 2) sorted_seq = sorted((x, -i) for i, x in enumerate(sequence)) def _update(pos: int, left: int, right: int, target: int, vertex: int) -> None: if left == right: tree[pos] = vertex return mid = (left + right) >> 1 if target <= mid: _update(pos << 1, left, mid, target, vertex) else: _update((pos << 1) | 1, mid + 1, right, target, vertex) tree[pos] = max(tree[pos << 1], tree[(pos << 1) | 1]) def _get_max(pos: int, left: int, right: int, start: int, end: int) -> int: if left > end or right < start: return 0 if left >= start and right <= end: return tree[pos] mid = (left + right) >> 1 return max( _get_max(pos << 1, left, mid, start, end), _get_max((pos << 1) | 1, mid + 1, right, start, end), ) ans = 0 for tup in sorted_seq: i = -tup[1] cur = _get_max(1, 0, length - 1, 0, i - 1) + 1 ans = max(ans, cur) _update(1, 0, length - 1, i, cur) return ans ================================================ FILE: algorithms/dynamic_programming/matrix_chain_order.py ================================================ """ Matrix Chain Multiplication Find the optimal parenthesization of a chain of matrices to minimize the total number of scalar multiplications. Reference: https://en.wikipedia.org/wiki/Matrix_chain_multiplication Complexity: Time: O(n^3) Space: O(n^2) """ from __future__ import annotations _INF = float("inf") def matrix_chain_order(array: list[int]) -> tuple[list[list[int]], list[list[int]]]: """Compute minimum multiplication cost and optimal split positions. Args: array: List of matrix dimensions where matrix i has dimensions array[i-1] x array[i]. Returns: A tuple of (cost_matrix, split_matrix) where cost_matrix[i][j] holds the minimum cost and split_matrix[i][j] holds the optimal split point. Examples: >>> m, s = matrix_chain_order([30, 35, 15, 5, 10, 20, 25]) >>> m[1][6] 15125 """ n = len(array) matrix = [[0 for x in range(n)] for x in range(n)] sol = [[0 for x in range(n)] for x in range(n)] for chain_length in range(2, n): for a in range(1, n - chain_length + 1): b = a + chain_length - 1 matrix[a][b] = _INF for c in range(a, b): cost = ( matrix[a][c] + matrix[c + 1][b] + array[a - 1] * array[c] * array[b] ) if cost < matrix[a][b]: matrix[a][b] = cost sol[a][b] = c return matrix, sol ================================================ FILE: algorithms/dynamic_programming/max_product_subarray.py ================================================ """ Maximum Product Subarray Find the contiguous subarray within an array that has the largest product. Reference: https://leetcode.com/problems/maximum-product-subarray/ Complexity: max_product: Time: O(n) Space: O(1) subarray_with_max_product: Time: O(n) Space: O(1) """ from __future__ import annotations from functools import reduce def max_product(nums: list[int]) -> int: """Find the maximum product of a contiguous subarray. Args: nums: List of integers (containing at least one number). Returns: Largest product among all contiguous subarrays. Examples: >>> max_product([2, 3, -2, 4]) 6 """ lmin = lmax = gmax = nums[0] for num in nums[1:]: t_1 = num * lmax t_2 = num * lmin lmax = max(max(t_1, t_2), num) lmin = min(min(t_1, t_2), num) gmax = max(gmax, lmax) return gmax def subarray_with_max_product(arr: list[int]) -> tuple[int, list[int]]: """Find the maximum product subarray and return the product and subarray. Args: arr: List of positive or negative integers. Returns: A tuple of (max_product, subarray) where subarray is the contiguous slice that achieves the maximum product. Examples: >>> subarray_with_max_product([-2, -3, 6, 0, -7, -5]) (36, [-2, -3, 6]) """ length = len(arr) product_so_far = max_product_end = 1 max_start_i = 0 so_far_start_i = so_far_end_i = 0 all_negative_flag = True for i in range(length): max_product_end *= arr[i] if arr[i] > 0: all_negative_flag = False if max_product_end <= 0: max_product_end = arr[i] max_start_i = i if product_so_far <= max_product_end: product_so_far = max_product_end so_far_end_i = i so_far_start_i = max_start_i if all_negative_flag: product = reduce(lambda x, y: x * y, arr) return product, arr return product_so_far, arr[so_far_start_i : so_far_end_i + 1] ================================================ FILE: algorithms/dynamic_programming/max_subarray.py ================================================ """ Maximum Subarray (Kadane's Algorithm) Find the contiguous subarray with the largest sum. Reference: https://en.wikipedia.org/wiki/Maximum_subarray_problem Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def max_subarray(array: list[int]) -> int: """Find the maximum sum of a contiguous subarray using Kadane's algorithm. Args: array: List of integers (containing at least one number). Returns: Largest sum among all contiguous subarrays. Examples: >>> max_subarray([1, 2, -3, 4, 5, -7, 23]) 25 """ max_so_far = max_now = array[0] for i in range(1, len(array)): max_now = max(array[i], max_now + array[i]) max_so_far = max(max_so_far, max_now) return max_so_far ================================================ FILE: algorithms/dynamic_programming/min_cost_path.py ================================================ """ Minimum Cost Path Find the minimum cost to travel from station 0 to station N-1 given a cost matrix where cost[i][j] is the price of going from station i to station j (for i < j). Reference: https://en.wikipedia.org/wiki/Shortest_path_problem Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations _INF = float("inf") def min_cost(cost: list[list[int]]) -> int: """Compute the minimum cost to reach the last station from station 0. Args: cost: Square matrix where cost[i][j] is the travel cost from station i to station j (for i < j). Returns: Minimum cost to reach station N-1 from station 0. Examples: >>> min_cost([[0, 15, 80, 90], [-1, 0, 40, 50], ... [-1, -1, 0, 70], [-1, -1, -1, 0]]) 65 """ length = len(cost) dist = [_INF] * length dist[0] = 0 for i in range(length): for j in range(i + 1, length): dist[j] = min(dist[j], dist[i] + cost[i][j]) return dist[length - 1] ================================================ FILE: algorithms/dynamic_programming/num_decodings.py ================================================ """ Decode Ways Given an encoded message of digits, count the total number of ways to decode it where 'A' = 1, 'B' = 2, ..., 'Z' = 26. Reference: https://leetcode.com/problems/decode-ways/ Complexity: Time: O(n) Space: O(1) for num_decodings, O(n) for num_decodings2 """ from __future__ import annotations def num_decodings(enc_mes: str) -> int: """Count decoding ways using constant-space iteration. Args: enc_mes: String of digits representing the encoded message. Returns: Total number of ways to decode the message. Examples: >>> num_decodings("12") 2 >>> num_decodings("226") 3 """ if not enc_mes or enc_mes[0] == "0": return 0 last_char, last_two_chars = 1, 1 for i in range(1, len(enc_mes)): last = last_char if enc_mes[i] != "0" else 0 last_two = ( last_two_chars if int(enc_mes[i - 1 : i + 1]) < 27 and enc_mes[i - 1] != "0" else 0 ) last_two_chars = last_char last_char = last + last_two return last_char def num_decodings2(enc_mes: str) -> int: """Count decoding ways using a stack-based approach. Args: enc_mes: String of digits representing the encoded message. Returns: Total number of ways to decode the message. Examples: >>> num_decodings2("12") 2 >>> num_decodings2("226") 3 """ if not enc_mes or enc_mes.startswith("0"): return 0 stack = [1, 1] for i in range(1, len(enc_mes)): if enc_mes[i] == "0": if enc_mes[i - 1] == "0" or enc_mes[i - 1] > "2": return 0 stack.append(stack[-2]) elif 9 < int(enc_mes[i - 1 : i + 1]) < 27: stack.append(stack[-2] + stack[-1]) else: stack.append(stack[-1]) return stack[-1] ================================================ FILE: algorithms/dynamic_programming/planting_trees.py ================================================ """ Planting Trees Given an even number of trees along one side of a road, calculate the minimum total distance to move them into valid positions on both sides at even intervals. Reference: https://en.wikipedia.org/wiki/Dynamic_programming Complexity: Time: O(n^2) Space: O(n^2) """ from __future__ import annotations from math import sqrt def planting_trees(trees: list[int], length: int, width: int) -> float: """Compute the minimum distance to rearrange trees to valid positions. Args: trees: Sorted list of current tree positions along the road. length: Length of the road. width: Width of the road. Returns: Minimum total distance the trees must be moved. Examples: >>> planting_trees([0, 1, 10, 10], 10, 1) 2.414213562373095 """ trees = [0] + trees n_pairs = int(len(trees) / 2) space_between_pairs = length / (n_pairs - 1) target_locations = [location * space_between_pairs for location in range(n_pairs)] cmatrix = [[0 for _ in range(n_pairs + 1)] for _ in range(n_pairs + 1)] for r_i in range(1, n_pairs + 1): cmatrix[r_i][0] = cmatrix[r_i - 1][0] + sqrt( width + abs(trees[r_i] - target_locations[r_i - 1]) ** 2 ) for l_i in range(1, n_pairs + 1): cmatrix[0][l_i] = cmatrix[0][l_i - 1] + abs( trees[l_i] - target_locations[l_i - 1] ) for r_i in range(1, n_pairs + 1): for l_i in range(1, n_pairs + 1): cmatrix[r_i][l_i] = min( cmatrix[r_i - 1][l_i] + sqrt(width + (trees[l_i + r_i] - target_locations[r_i - 1]) ** 2), cmatrix[r_i][l_i - 1] + abs(trees[l_i + r_i] - target_locations[l_i - 1]), ) return cmatrix[n_pairs][n_pairs] ================================================ FILE: algorithms/dynamic_programming/regex_matching.py ================================================ """ Regular Expression Matching Implement regular expression matching with support for '.' (matches any single character) and '*' (matches zero or more of the preceding element). Reference: https://leetcode.com/problems/regular-expression-matching/ Complexity: Time: O(m * n) Space: O(m * n) """ from __future__ import annotations def is_match(str_a: str, str_b: str) -> bool: """Determine whether str_a matches the pattern str_b. Args: str_a: Input string. str_b: Pattern string (may contain '.' and '*'). Returns: True if str_a fully matches str_b, False otherwise. Examples: >>> is_match("aa", "a") False >>> is_match("aa", "a*") True """ len_a, len_b = len(str_a) + 1, len(str_b) + 1 matches = [[False] * len_b for _ in range(len_a)] matches[0][0] = True for i, element in enumerate(str_b[1:], 2): matches[0][i] = matches[0][i - 2] and element == "*" for i, char_a in enumerate(str_a, 1): for j, char_b in enumerate(str_b, 1): if char_b != "*": matches[i][j] = matches[i - 1][j - 1] and char_b in (char_a, ".") else: matches[i][j] |= matches[i][j - 2] if char_a == str_b[j - 2] or str_b[j - 2] == ".": matches[i][j] |= matches[i - 1][j] return matches[-1][-1] ================================================ FILE: algorithms/dynamic_programming/rod_cut.py ================================================ """ Rod Cutting Problem Given a rod of length n and a list of prices for each piece length, determine the maximum revenue obtainable by cutting and selling the pieces. Reference: https://en.wikipedia.org/wiki/Cutting_stock_problem Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations _INT_MIN = -32767 def cut_rod(price: list[int]) -> int: """Compute the maximum obtainable value by cutting a rod optimally. Args: price: List where price[i] is the price of a piece of length i+1. Returns: Maximum revenue from cutting and selling the rod. Examples: >>> cut_rod([1, 5, 8, 9, 10, 17, 17, 20]) 22 """ n = len(price) val = [0] * (n + 1) for i in range(1, n + 1): max_val = _INT_MIN for j in range(i): max_val = max(max_val, price[j] + val[i - j - 1]) val[i] = max_val return val[n] ================================================ FILE: algorithms/dynamic_programming/word_break.py ================================================ """ Word Break Given a string and a dictionary of words, determine whether the string can be segmented into a sequence of dictionary words. Reference: https://leetcode.com/problems/word-break/ Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations def word_break(word: str, word_dict: set[str]) -> bool: """Determine if word can be segmented into dictionary words. Args: word: The string to segment. word_dict: Set of valid dictionary words. Returns: True if word can be segmented, False otherwise. Examples: >>> word_break("leetcode", {"leet", "code"}) True >>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"}) False """ dp_array = [False] * (len(word) + 1) dp_array[0] = True for i in range(1, len(word) + 1): for j in range(0, i): if dp_array[j] and word[j:i] in word_dict: dp_array[i] = True break return dp_array[-1] ================================================ FILE: algorithms/graph/__init__.py ================================================ """ Collection of graph algorithms. """ from __future__ import annotations from algorithms.data_structures.graph import DirectedEdge, DirectedGraph, Node from algorithms.graph.a_star import a_star from algorithms.graph.all_factors import ( get_factors, get_factors_iterative1, get_factors_iterative2, ) from algorithms.graph.all_pairs_shortest_path import ( all_pairs_shortest_path, ) from algorithms.graph.bellman_ford import bellman_ford from algorithms.graph.blossom import max_matching from algorithms.graph.check_bipartite import check_bipartite from algorithms.graph.clone_graph import ( UndirectedGraphNode, clone_graph, clone_graph1, clone_graph2, ) from algorithms.graph.count_islands_bfs import count_islands from algorithms.graph.count_islands_dfs import num_islands as num_islands_dfs from algorithms.graph.count_islands_unionfind import ( num_islands as num_islands_unionfind, ) from algorithms.graph.dijkstra import Dijkstra from algorithms.graph.dijkstra_heapq import dijkstra from algorithms.graph.find_all_cliques import find_all_cliques from algorithms.graph.kahns_algorithm import Solution as KahnsSolution from algorithms.graph.markov_chain import iterating_markov_chain, next_state from algorithms.graph.maximum_flow import dinic, edmonds_karp, ford_fulkerson from algorithms.graph.maximum_flow_bfs import maximum_flow_bfs from algorithms.graph.maximum_flow_dfs import maximum_flow_dfs from algorithms.graph.maze_search_bfs import maze_search from algorithms.graph.maze_search_dfs import find_path as find_path_dfs from algorithms.graph.minimum_spanning_tree import DisjointSet, Edge, kruskal from algorithms.graph.pacific_atlantic import pacific_atlantic from algorithms.graph.prims_minimum_spanning import prims_minimum_spanning from algorithms.graph.satisfiability import solve_sat from algorithms.graph.shortest_distance_from_all_buildings import shortest_distance from algorithms.graph.sudoku_solver import Sudoku from algorithms.graph.tarjan import Tarjan from algorithms.graph.topological_sort_bfs import topological_sort from algorithms.graph.topological_sort_dfs import top_sort, top_sort_recursive from algorithms.graph.traversal import ( bfs_traverse, dfs_traverse, dfs_traverse_recursive, ) from algorithms.graph.walls_and_gates import walls_and_gates from algorithms.graph.word_ladder import ladder_length __all__ = [ # a_star "a_star", # all_pairs_shortest_path "all_pairs_shortest_path", # bellman_ford "bellman_ford", # check_bipartite "check_bipartite", # clone_graph "UndirectedGraphNode", "clone_graph", "clone_graph1", "clone_graph2", # dijkstra "Dijkstra", "dijkstra", # find_all_cliques "find_all_cliques", # graph "DirectedEdge", "DirectedGraph", "Node", # kahns_algorithm "KahnsSolution", # markov_chain "iterating_markov_chain", "next_state", # maximum_flow "dinic", "edmonds_karp", "ford_fulkerson", # maximum_flow_bfs "maximum_flow_bfs", # maximum_flow_dfs "maximum_flow_dfs", # minimum_spanning_tree "DisjointSet", "Edge", "kruskal", # prims_minimum_spanning "prims_minimum_spanning", # satisfiability "solve_sat", # tarjan "Tarjan", # traversal "bfs_traverse", "dfs_traverse", "dfs_traverse_recursive", # count_islands (bfs) "count_islands", # count_islands (dfs) "num_islands_dfs", # maze_search (bfs) "maze_search", # maze_search (dfs) "find_path_dfs", # word_ladder "ladder_length", # shortest_distance_from_all_buildings "shortest_distance", # topological_sort (bfs) "topological_sort", # all_factors "get_factors", "get_factors_iterative1", "get_factors_iterative2", # pacific_atlantic "pacific_atlantic", # sudoku_solver "Sudoku", # walls_and_gates "walls_and_gates", # topological_sort_dfs "top_sort", "top_sort_recursive", # count_islands_unionfind "num_islands_unionfind", # blossom "max_matching", ] ================================================ FILE: algorithms/graph/a_star.py ================================================ """ A* (A-star) Search Algorithm Finds the shortest path in a weighted graph using a heuristic function. Reference: https://en.wikipedia.org/wiki/A*_search_algorithm Complexity: Time: O(E log V) with a binary heap Space: O(V) """ from __future__ import annotations import heapq from collections.abc import Callable from typing import Any def a_star( graph: dict[Any, list[tuple[Any, float]]], start: Any, goal: Any, h: Callable[[Any], float], ) -> tuple[list[Any] | None, float]: """Find the shortest path using A* search. Args: graph: Adjacency list mapping node to list of (neighbor, cost) pairs. start: Starting node. goal: Goal node. h: Heuristic function estimating cost from a node to the goal. Returns: A tuple (path, total_cost). If no path exists, returns (None, inf). Examples: >>> g = {'A': [('B', 1)], 'B': [('C', 2)], 'C': []} >>> a_star(g, 'A', 'C', lambda n: 0) (['A', 'B', 'C'], 3) """ open_set: list[tuple[float, float, Any, list[Any]]] = [] heapq.heappush(open_set, (h(start), 0, start, [start])) visited: set[Any] = set() while open_set: f_score, g_score, current, path = heapq.heappop(open_set) if current == goal: return path, g_score if current in visited: continue visited.add(current) for neighbor, cost in graph.get(current, []): if neighbor not in visited: g = g_score + cost f = g + h(neighbor) heapq.heappush(open_set, (f, g, neighbor, path + [neighbor])) return None, float("inf") ================================================ FILE: algorithms/graph/all_factors.py ================================================ """ Factor Combinations Given an integer n, return all possible combinations of its factors (excluding 1 and n itself in the factorisation). Reference: https://leetcode.com/problems/factor-combinations/ Complexity: Time: O(n^(1/2) * log n) (approximate, depends on factor density) Space: O(log n) """ from __future__ import annotations def get_factors(n: int) -> list[list[int]]: """Return all factor combinations of *n* using recursion. Args: n: The number to factorise. Returns: List of factor lists. Examples: >>> get_factors(12) [[2, 6], [2, 2, 3], [3, 4]] """ def _factor( n: int, i: int, combi: list[int], res: list[list[int]], ) -> list[list[int]]: while i * i <= n: if n % i == 0: res += (combi + [i, int(n / i)],) _factor(n / i, i, combi + [i], res) i += 1 return res return _factor(n, 2, [], []) def get_factors_iterative1(n: int) -> list[list[int]]: """Return all factor combinations using an explicit stack. Args: n: The number to factorise. Returns: List of factor lists. Examples: >>> get_factors_iterative1(12) [[2, 6], [3, 4], [2, 2, 3]] """ todo: list[tuple[int, int, list[int]]] = [(n, 2, [])] res: list[list[int]] = [] while todo: n, i, combi = todo.pop() while i * i <= n: if n % i == 0: res += (combi + [i, n // i],) todo.append((n // i, i, combi + [i])) i += 1 return res def get_factors_iterative2(n: int) -> list[list[int]]: """Return all factor combinations using a stack-based approach. Args: n: The number to factorise. Returns: List of factor lists. Examples: >>> get_factors_iterative2(12) [[2, 2, 3], [2, 6], [3, 4]] """ ans: list[list[int]] = [] stack: list[int] = [] x = 2 while True: if x > n // x: if not stack: return ans ans.append(stack + [n]) x = stack.pop() n *= x x += 1 elif n % x == 0: stack.append(x) n //= x else: x += 1 ================================================ FILE: algorithms/graph/all_pairs_shortest_path.py ================================================ """ All-Pairs Shortest Path (Floyd-Warshall) Given an n*n adjacency matrix, computes the shortest path between every pair of vertices using the Floyd-Warshall algorithm. Reference: https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm Complexity: Time: O(V^3) Space: O(V^2) """ from __future__ import annotations import copy def all_pairs_shortest_path( adjacency_matrix: list[list[float]], ) -> list[list[float]]: """Compute shortest distances between all pairs of vertices. Args: adjacency_matrix: An n*n matrix where entry [i][j] is the edge weight from vertex i to vertex j. Returns: A new n*n matrix containing the shortest distance between each pair. Examples: >>> all_pairs_shortest_path( ... [[0, 1, float('inf')], [float('inf'), 0, 1], ... [1, float('inf'), 0]]) [[0, 1, 2], [2, 0, 1], [1, 2, 0]] """ new_array = copy.deepcopy(adjacency_matrix) size = len(new_array) for k in range(size): for i in range(size): for j in range(size): if new_array[i][j] > new_array[i][k] + new_array[k][j]: new_array[i][j] = new_array[i][k] + new_array[k][j] return new_array ================================================ FILE: algorithms/graph/bellman_ford.py ================================================ """ Bellman-Ford Algorithm for Single-Source Shortest Path Finds the shortest paths from a source vertex to all other vertices in a weighted directed graph. Unlike Dijkstra's algorithm it can handle graphs with negative edge weights. Reference: https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm Complexity: Time: O(V * E) Space: O(V) """ from __future__ import annotations def bellman_ford(graph: dict[str, dict[str, float]], source: str) -> bool: """Compute shortest paths from *source* and detect negative cycles. Args: graph: Weighted directed graph as ``{node: {neighbor: edge_weight, ...}, ...}``. source: The starting vertex. Returns: True if shortest paths were computed (no negative cycle), False otherwise. Examples: >>> g = {'a': {'b': 1}, 'b': {'c': 2}, 'c': {}} >>> bellman_ford(g, 'a') True """ distance: dict[str, float] = {} predecessor: dict[str, str | None] = {} _initialize_single_source(graph, source, distance, predecessor) num_vertices = len(graph) for _ in range(1, num_vertices): for current_node in graph: for neighbor in graph[current_node]: edge_weight = graph[current_node][neighbor] if distance[neighbor] > distance[current_node] + edge_weight: distance[neighbor] = distance[current_node] + edge_weight predecessor[neighbor] = current_node for current_node in graph: for neighbor in graph[current_node]: edge_weight = graph[current_node][neighbor] if distance[neighbor] > distance[current_node] + edge_weight: return False return True def _initialize_single_source( graph: dict[str, dict[str, float]], source: str, distance: dict[str, float], predecessor: dict[str, str | None], ) -> None: """Set up initial distances and predecessors. Args: graph: The weighted directed graph dictionary. source: The source vertex. distance: Dictionary to store shortest distances (modified in place). predecessor: Dictionary to store path predecessors (modified in place). """ all_nodes: set[str] = set(graph.keys()) for neighbors in graph.values(): all_nodes.update(neighbors.keys()) for node in all_nodes: distance[node] = float("inf") predecessor[node] = None distance[source] = 0 ================================================ FILE: algorithms/graph/blossom.py ================================================ """Edmonds' blossom algorithm — maximum cardinality matching. Finds a maximum matching in a general (non-bipartite) undirected graph. The algorithm handles odd-length cycles ("blossoms") by contracting them and recursing. Time: O(V^2 * E). Inspired by PR #826 (abhishekiitm). """ from __future__ import annotations from collections import deque def max_matching(n: int, edges: list[tuple[int, int]]) -> list[tuple[int, int]]: """Return a maximum cardinality matching for an undirected graph. *n* is the number of vertices (0..n-1). *edges* is a list of (u, v) edges. Returns a list of matched (u, v) pairs. >>> sorted(max_matching(4, [(0,1),(1,2),(2,3)])) [(0, 1), (2, 3)] """ adj: list[list[int]] = [[] for _ in range(n)] for u, v in edges: adj[u].append(v) adj[v].append(u) match = [-1] * n def find_augmenting_path() -> bool: """Try to find an augmenting path from any free vertex.""" parent = [-1] * n visited = [False] * n for start in range(n): if match[start] != -1: continue # BFS from this free vertex queue: deque[int] = deque([start]) visited[start] = True found = False while queue and not found: u = queue.popleft() for v in adj[u]: if visited[v] or v == match[u]: continue if match[v] == -1 and v != start: # Augmenting path found — trace back and flip _augment(parent, match, u, v) return True if match[v] != -1: visited[v] = True visited[match[v]] = True parent[match[v]] = u queue.append(match[v]) return False while find_augmenting_path(): pass result = [] seen = set() for i in range(n): if match[i] != -1 and i not in seen: result.append((i, match[i])) seen.add(i) seen.add(match[i]) return result def _augment(parent: list[int], match: list[int], u: int, v: int) -> None: """Augment the matching along the path ending with edge (u, v).""" while u != -1: prev = match[u] match[u] = v match[v] = u v = prev u = parent[v] if v != -1 else -1 ================================================ FILE: algorithms/graph/check_bipartite.py ================================================ """ Check Bipartite Graph Determine whether an undirected graph is bipartite using BFS colouring. Reference: https://en.wikipedia.org/wiki/Bipartite_graph Complexity: Time: O(V^2) (adjacency-matrix representation) Space: O(V) """ from __future__ import annotations from collections import deque def check_bipartite(adj_list: list[list[int]]) -> bool: """Return True if the graph represented by *adj_list* is bipartite. Args: adj_list: An n*n adjacency matrix where adj_list[i][j] is truthy if there is an edge between vertex *i* and vertex *j*. Returns: True if bipartite, False otherwise. Examples: >>> check_bipartite([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) True """ vertices = len(adj_list) set_type = [-1 for _ in range(vertices)] set_type[0] = 0 queue = deque([0]) while queue: current = queue.popleft() if adj_list[current][current]: return False for adjacent in range(vertices): if adj_list[current][adjacent]: if set_type[adjacent] == set_type[current]: return False if set_type[adjacent] == -1: set_type[adjacent] = 1 - set_type[current] queue.append(adjacent) return True ================================================ FILE: algorithms/graph/check_digraph_strongly_connected.py ================================================ """ Check if a Directed Graph is Strongly Connected A directed graph is strongly connected if every vertex is reachable from every other vertex. This implementation uses two DFS passes (one on the original graph and one on the reversed graph). Reference: https://en.wikipedia.org/wiki/Strongly_connected_component Complexity: Time: O(V + E) Space: O(V + E) """ from __future__ import annotations from collections import defaultdict class Graph: """A directed graph for strong-connectivity testing.""" def __init__(self, vertex_count: int) -> None: """Create a new graph with *vertex_count* vertices. Args: vertex_count: Number of vertices (labelled 0 .. vertex_count-1). """ self.vertex_count = vertex_count self.graph: dict[int, list[int]] = defaultdict(list) def add_edge(self, source: int, target: int) -> None: """Add a directed edge from *source* to *target*. Args: source: Source vertex. target: Target vertex. """ self.graph[source].append(target) def dfs(self) -> bool: """Return True if all vertices are reachable from vertex 0.""" visited = [False] * self.vertex_count self._dfs_util(0, visited) return visited == [True] * self.vertex_count def _dfs_util(self, source: int, visited: list[bool]) -> None: """Recursive DFS helper. Args: source: Current vertex. visited: Visited flags (modified in place). """ visited[source] = True for adjacent in self.graph[source]: if not visited[adjacent]: self._dfs_util(adjacent, visited) def reverse_graph(self) -> Graph: """Return a new graph with every edge reversed. Returns: A new Graph instance with reversed edges. """ reverse = Graph(self.vertex_count) for source, adjacent in self.graph.items(): for target in adjacent: reverse.add_edge(target, source) return reverse def is_strongly_connected(self) -> bool: """Return True if the graph is strongly connected. Returns: True when every vertex can reach every other vertex. """ if self.dfs(): reversed_graph = self.reverse_graph() if reversed_graph.dfs(): return True return False ================================================ FILE: algorithms/graph/clone_graph.py ================================================ """ Clone an Undirected Graph Each node contains a label and a list of its neighbours. Three strategies are provided: BFS-based, iterative DFS, and recursive DFS. Reference: https://leetcode.com/problems/clone-graph/ Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations import collections class UndirectedGraphNode: """A node in an undirected graph.""" def __init__(self, label: int) -> None: self.label = label self.neighbors: list[UndirectedGraphNode] = [] def shallow_copy(self) -> UndirectedGraphNode: """Return a copy of this node without neighbours. Returns: A new node with the same label. """ return UndirectedGraphNode(self.label) def add_neighbor(self, node: UndirectedGraphNode) -> None: """Append *node* to the neighbour list. Args: node: Neighbour to add. """ self.neighbors.append(node) def clone_graph1(node: UndirectedGraphNode | None) -> UndirectedGraphNode | None: """Clone a graph using BFS. Args: node: Any node in the original graph. Returns: The corresponding node in the cloned graph, or None. """ if not node: return None node_copy = node.shallow_copy() dic: dict[UndirectedGraphNode, UndirectedGraphNode] = {node: node_copy} queue: collections.deque[UndirectedGraphNode] = collections.deque([node]) while queue: node = queue.popleft() for neighbor in node.neighbors: if neighbor not in dic: neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy dic[node].add_neighbor(neighbor_copy) queue.append(neighbor) else: dic[node].add_neighbor(dic[neighbor]) return node_copy def clone_graph2(node: UndirectedGraphNode | None) -> UndirectedGraphNode | None: """Clone a graph using iterative DFS. Args: node: Any node in the original graph. Returns: The corresponding node in the cloned graph, or None. """ if not node: return None node_copy = node.shallow_copy() dic: dict[UndirectedGraphNode, UndirectedGraphNode] = {node: node_copy} stack = [node] while stack: node = stack.pop() for neighbor in node.neighbors: if neighbor not in dic: neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy dic[node].add_neighbor(neighbor_copy) stack.append(neighbor) else: dic[node].add_neighbor(dic[neighbor]) return node_copy def clone_graph(node: UndirectedGraphNode | None) -> UndirectedGraphNode | None: """Clone a graph using recursive DFS. Args: node: Any node in the original graph. Returns: The corresponding node in the cloned graph, or None. """ if not node: return None node_copy = node.shallow_copy() dic: dict[UndirectedGraphNode, UndirectedGraphNode] = {node: node_copy} _dfs(node, dic) return node_copy def _dfs( node: UndirectedGraphNode, dic: dict[UndirectedGraphNode, UndirectedGraphNode], ) -> None: """Recursively clone neighbours into *dic*. Args: node: Current node being cloned. dic: Mapping from original nodes to their clones. """ for neighbor in node.neighbors: if neighbor not in dic: neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy dic[node].add_neighbor(neighbor_copy) _dfs(neighbor, dic) else: dic[node].add_neighbor(dic[neighbor]) ================================================ FILE: algorithms/graph/count_connected_number_of_component.py ================================================ """ Count Connected Components in an Undirected Graph Uses DFS to count the number of connected components. Reference: https://en.wikipedia.org/wiki/Component_(graph_theory) Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations def count_components(adjacency_list: list[list[int]], size: int) -> int: """Return the number of connected components. Args: adjacency_list: Adjacency list where adjacency_list[i] contains the neighbours of vertex *i* (1-indexed vertices). size: Number of vertices. Returns: The count of connected components. Examples: >>> count_components([[], [2], [1]], 2) 1 """ count = 0 visited = [False] * (size + 1) for i in range(1, size + 1): if not visited[i]: _dfs(i, visited, adjacency_list) count += 1 return count def _dfs( source: int, visited: list[bool], adjacency_list: list[list[int]], ) -> None: """Mark all vertices reachable from *source* as visited. Args: source: Starting vertex. visited: Visited flags (modified in place). adjacency_list: Graph adjacency list. """ visited[source] = True for child in adjacency_list[source]: if not visited[child]: _dfs(child, visited, adjacency_list) ================================================ FILE: algorithms/graph/count_islands_bfs.py ================================================ """ Count Islands (BFS) Given a 2D grid of 1s (land) and 0s (water), count the number of islands using breadth-first search. An island is a group of adjacent lands connected horizontally or vertically. Reference: https://leetcode.com/problems/number-of-islands/ Complexity: Time: O(M * N) Space: O(M * N) """ from __future__ import annotations from collections import deque def count_islands(grid: list[list[int]]) -> int: """Return the number of islands in *grid*. Args: grid: 2D matrix of 0s and 1s. Returns: Number of connected components of 1s. Examples: >>> count_islands([[1, 0], [0, 1]]) 2 """ row = len(grid) col = len(grid[0]) num_islands = 0 visited = [[0] * col for _ in range(row)] directions = [[-1, 0], [1, 0], [0, -1], [0, 1]] queue: deque[tuple[int, int]] = deque() for i in range(row): for j, num in enumerate(grid[i]): if num == 1 and visited[i][j] != 1: visited[i][j] = 1 queue.append((i, j)) while queue: x, y = queue.popleft() for k in range(len(directions)): nx_x = x + directions[k][0] nx_y = y + directions[k][1] if (0 <= nx_x < row and 0 <= nx_y < col and visited[nx_x][nx_y] != 1 and grid[nx_x][nx_y] == 1): queue.append((nx_x, nx_y)) visited[nx_x][nx_y] = 1 num_islands += 1 return num_islands ================================================ FILE: algorithms/graph/count_islands_dfs.py ================================================ """ Count Islands (DFS) Given a 2D grid of 1s (land) and 0s (water), count the number of islands using depth-first search. Reference: https://leetcode.com/problems/number-of-islands/ Complexity: Time: O(M * N) Space: O(M * N) recursion stack in worst case """ from __future__ import annotations def num_islands(grid: list[list[int]]) -> int: """Return the number of islands in *grid*. Args: grid: 2D matrix of 0s and 1s (modified in place during traversal). Returns: Number of connected components of 1s. Examples: >>> num_islands([[1, 0], [0, 1]]) 2 """ count = 0 for i in range(len(grid)): for j, col in enumerate(grid[i]): if col == 1: _dfs(grid, i, j) count += 1 return count def _dfs(grid: list[list[int]], i: int, j: int) -> None: """Flood-fill from (i, j), marking visited cells as 0. Args: grid: The grid (modified in place). i: Row index. j: Column index. """ if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]): return if grid[i][j] != 1: return grid[i][j] = 0 _dfs(grid, i + 1, j) _dfs(grid, i - 1, j) _dfs(grid, i, j + 1) _dfs(grid, i, j - 1) ================================================ FILE: algorithms/graph/count_islands_unionfind.py ================================================ """ Count Islands via Union-Find Uses the Union-Find (Disjoint Set) data structure to solve the "Number of Islands" problem. After each addLand operation, counts distinct connected components of land cells. Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure Complexity: Time: O(m * alpha(m)) where m is number of positions Space: O(m) """ from __future__ import annotations from algorithms.data_structures.union_find import Union def num_islands(positions: list[list[int]]) -> list[int]: """Count islands after each addLand operation. Given a sequence of positions on a 2D grid, each operation turns a water cell into land. After each operation, count the number of distinct islands (connected components of land cells). Args: positions: A list of [row, col] pairs indicating land additions. Returns: A list of island counts, one per operation. Examples: >>> num_islands([[0, 0], [0, 1], [1, 2], [2, 1]]) [1, 1, 2, 3] """ result: list[int] = [] islands = Union() for position in map(tuple, positions): islands.add(position) for delta in (0, 1), (0, -1), (1, 0), (-1, 0): adjacent = (position[0] + delta[0], position[1] + delta[1]) if adjacent in islands.parents: islands.unite(position, adjacent) result.append(islands.count) return result ================================================ FILE: algorithms/graph/cycle_detection.py ================================================ """ Cycle Detection in a Directed Graph Uses DFS with three-colour marking to determine whether a directed graph contains a cycle. Reference: https://en.wikipedia.org/wiki/Cycle_(graph_theory) Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations from enum import Enum class TraversalState(Enum): """Vertex states during DFS traversal.""" WHITE = 0 GRAY = 1 BLACK = 2 def is_in_cycle( graph: dict[str, list[str]], traversal_states: dict[str, TraversalState], vertex: str, ) -> bool: """Return True if *vertex* is part of a cycle. Args: graph: Adjacency list of a directed graph. traversal_states: Current DFS colour for each vertex. vertex: Vertex to inspect. Returns: True if a cycle is detected through *vertex*. """ if traversal_states[vertex] == TraversalState.GRAY: return True traversal_states[vertex] = TraversalState.GRAY for neighbor in graph[vertex]: if is_in_cycle(graph, traversal_states, neighbor): return True traversal_states[vertex] = TraversalState.BLACK return False def contains_cycle(graph: dict[str, list[str]]) -> bool: """Return True if *graph* contains at least one cycle. Args: graph: Directed graph as ``{vertex: [neighbours], ...}``. Returns: True when a cycle exists. Examples: >>> contains_cycle({'A': ['B'], 'B': ['A']}) True >>> contains_cycle({'A': ['B'], 'B': []}) False """ traversal_states = {vertex: TraversalState.WHITE for vertex in graph} for vertex, state in traversal_states.items(): if state == TraversalState.WHITE and is_in_cycle( graph, traversal_states, vertex ): return True return False ================================================ FILE: algorithms/graph/dijkstra.py ================================================ """ Dijkstra's Single-Source Shortest-Path Algorithm Finds shortest distances from a source vertex to every other vertex in a graph with non-negative edge weights. Reference: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm Complexity: Time: O(V^2) (adjacency-matrix representation) Space: O(V) """ from __future__ import annotations class Dijkstra: """A fully connected directed graph with edge weights.""" def __init__(self, vertex_count: int) -> None: """Initialise graph with *vertex_count* vertices. Args: vertex_count: Number of vertices. """ self.vertex_count = vertex_count self.graph: list[list[int]] = [ [0 for _ in range(vertex_count)] for _ in range(vertex_count) ] def min_distance(self, dist: list[float], min_dist_set: list[bool]) -> int: """Return the unvisited vertex with the smallest distance. Args: dist: Current shortest distances. min_dist_set: Flags indicating already-processed vertices. Returns: Index of the closest unvisited vertex. """ min_dist = float("inf") min_index = 0 for target in range(self.vertex_count): if min_dist_set[target]: continue if dist[target] < min_dist: min_dist = dist[target] min_index = target return min_index def dijkstra(self, src: int) -> list[float]: """Compute shortest distances from *src* to all other vertices. Args: src: Source vertex index. Returns: List of shortest distances indexed by vertex. Examples: >>> g = Dijkstra(3) >>> g.graph = [[0, 1, 4], [1, 0, 2], [4, 2, 0]] >>> g.dijkstra(0) [0, 1, 3] """ dist: list[float] = [float("inf")] * self.vertex_count dist[src] = 0 min_dist_set = [False] * self.vertex_count for _ in range(self.vertex_count): source = self.min_distance(dist, min_dist_set) min_dist_set[source] = True for target in range(self.vertex_count): if self.graph[source][target] <= 0 or min_dist_set[target]: continue if dist[target] > dist[source] + self.graph[source][target]: dist[target] = dist[source] + self.graph[source][target] return dist ================================================ FILE: algorithms/graph/dijkstra_heapq.py ================================================ """ Dijkstra's Shortest-Path Algorithm (Heap-Optimised) Computes single-source shortest paths in a graph with non-negative edge weights using a min-heap (priority queue) for efficient vertex selection. This adjacency-list implementation is faster than the O(V²) matrix version for sparse graphs. Reference: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm Complexity: Time: O((V + E) log V) using a binary heap Space: O(V + E) """ from __future__ import annotations import heapq def dijkstra( graph: dict[str, dict[str, int | float]], source: str, target: str = "", ) -> tuple[int | float, list[str]]: """Return the shortest distance and path from *source* to *target*. Args: graph: Adjacency-list mapping each vertex to a dict of {neighbour: weight}. source: Starting vertex. target: Destination vertex. When empty, compute shortest distances to all reachable vertices and return the path to the last vertex relaxed (mainly useful when a target is provided). Returns: A ``(distance, path)`` tuple where *distance* is the total shortest-path cost and *path* is the list of vertices from *source* to *target* inclusive. If *target* is unreachable the distance is ``float('inf')`` and the path is empty. Examples: >>> g = { ... "s": {"a": 2, "b": 1}, ... "a": {"s": 3, "b": 4, "c": 8}, ... "b": {"s": 4, "a": 2, "d": 2}, ... "c": {"a": 2, "d": 7, "t": 4}, ... "d": {"b": 1, "c": 11, "t": 5}, ... "t": {"c": 3, "d": 5}, ... } >>> dijkstra(g, "s", "t") (8, ['s', 'b', 'd', 't']) """ dist: dict[str, int | float] = {v: float("inf") for v in graph} dist[source] = 0 prev: dict[str, str | None] = {v: None for v in graph} heap: list[tuple[int | float, str]] = [(0, source)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue if u == target: break for v, weight in graph[u].items(): alt = dist[u] + weight if alt < dist.get(v, float("inf")): dist[v] = alt prev[v] = u heapq.heappush(heap, (alt, v)) # Reconstruct path path: list[str] = [] node: str | None = target if target else None if node and dist.get(node, float("inf")) < float("inf"): while node is not None: path.append(node) node = prev.get(node) path.reverse() return (dist.get(target, float("inf")), path) if target else (0, []) ================================================ FILE: algorithms/graph/find_all_cliques.py ================================================ """ Find All Cliques (Bron-Kerbosch) Finds every maximal clique in an undirected graph. Reference: Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph", Communications of the ACM. Complexity: Time: O(3^(V/3)) worst case Space: O(V) """ from __future__ import annotations def find_all_cliques(edges: dict[str, set[str]]) -> list[list[str]]: """Return all maximal cliques in the graph. Args: edges: Adjacency sets keyed by vertex label. Returns: A list of cliques, where each clique is a list of vertex labels. Examples: >>> find_all_cliques({'0': {'1'}, '1': {'0'}}) [['0', '1']] """ compsub: list[str] = [] solutions: list[list[str]] = [] def _expand_clique(candidates: set[str], nays: set[str]) -> None: if not candidates and not nays: solutions.append(compsub.copy()) else: for selected in candidates.copy(): candidates.remove(selected) candidates_temp = _get_connected(selected, candidates) nays_temp = _get_connected(selected, nays) compsub.append(selected) _expand_clique(candidates_temp, nays_temp) nays.add(compsub.pop()) def _get_connected(vertex: str, old_set: set[str]) -> set[str]: new_set: set[str] = set() for neighbor in edges[str(vertex)]: if neighbor in old_set: new_set.add(neighbor) return new_set possibles = set(edges.keys()) _expand_clique(possibles, set()) return solutions ================================================ FILE: algorithms/graph/find_path.py ================================================ """ Find Paths in a Graph Provides functions to find a single path, all paths, or the shortest path between two nodes using recursion and backtracking. Complexity: Time: O(V!) worst case (exponential backtracking) Space: O(V) per recursion stack """ from __future__ import annotations from typing import Any def find_path( graph: dict[Any, list[Any]], start: Any, end: Any, path: list[Any] | None = None, ) -> list[Any] | None: """Find a path between *start* and *end* using backtracking. Args: graph: Adjacency list. start: Source node. end: Target node. path: Accumulated path (internal use). Returns: A list representing the path, or None if no path exists. Examples: >>> find_path({'A': ['B'], 'B': ['C'], 'C': []}, 'A', 'C') ['A', 'B', 'C'] """ if path is None: path = [] path = path + [start] if start == end: return path if start not in graph: return None for node in graph[start]: if node not in path: newpath = find_path(graph, node, end, path) return newpath return None def find_all_path( graph: dict[Any, list[Any]], start: Any, end: Any, path: list[Any] | None = None, ) -> list[list[Any]]: """Find all paths between *start* and *end*. Args: graph: Adjacency list. start: Source node. end: Target node. path: Accumulated path (internal use). Returns: A list of all paths, where each path is a list of nodes. Examples: >>> find_all_path({'A': ['B', 'C'], 'B': ['C'], 'C': []}, 'A', 'C') [['A', 'B', 'C'], ['A', 'C']] """ if path is None: path = [] path = path + [start] if start == end: return [path] if start not in graph: return [] paths: list[list[Any]] = [] for node in graph[start]: if node not in path: newpaths = find_all_path(graph, node, end, path) for newpath in newpaths: paths.append(newpath) return paths def find_shortest_path( graph: dict[Any, list[Any]], start: Any, end: Any, path: list[Any] | None = None, ) -> list[Any] | None: """Find the shortest path between *start* and *end*. Args: graph: Adjacency list. start: Source node. end: Target node. path: Accumulated path (internal use). Returns: The shortest path as a list of nodes, or None if unreachable. Examples: >>> find_shortest_path({'A': ['B', 'C'], 'B': ['C'], 'C': []}, 'A', 'C') ['A', 'C'] """ if path is None: path = [] path = path + [start] if start == end: return path if start not in graph: return None shortest: list[Any] | None = None for node in graph[start]: if node not in path: newpath = find_shortest_path(graph, node, end, path) if newpath and (not shortest or len(newpath) < len(shortest)): shortest = newpath return shortest ================================================ FILE: algorithms/graph/graph.py ================================================ """Graph Data Structures (re-export). This module re-exports Node, DirectedEdge, and DirectedGraph from the canonical location in ``algorithms.data_structures.graph`` for backward compatibility. """ from algorithms.data_structures.graph import DirectedEdge, DirectedGraph, Node __all__ = ["DirectedEdge", "DirectedGraph", "Node"] ================================================ FILE: algorithms/graph/kahns_algorithm.py ================================================ """ Kahn's Algorithm (Topological Sort via BFS) Computes a topological ordering of a directed acyclic graph using an in-degree based BFS approach. Reference: https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations from collections import deque class Solution: """Wrapper class for Kahn's topological sort.""" def topological_sort( self, vertices: int, adj: list[list[int]] ) -> list[int]: """Return a topological ordering of the graph. Args: vertices: Number of vertices. adj: Adjacency list where adj[i] lists neighbours of vertex *i*. Returns: A list of vertices in topological order, or an empty list if a cycle is detected. Examples: >>> Solution().topological_sort(3, [[1], [2], []]) [0, 1, 2] """ in_degree = [0] * vertices for i in range(vertices): for neighbor in adj[i]: in_degree[neighbor] += 1 queue = deque( [i for i in range(vertices) if in_degree[i] == 0] ) topo_order: list[int] = [] while queue: node = queue.popleft() topo_order.append(node) for neighbor in adj[node]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) if len(topo_order) != vertices: return [] return topo_order ================================================ FILE: algorithms/graph/markov_chain.py ================================================ """ Markov Chain Provides utilities for stepping through and iterating a discrete Markov chain described as a dictionary of transition probabilities. Reference: https://en.wikipedia.org/wiki/Markov_chain Complexity: Time: O(S) per step, where S is the number of states Space: O(S) """ from __future__ import annotations import random from collections.abc import Iterator from typing import Any def _choose_state(state_map: dict[Any, float]) -> Any | None: """Choose the next state randomly according to *state_map*. Args: state_map: Mapping of state to its transition probability. Returns: The selected state, or None if probabilities don't sum to 1. """ choice = random.random() probability_reached = 0.0 for state, probability in state_map.items(): probability_reached += probability if probability_reached > choice: return state return None def next_state(chain: dict[Any, dict[Any, float]], current_state: Any) -> Any: """Return the next state given a Markov chain and current state. Args: chain: Markov chain as ``{state: {next_state: probability}}``. current_state: The current state. Returns: The randomly chosen next state. Examples: >>> c = {'A': {'A': 1.0}} >>> next_state(c, 'A') 'A' """ next_state_map = chain.get(current_state) return _choose_state(next_state_map) def iterating_markov_chain( chain: dict[Any, dict[Any, float]], state: Any, ) -> Iterator[Any]: """Yield an infinite sequence of states from a Markov chain. Args: chain: Markov chain transition dictionary. state: Initial state. Yields: Successive states of the chain. """ while True: state = next_state(chain, state) yield state ================================================ FILE: algorithms/graph/maximum_flow.py ================================================ """ Maximum Flow Algorithms Implements Ford-Fulkerson (DFS), Edmonds-Karp (BFS) and Dinic's algorithm for computing maximum flow in a flow network. Reference: https://en.wikipedia.org/wiki/Maximum_flow_problem Complexity: Ford-Fulkerson: O(E * f) where f is the max flow value Edmonds-Karp: O(V * E^2) Dinic: O(V^2 * E) """ from __future__ import annotations from queue import Queue def _dfs( capacity: list[list[int]], flow: list[list[int]], visit: list[bool], vertices: int, idx: int, sink: int, current_flow: int = 1 << 63, ) -> int: """DFS helper for Ford-Fulkerson. Args: capacity: Capacity matrix. flow: Current flow matrix. visit: Visited flags. vertices: Total number of vertices. idx: Current vertex index. sink: Sink vertex index. current_flow: Flow available along this path. Returns: Flow pushed along the augmenting path found. """ if idx == sink: return current_flow visit[idx] = True for nxt in range(vertices): if not visit[nxt] and flow[idx][nxt] < capacity[idx][nxt]: available_flow = min(current_flow, capacity[idx][nxt] - flow[idx][nxt]) tmp = _dfs(capacity, flow, visit, vertices, nxt, sink, available_flow) if tmp: flow[idx][nxt] += tmp flow[nxt][idx] -= tmp return tmp return 0 def ford_fulkerson(capacity: list[list[int]], source: int, sink: int) -> int: """Compute maximum flow using Ford-Fulkerson (DFS). Args: capacity: Capacity matrix. source: Source vertex. sink: Sink vertex. Returns: The maximum flow value. Examples: >>> ford_fulkerson([[0, 10, 0], [0, 0, 10], [0, 0, 0]], 0, 2) 10 """ vertices = len(capacity) ret = 0 flow = [[0] * vertices for _ in range(vertices)] while True: visit = [False for _ in range(vertices)] tmp = _dfs(capacity, flow, visit, vertices, source, sink) if tmp: ret += tmp else: break return ret def edmonds_karp(capacity: list[list[int]], source: int, sink: int) -> int: """Compute maximum flow using Edmonds-Karp (BFS). Args: capacity: Capacity matrix. source: Source vertex. sink: Sink vertex. Returns: The maximum flow value. Examples: >>> edmonds_karp([[0, 10, 0], [0, 0, 10], [0, 0, 0]], 0, 2) 10 """ vertices = len(capacity) ret = 0 flow = [[0] * vertices for _ in range(vertices)] while True: tmp = 0 queue: Queue[tuple[int, int]] = Queue() visit = [False for _ in range(vertices)] par = [-1 for _ in range(vertices)] visit[source] = True queue.put((source, 1 << 63)) while queue.qsize(): front = queue.get() idx, current_flow = front if idx == sink: tmp = current_flow break for nxt in range(vertices): if not visit[nxt] and flow[idx][nxt] < capacity[idx][nxt]: visit[nxt] = True par[nxt] = idx queue.put( (nxt, min(current_flow, capacity[idx][nxt] - flow[idx][nxt])) ) if par[sink] == -1: break ret += tmp parent = par[sink] idx = sink while parent != -1: flow[parent][idx] += tmp flow[idx][parent] -= tmp idx = parent parent = par[parent] return ret def _dinic_bfs( capacity: list[list[int]], flow: list[list[int]], level: list[int], source: int, sink: int, ) -> bool: """BFS level graph construction for Dinic's algorithm. Args: capacity: Capacity matrix. flow: Current flow matrix. level: Level array (modified in place). source: Source vertex. sink: Sink vertex. Returns: True if sink is reachable from source. """ vertices = len(capacity) queue: Queue[int] = Queue() queue.put(source) level[source] = 0 while queue.qsize(): front = queue.get() for nxt in range(vertices): if level[nxt] == -1 and flow[front][nxt] < capacity[front][nxt]: level[nxt] = level[front] + 1 queue.put(nxt) return level[sink] != -1 def _dinic_dfs( capacity: list[list[int]], flow: list[list[int]], level: list[int], idx: int, sink: int, work: list[int], current_flow: int = 1 << 63, ) -> int: """DFS blocking flow for Dinic's algorithm. Args: capacity: Capacity matrix. flow: Current flow matrix. level: Level array. idx: Current vertex. sink: Sink vertex. work: Work pointer array. current_flow: Available flow. Returns: Flow pushed. """ if idx == sink: return current_flow vertices = len(capacity) while work[idx] < vertices: nxt = work[idx] if level[nxt] == level[idx] + 1 and flow[idx][nxt] < capacity[idx][nxt]: available_flow = min(current_flow, capacity[idx][nxt] - flow[idx][nxt]) tmp = _dinic_dfs(capacity, flow, level, nxt, sink, work, available_flow) if tmp > 0: flow[idx][nxt] += tmp flow[nxt][idx] -= tmp return tmp work[idx] += 1 return 0 def dinic(capacity: list[list[int]], source: int, sink: int) -> int: """Compute maximum flow using Dinic's algorithm. Args: capacity: Capacity matrix. source: Source vertex. sink: Sink vertex. Returns: The maximum flow value. Examples: >>> dinic([[0, 10, 0], [0, 0, 10], [0, 0, 0]], 0, 2) 10 """ vertices = len(capacity) flow = [[0] * vertices for _ in range(vertices)] ret = 0 while True: level = [-1 for _ in range(vertices)] work = [0 for _ in range(vertices)] if not _dinic_bfs(capacity, flow, level, source, sink): break while True: tmp = _dinic_dfs(capacity, flow, level, source, sink, work) if tmp > 0: ret += tmp else: break return ret ================================================ FILE: algorithms/graph/maximum_flow_bfs.py ================================================ """ Maximum Flow via BFS Computes maximum flow in a network represented as an adjacency matrix, using BFS to find augmenting paths. Reference: https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm Complexity: Time: O(V * E^2) Space: O(V^2) """ from __future__ import annotations import copy import math import queue def maximum_flow_bfs(adjacency_matrix: list[list[int]]) -> int: """Compute maximum flow using BFS augmenting paths. The source is the first vertex and the sink is the last vertex. Args: adjacency_matrix: n*n capacity matrix. Returns: The maximum flow value. Examples: >>> maximum_flow_bfs([[0, 10, 0], [0, 0, 10], [0, 0, 0]]) 10 """ new_array = copy.deepcopy(adjacency_matrix) total = 0 while True: min_flow = math.inf visited = [0] * len(new_array) path = [0] * len(new_array) bfs: queue.Queue[int] = queue.Queue() visited[0] = 1 bfs.put(0) while bfs.qsize() > 0: src = bfs.get() for k in range(len(new_array)): if new_array[src][k] > 0 and visited[k] == 0: visited[k] = 1 bfs.put(k) path[k] = src if visited[len(new_array) - 1] == 0: break tmp = len(new_array) - 1 while tmp != 0: if min_flow > new_array[path[tmp]][tmp]: min_flow = new_array[path[tmp]][tmp] tmp = path[tmp] tmp = len(new_array) - 1 while tmp != 0: new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min_flow tmp = path[tmp] total = total + min_flow return total ================================================ FILE: algorithms/graph/maximum_flow_dfs.py ================================================ """ Maximum Flow via DFS Computes maximum flow in a network represented as an adjacency matrix, using DFS to find augmenting paths. Reference: https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm Complexity: Time: O(E * f) where f is the max flow value Space: O(V^2) """ from __future__ import annotations import copy import math def maximum_flow_dfs(adjacency_matrix: list[list[int]]) -> int: """Compute maximum flow using DFS augmenting paths. The source is the first vertex and the sink is the last vertex. Args: adjacency_matrix: n*n capacity matrix. Returns: The maximum flow value. Examples: >>> maximum_flow_dfs([[0, 10, 0], [0, 0, 10], [0, 0, 0]]) 10 """ new_array = copy.deepcopy(adjacency_matrix) total = 0 while True: min_flow = math.inf visited = [0] * len(new_array) path = [0] * len(new_array) stack: list[int] = [] visited[0] = 1 stack.append(0) while len(stack) > 0: src = stack.pop() for k in range(len(new_array)): if new_array[src][k] > 0 and visited[k] == 0: visited[k] = 1 stack.append(k) path[k] = src if visited[len(new_array) - 1] == 0: break tmp = len(new_array) - 1 while tmp != 0: if min_flow > new_array[path[tmp]][tmp]: min_flow = new_array[path[tmp]][tmp] tmp = path[tmp] tmp = len(new_array) - 1 while tmp != 0: new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min_flow tmp = path[tmp] total = total + min_flow return total ================================================ FILE: algorithms/graph/maze_search_bfs.py ================================================ """ Maze Search (BFS) Find the minimum number of steps from the top-left corner to the bottom-right corner of a grid. Only cells with value 1 may be traversed. Returns -1 if no path exists. Complexity: Time: O(M * N) Space: O(M * N) """ from __future__ import annotations from collections import deque def maze_search(maze: list[list[int]]) -> int: """Return the shortest path length in *maze*, or -1 if unreachable. Args: maze: 2D grid where 1 = passable, 0 = blocked. Returns: Minimum steps from (0,0) to (height-1, width-1), or -1. Examples: >>> maze_search([[1, 1], [1, 1]]) 2 >>> maze_search([[1, 0], [0, 1]]) -1 """ blocked, allowed = 0, 1 unvisited, visited = 0, 1 initial_x, initial_y = 0, 0 if maze[initial_x][initial_y] == blocked: return -1 directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] height, width = len(maze), len(maze[0]) target_x, target_y = height - 1, width - 1 queue = deque([(initial_x, initial_y, 0)]) is_visited = [[unvisited for _ in range(width)] for _ in range(height)] is_visited[initial_x][initial_y] = visited while queue: x, y, steps = queue.popleft() if x == target_x and y == target_y: return steps for dx, dy in directions: new_x = x + dx new_y = y + dy if not (0 <= new_x < height and 0 <= new_y < width): continue if maze[new_x][new_y] == allowed and is_visited[new_x][new_y] == unvisited: queue.append((new_x, new_y, steps + 1)) is_visited[new_x][new_y] = visited return -1 ================================================ FILE: algorithms/graph/maze_search_dfs.py ================================================ """ Maze Search (DFS) Find the shortest path from the top-left corner to the bottom-right corner of a grid using depth-first search with backtracking. Only cells with value 1 may be traversed. Returns -1 if no path exists. Complexity: Time: O(4^(M*N)) worst case (backtracking) Space: O(M * N) """ from __future__ import annotations def find_path(maze: list[list[int]]) -> int: """Return the shortest path length in *maze*, or -1 if unreachable. Args: maze: 2D grid where 1 = passable, 0 = blocked. Returns: Minimum steps from (0,0) to (height-1, width-1), or -1. Examples: >>> find_path([[1, 1], [1, 1]]) 2 """ cnt = _dfs(maze, 0, 0, 0, -1) return cnt def _dfs( maze: list[list[int]], i: int, j: int, depth: int, cnt: int, ) -> int: """Recursive DFS helper for maze search. Args: maze: The grid (modified temporarily during recursion). i: Current row. j: Current column. depth: Current path length. cnt: Best path length found so far (-1 = none). Returns: Updated best path length. """ directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] row = len(maze) col = len(maze[0]) if i == row - 1 and j == col - 1: if cnt == -1: cnt = depth else: if cnt > depth: cnt = depth return cnt maze[i][j] = 0 for k in range(len(directions)): nx_i = i + directions[k][0] nx_j = j + directions[k][1] if 0 <= nx_i < row and 0 <= nx_j < col and maze[nx_i][nx_j] == 1: cnt = _dfs(maze, nx_i, nx_j, depth + 1, cnt) maze[i][j] = 1 return cnt ================================================ FILE: algorithms/graph/minimum_spanning_tree.py ================================================ """ Minimum Spanning Tree (Kruskal's Algorithm) Finds the MST of an undirected graph using Kruskal's algorithm with a disjoint-set (union-find) data structure. Reference: https://en.wikipedia.org/wiki/Kruskal%27s_algorithm Complexity: Time: O(E log E) Space: O(V) """ from __future__ import annotations class Edge: """An edge of an undirected weighted graph.""" def __init__(self, source: int, target: int, weight: int) -> None: self.source = source self.target = target self.weight = weight class DisjointSet: """Union-Find data structure with path compression and union by size.""" def __init__(self, size: int) -> None: """Create *size* singleton sets. Args: size: Number of elements. """ self.parent = list(range(size)) self.size = [1] * size def merge_set(self, node1: int, node2: int) -> None: """Merge the sets containing *node1* and *node2*. Args: node1: First element. node2: Second element. """ node1 = self.find_set(node1) node2 = self.find_set(node2) if self.size[node1] < self.size[node2]: self.parent[node1] = node2 self.size[node2] += self.size[node1] else: self.parent[node2] = node1 self.size[node1] += self.size[node2] def find_set(self, node: int) -> int: """Return the root representative of the set containing *node*. Args: node: Element to look up. Returns: Root representative. """ if self.parent[node] != node: self.parent[node] = self.find_set(self.parent[node]) return self.parent[node] def kruskal(vertex_count: int, edges: list[Edge], forest: DisjointSet) -> int: """Return the total weight of the MST computed by Kruskal's algorithm. Args: vertex_count: Number of vertices. edges: List of weighted edges. forest: Disjoint-set instance for the vertices. Returns: Sum of weights in the minimum spanning tree. Examples: >>> e = [Edge(0, 1, 1), Edge(1, 2, 2), Edge(0, 2, 3)] >>> kruskal(3, e, DisjointSet(3)) 3 """ edges.sort(key=lambda edge: edge.weight) mst: list[Edge] = [] for edge in edges: set_u = forest.find_set(edge.source) set_v = forest.find_set(edge.target) if set_u != set_v: forest.merge_set(set_u, set_v) mst.append(edge) if len(mst) == vertex_count - 1: break return sum(edge.weight for edge in mst) ================================================ FILE: algorithms/graph/pacific_atlantic.py ================================================ """ Pacific Atlantic Water Flow Given an m*n matrix of heights, find all cells from which water can flow to both the Pacific (top / left edges) and Atlantic (bottom / right edges) oceans. Reference: https://leetcode.com/problems/pacific-atlantic-water-flow/ Complexity: Time: O(M * N) Space: O(M * N) """ from __future__ import annotations def pacific_atlantic(matrix: list[list[int]]) -> list[list[int]]: """Return coordinates where water can flow to both oceans. Args: matrix: Height map. Returns: List of [row, col] pairs. Examples: >>> pacific_atlantic([[1]]) [[0, 0]] """ n = len(matrix) if not n: return [] m = len(matrix[0]) if not m: return [] res: list[list[int]] = [] atlantic = [[False for _ in range(n)] for _ in range(m)] pacific = [[False for _ in range(n)] for _ in range(m)] for i in range(n): _dfs(pacific, matrix, float("-inf"), i, 0) _dfs(atlantic, matrix, float("-inf"), i, m - 1) for i in range(m): _dfs(pacific, matrix, float("-inf"), 0, i) _dfs(atlantic, matrix, float("-inf"), n - 1, i) for i in range(n): for j in range(m): if pacific[i][j] and atlantic[i][j]: res.append([i, j]) return res def _dfs( grid: list[list[bool]], matrix: list[list[int]], height: float, i: int, j: int, ) -> None: """Mark cells reachable from (i, j) flowing uphill. Args: grid: Reachability matrix (modified in place). matrix: Height map. height: Previous cell height. i: Row index. j: Column index. """ if i < 0 or i >= len(matrix) or j < 0 or j >= len(matrix[0]): return if grid[i][j] or matrix[i][j] < height: return grid[i][j] = True _dfs(grid, matrix, matrix[i][j], i - 1, j) _dfs(grid, matrix, matrix[i][j], i + 1, j) _dfs(grid, matrix, matrix[i][j], i, j - 1) _dfs(grid, matrix, matrix[i][j], i, j + 1) ================================================ FILE: algorithms/graph/path_between_two_vertices_in_digraph.py ================================================ """ Path Between Two Vertices in a Directed Graph Determines whether there is a directed path from a source vertex to a target vertex using DFS. Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations from collections import defaultdict class Graph: """A directed graph for reachability queries.""" def __init__(self, vertex_count: int) -> None: """Create a graph with *vertex_count* vertices. Args: vertex_count: Number of vertices. """ self.vertex_count = vertex_count self.graph: dict[int, list[int]] = defaultdict(list) self.has_path = False def add_edge(self, source: int, target: int) -> None: """Add a directed edge. Args: source: Source vertex. target: Target vertex. """ self.graph[source].append(target) def _dfs(self, source: int, target: int) -> None: """Run DFS to determine reachability. Args: source: Source vertex. target: Target vertex. """ visited = [False] * self.vertex_count self._dfs_util(visited, source, target) def _dfs_util(self, visited: list[bool], source: int, target: int) -> None: """Recursive DFS helper. Args: visited: Visited flags. source: Current vertex. target: Destination vertex. """ visited[source] = True for i in self.graph[source]: if target in self.graph[source]: self.has_path = True return if not visited[i]: self._dfs_util(visited, source, i) def is_reachable(self, source: int, target: int) -> bool: """Return True if *target* is reachable from *source*. Args: source: Source vertex. target: Target vertex. Returns: True if a directed path exists. Examples: >>> g = Graph(2); g.add_edge(0, 1); g.is_reachable(0, 1) True """ self.has_path = False self._dfs(source, target) return self.has_path ================================================ FILE: algorithms/graph/prims_minimum_spanning.py ================================================ """ Prim's Minimum Spanning Tree Computes the weight of a minimum spanning tree for a connected weighted undirected graph using a priority queue. Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm Complexity: Time: O(E log V) Space: O(V + E) """ from __future__ import annotations import heapq from typing import Any def prims_minimum_spanning( graph_used: dict[Any, list[list[int | Any]]], ) -> int: """Return the total weight of the MST using Prim's algorithm. Args: graph_used: Adjacency list as ``{node: [[weight, neighbour], ...]}``. Returns: Sum of edge weights in the minimum spanning tree. Examples: >>> prims_minimum_spanning({1: [[1, 2]], 2: [[1, 1]]}) 1 """ vis: list[Any] = [] heap: list[list[int | Any]] = [[0, 1]] prim: set[Any] = set() mincost = 0 while len(heap) > 0: cost, node = heapq.heappop(heap) if node in vis: continue mincost += cost prim.add(node) vis.append(node) for distance, adjacent in graph_used[node]: if adjacent not in vis: heapq.heappush(heap, [distance, adjacent]) return mincost ================================================ FILE: algorithms/graph/satisfiability.py ================================================ """ 2-SAT Satisfiability Given a formula in conjunctive normal form (2-CNF), finds an assignment of True/False values that satisfies all clauses, or reports that no solution exists. Reference: https://en.wikipedia.org/wiki/2-satisfiability Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations from typing import Any def _dfs_transposed( vertex: Any, graph: dict[Any, list[Any]], order: list[Any], visited: dict[Any, bool], ) -> None: """DFS on the transposed graph, recording finish order. Args: vertex: Current vertex. graph: Transposed graph adjacency list. order: Finish order (appended to). visited: Visited flags. """ visited[vertex] = True for adjacent in graph[vertex]: if not visited[adjacent]: _dfs_transposed(adjacent, graph, order, visited) order.append(vertex) def _dfs( vertex: Any, current_comp: int, vertex_scc: dict[Any, int], graph: dict[Any, list[Any]], visited: dict[Any, bool], ) -> None: """DFS assigning SCC labels. Args: vertex: Current vertex. current_comp: Current component label. vertex_scc: SCC mapping (modified in place). graph: Graph adjacency list. visited: Visited flags. """ visited[vertex] = True vertex_scc[vertex] = current_comp for adjacent in graph[vertex]: if not visited[adjacent]: _dfs(adjacent, current_comp, vertex_scc, graph, visited) def _add_edge(graph: dict[Any, list[Any]], vertex_from: Any, vertex_to: Any) -> None: """Add a directed edge. Args: graph: Adjacency list (modified in place). vertex_from: Source vertex. vertex_to: Target vertex. """ if vertex_from not in graph: graph[vertex_from] = [] graph[vertex_from].append(vertex_to) def _scc(graph: dict[Any, list[Any]]) -> dict[Any, int]: """Compute SCCs using Kosaraju's algorithm. Args: graph: Directed graph adjacency list. Returns: Mapping from vertex to its SCC index. """ order: list[Any] = [] visited = {vertex: False for vertex in graph} graph_transposed: dict[Any, list[Any]] = {vertex: [] for vertex in graph} for source, neighbours in graph.items(): for target in neighbours: _add_edge(graph_transposed, target, source) for vertex in graph: if not visited[vertex]: _dfs_transposed(vertex, graph_transposed, order, visited) visited = {vertex: False for vertex in graph} vertex_scc: dict[Any, int] = {} current_comp = 0 for vertex in reversed(order): if not visited[vertex]: _dfs(vertex, current_comp, vertex_scc, graph, visited) current_comp += 1 return vertex_scc def _build_graph( formula: list[tuple[tuple[str, bool], tuple[str, bool]]], ) -> dict[tuple[str, bool], list[tuple[str, bool]]]: """Build the implication graph from a 2-CNF formula. Args: formula: List of clauses, each a pair of literals ``(name, is_negated)``. Returns: Implication graph as an adjacency list. """ graph: dict[tuple[str, bool], list[tuple[str, bool]]] = {} for clause in formula: for lit, _ in clause: for neg in [False, True]: graph[(lit, neg)] = [] for (a_lit, a_neg), (b_lit, b_neg) in formula: _add_edge(graph, (a_lit, a_neg), (b_lit, not b_neg)) _add_edge(graph, (b_lit, b_neg), (a_lit, not a_neg)) return graph def solve_sat( formula: list[tuple[tuple[str, bool], tuple[str, bool]]], ) -> dict[str, bool] | None: """Solve a 2-SAT formula. Args: formula: List of clauses in 2-CNF. Returns: A satisfying assignment as ``{variable: value}`` or None if unsatisfiable. Examples: >>> solve_sat([(('x', False), ('y', False)), (('x', True), ('y', True))]) {'x': False, 'y': False} """ graph = _build_graph(formula) vertex_scc = _scc(graph) for var, _ in graph: if vertex_scc[(var, False)] == vertex_scc[(var, True)]: return None comp_repr: dict[int, tuple[str, bool]] = {} for vertex in graph: if vertex_scc[vertex] not in comp_repr: comp_repr[vertex_scc[vertex]] = vertex comp_value: dict[int, bool] = {} components = sorted(vertex_scc.values()) for comp in components: if comp not in comp_value: comp_value[comp] = False lit, neg = comp_repr[comp] comp_value[vertex_scc[(lit, not neg)]] = True value = {var: comp_value[vertex_scc[(var, False)]] for var, _ in graph} return value ================================================ FILE: algorithms/graph/shortest_distance_from_all_buildings.py ================================================ """ Shortest Distance from All Buildings Given a 2D grid with buildings (1), empty land (0) and obstacles (2), find the empty land with the smallest total distance to all buildings. Reference: https://leetcode.com/problems/shortest-distance-from-all-buildings/ Complexity: Time: O(B * M * N) where B is the number of buildings Space: O(M * N) """ from __future__ import annotations from collections import deque def shortest_distance(grid: list[list[int]]) -> int: """Return the minimum total distance from an empty cell to all buildings. Args: grid: 2D grid (0 = empty, 1 = building, 2 = obstacle). Returns: Minimum sum of distances, or -1 if impossible. Examples: >>> shortest_distance([[1, 0, 1]]) 2 """ if not grid or not grid[0]: return -1 matrix = [[[0, 0] for _ in range(len(grid[0]))] for _ in range(len(grid))] count = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == 1: _bfs(grid, matrix, i, j, count) count += 1 res = float("inf") for i in range(len(matrix)): for j in range(len(matrix[0])): if matrix[i][j][1] == count: res = min(res, matrix[i][j][0]) return res if res != float("inf") else -1 def _bfs( grid: list[list[int]], matrix: list[list[list[int]]], i: int, j: int, count: int, ) -> None: """BFS from building at (i, j), updating *matrix* distances. Args: grid: The original grid. matrix: Accumulator for [total_distance, visit_count]. i: Row of the building. j: Column of the building. count: Number of buildings visited so far. """ q: deque[tuple[int, int, int]] = deque([(i, j, 0)]) while q: i, j, step = q.popleft() for k, col in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]: if ( 0 <= k < len(grid) and 0 <= col < len(grid[0]) and matrix[k][col][1] == count and grid[k][col] == 0 ): matrix[k][col][0] += step + 1 matrix[k][col][1] = count + 1 q.append((k, col, step + 1)) ================================================ FILE: algorithms/graph/strongly_connected_components_kosaraju.py ================================================ """ Strongly Connected Components (Kosaraju's Algorithm) Counts the number of strongly connected components in a directed graph using two DFS passes. Reference: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm Complexity: Time: O(V + E) Space: O(V + E) """ from __future__ import annotations class Kosaraju: """Kosaraju's algorithm for counting SCCs.""" def dfs( self, i: int, vertices: int, adj: list[list[int]], visited: list[int], stk: list[int], ) -> None: """DFS that records vertices in finish-time order. Args: i: Current vertex. vertices: Number of vertices. adj: Adjacency list. visited: Visited flags (-1 = unvisited). stk: Stack recording finish order. """ visited[i] = 1 for x in adj[i]: if visited[x] == -1: self.dfs(x, vertices, adj, visited, stk) stk.append(i) def kosaraju(self, vertices: int, adj: list[list[int]]) -> int: """Return the number of strongly connected components. Args: vertices: Number of vertices. adj: Adjacency list. Returns: Count of SCCs. Examples: >>> Kosaraju().kosaraju(3, [[1], [2], [0]]) 1 """ stk: list[int] = [] visited = [-1] * (vertices + 1) for i in range(vertices): if visited[i] == -1: self.dfs(i, vertices, adj, visited, stk) stk.reverse() res = stk.copy() ans = 0 visited1 = [-1] * (vertices + 1) adj1: list[list[int]] = [[] for _ in range(vertices)] for i in range(len(adj)): for x in adj[i]: adj1[x].append(i) for i in range(len(res)): if visited1[res[i]] == -1: ans += 1 self.dfs(res[i], vertices, adj1, visited1, stk) return ans ================================================ FILE: algorithms/graph/sudoku_solver.py ================================================ """ Sudoku Solver (DFS / Backtracking) Solves a Sudoku puzzle using constraint propagation and depth-first search with backtracking, starting from the cell with the fewest possible values. Reference: https://leetcode.com/problems/sudoku-solver/ Complexity: Time: O(9^(empty cells)) worst case Space: O(N^2) """ from __future__ import annotations class Sudoku: """A Sudoku board solver.""" def __init__( self, board: list[list[str]], row: int, col: int, ) -> None: """Initialise the solver with the given board. Args: board: 2D list of digits or '.' for empty cells. row: Number of rows. col: Number of columns. """ self.board = board self.row = row self.col = col self.val = self._possible_values() def _possible_values(self) -> dict[tuple[int, int], list[str]]: """Compute possible values for each empty cell. Returns: Mapping from (row, col) to list of candidate digits. """ a = "123456789" d: dict[tuple[str, int] | tuple[int, int], list[str]] = {} val: dict[tuple[int, int], list[str]] = {} for i in range(self.row): for j in range(self.col): ele = self.board[i][j] if ele != ".": d[("r", i)] = d.get(("r", i), []) + [ele] d[("c", j)] = d.get(("c", j), []) + [ele] d[(i // 3, j // 3)] = d.get((i // 3, j // 3), []) + [ele] else: val[(i, j)] = [] for i, j in val: inval = ( d.get(("r", i), []) + d.get(("c", j), []) + d.get((i / 3, j / 3), []) ) val[(i, j)] = [n for n in a if n not in inval] return val def solve(self) -> bool: """Attempt to solve the board in place. Returns: True if a solution was found. """ if len(self.val) == 0: return True kee = min(self.val.keys(), key=lambda x: len(self.val[x])) nums = self.val[kee] for n in nums: update: dict[tuple[int, int], str | list[str]] = {kee: self.val[kee]} if self._valid_one(n, kee, update) and self.solve(): return True self._undo(kee, update) return False def _valid_one( self, n: str, kee: tuple[int, int], update: dict[tuple[int, int], str | list[str]], ) -> bool: """Place digit *n* at *kee* and propagate constraints. Args: n: Digit to place. kee: (row, col) coordinate. update: Undo log (modified in place). Returns: True if placement is valid. """ self.board[kee[0]][kee[1]] = n del self.val[kee] i, j = kee for ind in list(self.val.keys()): if n in self.val[ind] and ( ind[0] == i or ind[1] == j or (ind[0] / 3, ind[1] / 3) == (i / 3, j / 3) ): update[ind] = n self.val[ind].remove(n) if len(self.val[ind]) == 0: return False return True def _undo( self, kee: tuple[int, int], update: dict[tuple[int, int], str | list[str]], ) -> None: """Revert the placement at *kee* using *update*. Args: kee: (row, col) coordinate. update: Undo log. """ self.board[kee[0]][kee[1]] = "." for k in update: if k not in self.val: self.val[k] = update[k] else: self.val[k].append(update[k]) def __str__(self) -> str: """Return a string representation of the board. Returns: Formatted board string. """ resp = "" for i in range(self.row): for j in range(self.col): resp += f" {self.board[i][j]} " resp += "\n" return resp ================================================ FILE: algorithms/graph/tarjan.py ================================================ """ Tarjan's Strongly Connected Components Algorithm Finds all strongly connected components in a directed graph. Reference: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations from algorithms.graph.graph import DirectedGraph class Tarjan: """Find SCCs via Tarjan's algorithm.""" def __init__(self, dict_graph: dict[str, list[str]]) -> None: """Build the graph and compute all SCCs. Args: dict_graph: Adjacency dict ``{vertex: [neighbours]}``. """ self.graph = DirectedGraph(dict_graph) self.index = 0 self.stack: list = [] for vertex in self.graph.nodes: vertex.index = None self.sccs: list[list] = [] for vertex in self.graph.nodes: if vertex.index is None: self._strongconnect(vertex, self.sccs) def _strongconnect(self, vertex: object, sccs: list[list]) -> None: """Process *vertex* and discover its SCC. Args: vertex: Current vertex node. sccs: Accumulated list of SCCs. """ vertex.index = self.index vertex.lowlink = self.index self.index += 1 self.stack.append(vertex) vertex.on_stack = True for adjacent in self.graph.adjacency_list[vertex]: if adjacent.index is None: self._strongconnect(adjacent, sccs) vertex.lowlink = min(vertex.lowlink, adjacent.lowlink) elif adjacent.on_stack: vertex.lowlink = min(vertex.lowlink, adjacent.index) if vertex.lowlink == vertex.index: scc: list = [] while True: adjacent = self.stack.pop() adjacent.on_stack = False scc.append(adjacent) if adjacent == vertex: break scc.sort() sccs.append(scc) ================================================ FILE: algorithms/graph/topological_sort_bfs.py ================================================ """ Topological Sort (Kahn's Algorithm / BFS) Computes a topological ordering of a directed acyclic graph. Raises ValueError when a cycle is detected. Reference: https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm Complexity: Time: O(V + E) Space: O(V + E) """ from __future__ import annotations from collections import defaultdict, deque def topological_sort(vertices: int, edges: list[tuple[int, int]]) -> list[int]: """Return a topological ordering of the vertices. Args: vertices: Number of vertices (labelled 0 .. vertices-1). edges: Directed edges as (u, v) meaning u -> v. Returns: List of vertices in topological order. Raises: ValueError: If the graph contains a cycle. Examples: >>> topological_sort(3, [(0, 1), (1, 2)]) [0, 1, 2] """ graph: dict[int, list[int]] = defaultdict(list) in_degree = [0] * vertices for u, v in edges: graph[u].append(v) in_degree[v] += 1 queue: deque[int] = deque() for i in range(vertices): if in_degree[i] == 0: queue.append(i) sorted_order: list[int] = [] processed = 0 while queue: node = queue.popleft() sorted_order.append(node) processed += 1 for neighbor in graph[node]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) if processed != vertices: raise ValueError("Cycle detected, topological sort failed") return sorted_order ================================================ FILE: algorithms/graph/topological_sort_dfs.py ================================================ """ Topological Sort Topological sort produces a linear ordering of vertices in a directed acyclic graph (DAG) such that for every directed edge (u, v), vertex u comes before v. Two implementations are provided: one recursive (DFS-based) and one iterative. Reference: https://en.wikipedia.org/wiki/Topological_sorting Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations _GRAY, _BLACK = 0, 1 def top_sort_recursive(graph: dict[str, list[str]]) -> list[str]: """Return a topological ordering of *graph* using recursive DFS. Args: graph: Adjacency-list representation of a directed graph. Returns: A list of vertices in topological order. Raises: ValueError: If the graph contains a cycle. Examples: >>> top_sort_recursive({'a': ['b'], 'b': []}) ['b', 'a'] """ order: list[str] = [] enter = set(graph) state: dict[str, int] = {} def _dfs(node: str) -> None: state[node] = _GRAY for neighbour in graph.get(node, ()): neighbour_state = state.get(neighbour) if neighbour_state == _GRAY: raise ValueError("cycle") if neighbour_state == _BLACK: continue enter.discard(neighbour) _dfs(neighbour) order.append(node) state[node] = _BLACK while enter: _dfs(enter.pop()) return order def top_sort(graph: dict[str, list[str]]) -> list[str]: """Return a topological ordering of *graph* using an iterative approach. Args: graph: Adjacency-list representation of a directed graph. Returns: A list of vertices in topological order. Raises: ValueError: If the graph contains a cycle. Examples: >>> top_sort({'a': ['b'], 'b': []}) ['b', 'a'] """ order: list[str] = [] enter = set(graph) state: dict[str, int] = {} def _is_ready(node: str) -> bool: neighbours = graph.get(node, ()) if len(neighbours) == 0: return True for neighbour in neighbours: neighbour_state = state.get(neighbour) if neighbour_state == _GRAY: raise ValueError("cycle") if neighbour_state != _BLACK: return False return True while enter: node = enter.pop() stack: list[str] = [] while True: state[node] = _GRAY stack.append(node) for neighbour in graph.get(node, ()): neighbour_state = state.get(neighbour) if neighbour_state == _GRAY: raise ValueError("cycle") if neighbour_state == _BLACK: continue enter.discard(neighbour) stack.append(neighbour) while stack and _is_ready(stack[-1]): node = stack.pop() order.append(node) state[node] = _BLACK if len(stack) == 0: break node = stack.pop() return order ================================================ FILE: algorithms/graph/transitive_closure_dfs.py ================================================ """ Transitive Closure via DFS Computes the transitive closure of a directed graph using depth-first search. Reference: https://en.wikipedia.org/wiki/Transitive_closure#In_graph_theory Complexity: Time: O(V * (V + E)) Space: O(V^2) """ from __future__ import annotations class Graph: """A directed graph for transitive closure computation.""" def __init__(self, vertices: int) -> None: """Create a graph with *vertices* vertices. Args: vertices: Number of vertices. """ self.vertex_count = vertices self.graph: dict[int, list[int]] = {} self.closure = [[0 for _ in range(vertices)] for _ in range(vertices)] def add_edge(self, source: int, target: int) -> None: """Add a directed edge. Args: source: Source vertex. target: Target vertex. """ if source in self.graph: self.graph[source].append(target) else: self.graph[source] = [target] def _dfs_util(self, source: int, target: int) -> None: """Recursive DFS marking reachability from *source* through *target*. Args: source: Origin vertex. target: Current vertex being explored. """ self.closure[source][target] = 1 for adjacent in self.graph[target]: if self.closure[source][adjacent] == 0: self._dfs_util(source, adjacent) def transitive_closure(self) -> list[list[int]]: """Compute and return the transitive closure matrix. Returns: An n*n matrix where entry [i][j] is 1 if j is reachable from i. Examples: >>> g = Graph(2); g.add_edge(0, 1); g.transitive_closure() [[1, 1], [0, 1]] """ for i in range(self.vertex_count): self._dfs_util(i, i) return self.closure ================================================ FILE: algorithms/graph/traversal.py ================================================ """ Graph Traversal Algorithms Provides DFS and BFS traversal of a graph represented as an adjacency dictionary. Complexity: Time: O(V + E) Space: O(V) """ from __future__ import annotations from collections import deque from typing import Any def dfs_traverse(graph: dict[Any, list[Any]], start: Any) -> set[Any]: """Traverse the graph from *start* using iterative DFS. Args: graph: Adjacency list. start: Starting node. Returns: Set of visited nodes. Examples: >>> sorted(dfs_traverse({'a': ['b'], 'b': []}, 'a')) ['a', 'b'] """ visited: set[Any] = set() stack = [start] while stack: node = stack.pop() if node not in visited: visited.add(node) for next_node in graph[node]: if next_node not in visited: stack.append(next_node) return visited def bfs_traverse(graph: dict[Any, list[Any]], start: Any) -> set[Any]: """Traverse the graph from *start* using BFS. Args: graph: Adjacency list. start: Starting node. Returns: Set of visited nodes. Examples: >>> sorted(bfs_traverse({'a': ['b'], 'b': []}, 'a')) ['a', 'b'] """ visited: set[Any] = set() queue = deque([start]) while queue: node = queue.popleft() if node not in visited: visited.add(node) for next_node in graph[node]: if next_node not in visited: queue.append(next_node) return visited def dfs_traverse_recursive( graph: dict[Any, list[Any]], start: Any, visited: set[Any] | None = None, ) -> set[Any]: """Traverse the graph from *start* using recursive DFS. Args: graph: Adjacency list. start: Starting node. visited: Already-visited set (internal use). Returns: Set of visited nodes. Examples: >>> sorted(dfs_traverse_recursive({'a': ['b'], 'b': []}, 'a')) ['a', 'b'] """ if visited is None: visited = set() visited.add(start) for next_node in graph[start]: if next_node not in visited: dfs_traverse_recursive(graph, next_node, visited) return visited ================================================ FILE: algorithms/graph/walls_and_gates.py ================================================ """ Walls and Gates Fill each empty room (INF) with the distance to its nearest gate (0). Walls are represented by -1. Reference: https://leetcode.com/problems/walls-and-gates/ Complexity: Time: O(M * N) Space: O(M * N) recursion stack """ from __future__ import annotations def walls_and_gates(rooms: list[list[int]]) -> None: """Fill *rooms* in place with distances to nearest gates. Args: rooms: 2D grid (-1 = wall, 0 = gate, INF = empty room). Examples: >>> r = [[float('inf'), 0]]; walls_and_gates(r); r [[1, 0]] """ for i in range(len(rooms)): for j in range(len(rooms[0])): if rooms[i][j] == 0: _dfs(rooms, i, j, 0) def _dfs(rooms: list[list[int]], i: int, j: int, depth: int) -> None: """Recursive DFS from a gate, updating room distances. Args: rooms: The grid (modified in place). i: Row index. j: Column index. depth: Current distance from the gate. """ if i < 0 or i >= len(rooms) or j < 0 or j >= len(rooms[0]): return if rooms[i][j] < depth: return rooms[i][j] = depth _dfs(rooms, i + 1, j, depth + 1) _dfs(rooms, i - 1, j, depth + 1) _dfs(rooms, i, j + 1, depth + 1) _dfs(rooms, i, j - 1, depth + 1) ================================================ FILE: algorithms/graph/word_ladder.py ================================================ """ Word Ladder (Bidirectional BFS) Given two words and a dictionary, find the length of the shortest transformation sequence where only one letter changes at each step and every intermediate word must exist in the dictionary. Reference: https://leetcode.com/problems/word-ladder/ Complexity: Time: O(N * L^2) where N = size of word list, L = word length Space: O(N * L) """ from __future__ import annotations from collections.abc import Iterator def ladder_length(begin_word: str, end_word: str, word_list: list[str]) -> int: """Return the shortest transformation length, or -1 if impossible. Args: begin_word: Starting word. end_word: Target word. word_list: Allowed intermediate words. Returns: Length of the shortest transformation sequence, or -1. Examples: >>> ladder_length('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log']) 5 """ if len(begin_word) != len(end_word): return -1 if begin_word == end_word: return 0 if sum(c1 != c2 for c1, c2 in zip(begin_word, end_word, strict=False)) == 1: return 1 begin_set: set[str] = set() end_set: set[str] = set() begin_set.add(begin_word) end_set.add(end_word) result = 2 while begin_set and end_set: if len(begin_set) > len(end_set): begin_set, end_set = end_set, begin_set next_begin_set: set[str] = set() for word in begin_set: for ladder_word in _word_range(word): if ladder_word in end_set: return result if ladder_word in word_list: next_begin_set.add(ladder_word) word_list.remove(ladder_word) begin_set = next_begin_set result += 1 return -1 def _word_range(word: str) -> Iterator[str]: """Yield all words that differ from *word* by exactly one letter. Args: word: The source word. Yields: Words with a single character changed. """ for ind in range(len(word)): temp = word[ind] for c in [chr(x) for x in range(ord("a"), ord("z") + 1)]: if c != temp: yield word[:ind] + c + word[ind + 1 :] ================================================ FILE: algorithms/greedy/__init__.py ================================================ from .gale_shapley import gale_shapley from .max_contiguous_subsequence_sum import max_contiguous_subsequence_sum __all__ = [ "gale_shapley", "max_contiguous_subsequence_sum", ] ================================================ FILE: algorithms/greedy/gale_shapley.py ================================================ """ Gale-Shapley Stable Matching Solves the stable matching (stable marriage) problem. Given N men and N women with ranked preferences, produces a stable matching where no pair would prefer each other over their current partners. Reference: https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations def gale_shapley( men: dict[str, list[str]], women: dict[str, list[str]], ) -> dict[str, str]: """Find a stable matching between men and women. Args: men: Mapping of each man to his preference list of women (highest to lowest). women: Mapping of each woman to her preference list of men (highest to lowest). Returns: A dict mapping each man to his matched woman. Examples: >>> men = {"M1": ["W1", "W2"], "M2": ["W1", "W2"]} >>> women = {"W1": ["M2", "M1"], "W2": ["M1", "M2"]} >>> sorted(gale_shapley(men, women).items()) [('M1', 'W2'), ('M2', 'W1')] """ men_available: list[str] = list(men.keys()) married: dict[str, str] = {} proposal_counts: dict[str, int] = {man: 0 for man in men} while men_available: man = men_available.pop(0) woman = men[man][proposal_counts[man]] proposal_counts[man] += 1 if woman not in married: married[woman] = man else: current_partner = married[woman] if women[woman].index(man) < women[woman].index(current_partner): married[woman] = man men_available.append(current_partner) else: men_available.append(man) return {man: woman for woman, man in married.items()} ================================================ FILE: algorithms/greedy/max_contiguous_subsequence_sum.py ================================================ """ Maximum Contiguous Subsequence Sum (Kadane's Algorithm) Finds the maximum sum of a contiguous sub-array within a one-dimensional array of numbers. The algorithm is greedy / dynamic-programming hybrid. Reference: https://en.wikipedia.org/wiki/Maximum_subarray_problem Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def max_contiguous_subsequence_sum(arr: list[int]) -> int: """Return the maximum contiguous subsequence sum. The contiguous subsequence must contain at least one element. Returns 0 for an empty array. Args: arr: A list of integers. Returns: The maximum contiguous subsequence sum. Examples: >>> max_contiguous_subsequence_sum([-2, 3, 8, -1, 4]) 14 >>> max_contiguous_subsequence_sum([-1, -3, -4]) -1 >>> max_contiguous_subsequence_sum([]) 0 """ if not arr: return 0 max_sum = arr[0] current_sum = 0 for value in arr: if current_sum + value < value: current_sum = value else: current_sum += value max_sum = max(max_sum, current_sum) return max_sum ================================================ FILE: algorithms/heap/__init__.py ================================================ """Heap-based algorithm implementations.""" from __future__ import annotations from algorithms.data_structures.heap import AbstractHeap, BinaryHeap from .k_closest_points import k_closest from .merge_sorted_k_lists import ListNode, merge_k_lists from .skyline import get_skyline from .sliding_window_max import max_sliding_window __all__ = [ "AbstractHeap", "BinaryHeap", "ListNode", "get_skyline", "k_closest", "max_sliding_window", "merge_k_lists", ] ================================================ FILE: algorithms/heap/k_closest_points.py ================================================ """ K Closest Points to Origin Given a list of points, find the k closest to the origin using a max heap of size k. For each subsequent point, replace the heap root if the new point is closer. Reference: https://leetcode.com/problems/k-closest-points-to-origin/ Complexity: Time: O(k + (n - k) log k) Space: O(k) """ from __future__ import annotations from heapq import heapify, heappushpop def k_closest( points: list[tuple[int, int]], k: int, origin: tuple[int, int] = (0, 0), ) -> list[tuple[int, int]]: """Find the k closest points to the origin. Args: points: List of (x, y) coordinate tuples. k: Number of closest points to return. origin: The reference point, defaults to (0, 0). Returns: List of the k closest points. Examples: >>> k_closest([(1, 0), (-1, 0), (2, 3)], 2) [(-1, 0), (1, 0)] """ heap = [(-_distance(p, origin), p) for p in points[:k]] heapify(heap) for point in points[k:]: dist = _distance(point, origin) heappushpop(heap, (-dist, point)) return [point for _, point in heap] def _distance(point: tuple[int, int], origin: tuple[int, int] = (0, 0)) -> int: """Compute squared Euclidean distance from point to origin. Args: point: The (x, y) coordinate. origin: The reference point. Returns: Squared Euclidean distance. """ return (point[0] - origin[0]) ** 2 + (point[1] - origin[1]) ** 2 ================================================ FILE: algorithms/heap/merge_sorted_k_lists.py ================================================ """ Merge K Sorted Linked Lists Merge k sorted linked lists into one sorted linked list using a heap for efficient minimum extraction. Reference: https://leetcode.com/problems/merge-k-sorted-lists/ Complexity: Time: O(n log k) where n is total elements and k is number of lists Space: O(k) """ from __future__ import annotations from heapq import heapify, heappop, heapreplace class ListNode: """Singly linked list node. Args: val: The node value. """ def __init__(self, val: int) -> None: self.val = val self.next: ListNode | None = None def merge_k_lists(lists: list[ListNode | None]) -> ListNode | None: """Merge k sorted linked lists into a single sorted linked list. Args: lists: A list of head nodes of sorted linked lists. Returns: Head of the merged sorted linked list, or None if all are empty. Examples: >>> n1 = ListNode(1) >>> n2 = ListNode(2) >>> result = merge_k_lists([n1, n2]) >>> result.val 1 """ dummy = node = ListNode(0) heap: list[tuple[int, int, ListNode]] = [] for idx, head in enumerate(lists): if head: heap.append((head.val, idx, head)) heapify(heap) while heap: val, idx, n_val = heap[0] if n_val.next is None: heappop(heap) else: heapreplace(heap, (n_val.next.val, idx, n_val.next)) node.next = n_val node = node.next return dummy.next ================================================ FILE: algorithms/heap/skyline.py ================================================ """ Skyline Problem Given building triplets [left, right, height], compute the skyline contour as a list of key points using a heap-based sweep line approach. Reference: https://leetcode.com/problems/the-skyline-problem/ Complexity: Time: O(n log n) Space: O(n) """ from __future__ import annotations import heapq def get_skyline(lrh: list[list[int]]) -> list[list[int]]: """Compute the skyline from a list of buildings. Args: lrh: List of [left, right, height] building triplets, sorted by left coordinate. Returns: List of [x, height] key points defining the skyline. Examples: >>> get_skyline([[2, 9, 10], [3, 7, 15]]) [[2, 10], [3, 15], [7, 10], [9, 0]] """ skyline: list[list[int]] = [] live: list[list[int]] = [] i, n = 0, len(lrh) while i < n or live: if not live or i < n and lrh[i][0] <= -live[0][1]: x = lrh[i][0] while i < n and lrh[i][0] == x: heapq.heappush(live, [-lrh[i][2], -lrh[i][1]]) i += 1 else: x = -live[0][1] while live and -live[0][1] <= x: heapq.heappop(live) height = len(live) and -live[0][0] if not skyline or height != skyline[-1][1]: skyline += [[x, height]] return skyline ================================================ FILE: algorithms/heap/sliding_window_max.py ================================================ """ Sliding Window Maximum (Heap-based) Given an array and a window size k, find the maximum element in each sliding window using a deque that maintains decreasing order of values. Reference: https://leetcode.com/problems/sliding-window-maximum/ Complexity: Time: O(n) Space: O(k) """ from __future__ import annotations import collections def max_sliding_window(nums: list[int], k: int) -> list[int]: """Find the maximum in each sliding window of size k. Args: nums: Input array of integers. k: Window size. Returns: List of maximum values for each window position. Examples: >>> max_sliding_window([1, 3, -1, -3, 5, 3, 6, 7], 3) [3, 3, 5, 5, 6, 7] """ if not nums: return nums queue: collections.deque[int] = collections.deque() result: list[int] = [] for num in nums: if len(queue) < k: queue.append(num) else: result.append(max(queue)) queue.popleft() queue.append(num) result.append(max(queue)) return result ================================================ FILE: algorithms/linked_list/__init__.py ================================================ """Linked list algorithm implementations.""" from algorithms.data_structures.linked_list import ( DoublyLinkedListNode, SinglyLinkedListNode, ) from algorithms.linked_list.add_two_numbers import ( add_two_numbers, convert_to_list, convert_to_str, ) from algorithms.linked_list.copy_random_pointer import ( RandomListNode, copy_random_pointer_v1, copy_random_pointer_v2, ) from algorithms.linked_list.delete_node import delete_node from algorithms.linked_list.first_cyclic_node import first_cyclic_node from algorithms.linked_list.intersection import intersection from algorithms.linked_list.is_cyclic import is_cyclic from algorithms.linked_list.is_palindrome import ( is_palindrome, is_palindrome_dict, is_palindrome_stack, ) from algorithms.linked_list.is_sorted import is_sorted from algorithms.linked_list.kth_to_last import ( kth_to_last, kth_to_last_dict, kth_to_last_eval, ) from algorithms.linked_list.merge_two_list import ( merge_two_list, merge_two_list_recur, ) from algorithms.linked_list.partition import partition from algorithms.linked_list.remove_duplicates import ( remove_dups, remove_dups_wothout_set, ) from algorithms.linked_list.remove_range import remove_range from algorithms.linked_list.reverse import ( reverse_list, reverse_list_recursive, ) from algorithms.linked_list.rotate_list import rotate_right from algorithms.linked_list.swap_in_pairs import swap_pairs __all__ = [ "add_two_numbers", "convert_to_list", "convert_to_str", "RandomListNode", "copy_random_pointer_v1", "copy_random_pointer_v2", "delete_node", "first_cyclic_node", "intersection", "is_cyclic", "is_palindrome", "is_palindrome_dict", "is_palindrome_stack", "is_sorted", "kth_to_last", "kth_to_last_dict", "kth_to_last_eval", "DoublyLinkedListNode", "SinglyLinkedListNode", "merge_two_list", "merge_two_list_recur", "partition", "remove_dups", "remove_dups_wothout_set", "remove_range", "reverse_list", "reverse_list_recursive", "rotate_right", "swap_pairs", ] ================================================ FILE: algorithms/linked_list/add_two_numbers.py ================================================ """ Add Two Numbers (Linked List) Given two non-empty linked lists representing two non-negative integers with digits stored in reverse order, add the two numbers and return the sum as a linked list. Reference: https://leetcode.com/problems/add-two-numbers/ Complexity: Time: O(max(m, n)) Space: O(max(m, n)) """ from __future__ import annotations class Node: def __init__(self, x: int) -> None: self.val = x self.next: Node | None = None def add_two_numbers(left: Node, right: Node) -> Node: """Add two numbers represented as reversed linked lists. Args: left: Head of the first number's linked list. right: Head of the second number's linked list. Returns: Head of the resulting sum linked list. Examples: >>> # (2 -> 4 -> 3) + (5 -> 6 -> 4) = (7 -> 0 -> 8) >>> l1 = Node(2); l1.next = Node(4); l1.next.next = Node(3) >>> l2 = Node(5); l2.next = Node(6); l2.next.next = Node(4) >>> convert_to_str(add_two_numbers(l1, l2)) '708' """ head = Node(0) current = head carry = 0 while left or right: carry //= 10 if left: carry += left.val left = left.next if right: carry += right.val right = right.next current.next = Node(carry % 10) current = current.next if carry // 10 == 1: current.next = Node(1) return head.next def convert_to_list(number: int) -> Node | None: """Convert a non-negative integer into a reversed linked list. Args: number: A non-negative integer to convert. Returns: Head of the reversed linked list, or None if number is negative. Examples: >>> convert_to_str(convert_to_list(112)) '211' """ if number < 0: return None head = Node(0) current = head remainder = number % 10 quotient = number // 10 while quotient != 0: current.next = Node(remainder) current = current.next remainder = quotient % 10 quotient //= 10 current.next = Node(remainder) return head.next def convert_to_str(node: Node | None) -> str: """Convert a linked list of digits to a string. Args: node: Head of the linked list. Returns: String representation of the linked list values. Examples: >>> n = Node(2); n.next = Node(4); n.next.next = Node(3) >>> convert_to_str(n) '243' """ result = "" while node: result += str(node.val) node = node.next return result ================================================ FILE: algorithms/linked_list/copy_random_pointer.py ================================================ """ Copy List with Random Pointer Given a linked list where each node contains an additional random pointer that could point to any node in the list or null, return a deep copy of the list. Reference: https://leetcode.com/problems/copy-list-with-random-pointer/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from collections import defaultdict class RandomListNode: """Node with next and random pointers for deep-copy problem.""" def __init__(self, label: int) -> None: self.label = label self.next: RandomListNode | None = None self.random: RandomListNode | None = None def copy_random_pointer_v1(head: RandomListNode | None) -> RandomListNode | None: """Deep-copy a linked list with random pointers using a dictionary. Args: head: Head of the original list. Returns: Head of the deep-copied list. Examples: >>> node = RandomListNode(1) >>> node.random = node >>> copied = copy_random_pointer_v1(node) >>> copied.label == 1 and copied.random is copied True """ node_map: dict[RandomListNode, RandomListNode] = {} current = head while current: node_map[current] = RandomListNode(current.label) current = current.next current = head while current: node_map[current].next = node_map.get(current.next) node_map[current].random = node_map.get(current.random) current = current.next return node_map.get(head) def copy_random_pointer_v2(head: RandomListNode | None) -> RandomListNode | None: """Deep-copy a linked list with random pointers using defaultdict. Args: head: Head of the original list. Returns: Head of the deep-copied list. Examples: >>> node = RandomListNode(1) >>> node.random = node >>> copied = copy_random_pointer_v2(node) >>> copied.label == 1 and copied.random is copied True """ copy: defaultdict[RandomListNode | None, RandomListNode | None] = defaultdict( lambda: RandomListNode(0) ) copy[None] = None node = head while node: copy[node].label = node.label copy[node].next = copy[node.next] copy[node].random = copy[node.random] node = node.next return copy[head] ================================================ FILE: algorithms/linked_list/delete_node.py ================================================ """ Delete Node in a Linked List Given only access to a node (not the tail) in a singly linked list, delete that node by copying the next node's value and skipping over it. Reference: https://leetcode.com/problems/delete-node-in-a-linked-list/ Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, x: int) -> None: self.val = x self.next: Node | None = None def delete_node(node: Node | None) -> None: """Delete the given node from a singly linked list in-place. The node must not be the tail node. The deletion is performed by copying the value from the next node and then skipping the next node. Args: node: The node to delete (must not be None or the tail). Raises: ValueError: If node is None or is the tail node. Examples: >>> head = Node(1); head.next = Node(2); head.next.next = Node(3) >>> delete_node(head.next) >>> head.next.val 3 """ if node is None or node.next is None: raise ValueError node.val = node.next.val node.next = node.next.next ================================================ FILE: algorithms/linked_list/first_cyclic_node.py ================================================ """ First Cyclic Node Given a linked list, find the first node of a cycle in it using Floyd's cycle-finding algorithm (Tortoise and Hare). Reference: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, x: object) -> None: self.val = x self.next: Node | None = None def first_cyclic_node(head: Node | None) -> Node | None: """Find the first node of a cycle in the linked list. Args: head: Head of the linked list. Returns: The first node in the cycle, or None if there is no cycle. Examples: >>> a = Node(1); b = Node(2); c = Node(3) >>> a.next = b; b.next = c; c.next = b >>> first_cyclic_node(a).val 2 """ runner = walker = head while runner and runner.next: runner = runner.next.next walker = walker.next if runner is walker: break if runner is None or runner.next is None: return None walker = head while runner is not walker: runner, walker = runner.next, walker.next return runner ================================================ FILE: algorithms/linked_list/intersection.py ================================================ """ Intersection of Two Linked Lists Given two singly linked lists that converge at some node, find and return the intersecting node. The node identity (not value) is the unique identifier. Reference: https://leetcode.com/problems/intersection-of-two-linked-lists/ Complexity: Time: O(m + n) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, val: object = None) -> None: self.val = val self.next: Node | None = None def intersection(h1: Node, h2: Node) -> Node | None: """Find the intersection node of two linked lists. Args: h1: Head of the first linked list. h2: Head of the second linked list. Returns: The intersecting node, or None if the lists do not intersect. Examples: >>> shared = Node(7) >>> a = Node(1); a.next = shared >>> b = Node(2); b.next = shared >>> intersection(a, b).val 7 """ count = 0 flag = None h1_orig = h1 h2_orig = h2 while h1 or h2: count += 1 if not flag and (h1.next is None or h2.next is None): flag = (count, h1.next, h2.next) if h1: h1 = h1.next if h2: h2 = h2.next long_len = count short_len = flag[0] if flag[1] is None: shorter = h1_orig longer = h2_orig elif flag[2] is None: shorter = h2_orig longer = h1_orig while longer and shorter: while long_len > short_len: longer = longer.next long_len -= 1 if longer == shorter: return longer else: longer = longer.next shorter = shorter.next return None ================================================ FILE: algorithms/linked_list/is_cyclic.py ================================================ """ Linked List Cycle Detection Given a linked list, determine if it has a cycle using Floyd's Tortoise and Hare algorithm without extra space. Reference: https://leetcode.com/problems/linked-list-cycle/ Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, x: object) -> None: self.val = x self.next: Node | None = None def is_cyclic(head: Node | None) -> bool: """Determine whether a linked list contains a cycle. Args: head: Head of the linked list. Returns: True if the list has a cycle, False otherwise. Examples: >>> a = Node(1); b = Node(2); a.next = b; b.next = a >>> is_cyclic(a) True >>> c = Node(3); c.next = Node(4) >>> is_cyclic(c) False """ if not head: return False runner = head walker = head while runner.next and runner.next.next: runner = runner.next.next walker = walker.next if runner == walker: return True return False ================================================ FILE: algorithms/linked_list/is_palindrome.py ================================================ """ Palindrome Linked List Determine whether a singly linked list is a palindrome. Three approaches are provided: reverse-half, stack-based, and dictionary-based. Reference: https://leetcode.com/problems/palindrome-linked-list/ Complexity (reverse-half): Time: O(n) Space: O(1) """ from __future__ import annotations def is_palindrome(head: object | None) -> bool: """Check if a linked list is a palindrome by reversing the second half. Args: head: Head node of the linked list (must have .val and .next attrs). Returns: True if the list is a palindrome, False otherwise. Examples: >>> is_palindrome(None) True """ if not head: return True fast, slow = head.next, head while fast and fast.next: fast = fast.next.next slow = slow.next second = slow.next slow.next = None node = None while second: nxt = second.next second.next = node node = second second = nxt while node: if node.val != head.val: return False node = node.next head = head.next return True def is_palindrome_stack(head: object | None) -> bool: """Check if a linked list is a palindrome using a stack. Args: head: Head node of the linked list. Returns: True if the list is a palindrome, False otherwise. Examples: >>> is_palindrome_stack(None) True """ if not head or not head.next: return True slow = fast = current = head while fast and fast.next: fast, slow = fast.next.next, slow.next stack = [slow.val] while slow.next: slow = slow.next stack.append(slow.val) while stack: if stack.pop() != current.val: return False current = current.next return True def is_palindrome_dict(head: object | None) -> bool: """Check if a linked list is a palindrome using a dictionary of positions. Builds a dictionary mapping each value to its list of positions, then verifies that positions are symmetric around the center. Args: head: Head node of the linked list. Returns: True if the list is a palindrome, False otherwise. Examples: >>> is_palindrome_dict(None) True """ if not head or not head.next: return True positions: dict[object, list[int]] = {} pos = 0 current = head while current: if current.val in positions: positions[current.val].append(pos) else: positions[current.val] = [pos] current = current.next pos += 1 checksum = pos - 1 middle = 0 for indices in positions.values(): if len(indices) % 2 != 0: middle += 1 else: for step, i in enumerate(range(len(indices))): if indices[i] + indices[len(indices) - 1 - step] != checksum: return False if middle > 1: return False return True ================================================ FILE: algorithms/linked_list/is_sorted.py ================================================ """ Is Sorted Linked List Given a linked list, determine whether the list is sorted in non-decreasing order. An empty list is considered sorted. Reference: https://en.wikipedia.org/wiki/Linked_list Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def is_sorted(head: object | None) -> bool: """Check if a linked list is sorted in non-decreasing order. Args: head: Head node of the linked list (must have .val and .next attrs). Returns: True if the list is sorted or empty, False otherwise. Examples: >>> is_sorted(None) True """ if not head: return True current = head while current.next: if current.val > current.next.val: return False current = current.next return True ================================================ FILE: algorithms/linked_list/kth_to_last.py ================================================ """ Kth to Last Element Find the kth to last element of a singly linked list. Three approaches are provided: eval-based, dictionary-based, and two-pointer iterative. Reference: https://en.wikipedia.org/wiki/Linked_list Complexity (two-pointer): Time: O(n) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, val: object = None) -> None: self.val = val self.next: Node | None = None def kth_to_last_eval(head: Node, k: int) -> Node | bool: """Find the kth to last element using a safe iterative loop. Args: head: Head of the linked list. k: Position from the end (1-indexed). Returns: The kth to last node, or False if k is invalid. Examples: >>> a = Node(1); b = Node(2); a.next = b >>> kth_to_last_eval(a, 1).val 2 """ if not isinstance(k, int) or not head.val: return False while head: seeker = head for _ in range(k): if seeker is None: return False seeker = seeker.next if seeker is None: return head head = head.next return False def kth_to_last_dict(head: Node | None, k: int) -> Node | bool: """Find the kth to last element using a dictionary. Args: head: Head of the linked list. k: Position from the end (1-indexed). Returns: The kth to last node, or False if k is invalid. Examples: >>> a = Node(1); b = Node(2); a.next = b >>> kth_to_last_dict(a, 1).val 2 """ if not (head and k > -1): return False index_map: dict[int, Node] = {} count = 0 while head: index_map[count] = head head = head.next count += 1 return len(index_map) - k in index_map and index_map[len(index_map) - k] def kth_to_last(head: Node | None, k: int) -> Node | bool: """Find the kth to last element using two pointers. Advances the first pointer k steps ahead, then moves both pointers together until the first pointer reaches the end. Args: head: Head of the linked list. k: Position from the end (1-indexed). Returns: The kth to last node, or False if the list is empty. Raises: IndexError: If k exceeds the length of the list. Examples: >>> a = Node(1); b = Node(2); a.next = b >>> kth_to_last(a, 1).val 2 """ if not (head or k > -1): return False ahead = head behind = head for _ in range(1, k + 1): if ahead is None: raise IndexError ahead = ahead.next while ahead: ahead = ahead.next behind = behind.next return behind ================================================ FILE: algorithms/linked_list/merge_two_list.py ================================================ """ Merge Two Sorted Lists Merge two sorted linked lists into a single sorted list by splicing together the nodes of the two input lists. Reference: https://leetcode.com/problems/merge-two-sorted-lists/ Complexity: Time: O(m + n) Space: O(1) iterative, O(m + n) recursive """ from __future__ import annotations class Node: def __init__(self, x: int) -> None: self.val = x self.next: Node | None = None def merge_two_list(l1: Node | None, l2: Node | None) -> Node | None: """Merge two sorted linked lists iteratively. Args: l1: Head of the first sorted list. l2: Head of the second sorted list. Returns: Head of the merged sorted list. Examples: >>> a = Node(1); a.next = Node(3) >>> b = Node(2); b.next = Node(4) >>> result = merge_two_list(a, b) >>> result.val 1 """ sentinel = current = Node(0) while l1 and l2: if l1.val < l2.val: current.next = l1 l1 = l1.next else: current.next = l2 l2 = l2.next current = current.next current.next = l1 or l2 return sentinel.next def merge_two_list_recur(l1: Node | None, l2: Node | None) -> Node | None: """Merge two sorted linked lists recursively. Args: l1: Head of the first sorted list. l2: Head of the second sorted list. Returns: Head of the merged sorted list. Examples: >>> a = Node(1); a.next = Node(3) >>> b = Node(2); b.next = Node(4) >>> result = merge_two_list_recur(a, b) >>> result.val 1 """ if not l1 or not l2: return l1 or l2 if l1.val < l2.val: l1.next = merge_two_list_recur(l1.next, l2) return l1 else: l2.next = merge_two_list_recur(l1, l2.next) return l2 ================================================ FILE: algorithms/linked_list/partition.py ================================================ """ Partition Linked List Partition a linked list around a value x so that all nodes with values less than x come before nodes with values greater than or equal to x. Reference: https://leetcode.com/problems/partition-list/ Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, val: object = None) -> None: self.val = int(val) self.next: Node | None = None def partition(head: Node | None, x: int) -> None: """Partition a linked list in-place around value x. Rearranges nodes so that all nodes with values less than x appear before nodes with values greater than or equal to x. Args: head: Head of the linked list. x: The partition value. Returns: None. The list is modified in-place. Examples: >>> a = Node(3); b = Node(5); c = Node(1) >>> a.next = b; b.next = c >>> partition(a, 5) """ left = None right = None prev = None current = head while current: if int(current.val) >= x: if not right: right = current else: if not left: left = current else: prev.next = current.next left.next = current left = current left.next = right if prev and prev.next is None: break prev = current current = current.next ================================================ FILE: algorithms/linked_list/remove_duplicates.py ================================================ """ Remove Duplicates from Linked List Remove duplicate values from an unsorted linked list. Two approaches are provided: hash-set-based (O(n) time, O(n) space) and runner technique (O(n^2) time, O(1) space). Reference: https://en.wikipedia.org/wiki/Linked_list Complexity (hash set): Time: O(n) Space: O(n) """ from __future__ import annotations class Node: def __init__(self, val: object = None) -> None: self.val = val self.next: Node | None = None def remove_dups(head: Node | None) -> None: """Remove duplicates from an unsorted linked list using a hash set. Args: head: Head of the linked list. Modified in-place. Returns: None. The list is modified in-place. Examples: >>> a = Node(1); b = Node(2); c = Node(1) >>> a.next = b; b.next = c >>> remove_dups(a) >>> a.next.val 2 """ seen: set[object] = set() prev = Node() while head: if head.val in seen: prev.next = head.next else: seen.add(head.val) prev = head head = head.next def remove_dups_wothout_set(head: Node | None) -> None: """Remove duplicates from an unsorted linked list without extra space. Uses a runner pointer to check for duplicates of each node value. Args: head: Head of the linked list. Modified in-place. Returns: None. The list is modified in-place. Examples: >>> a = Node(1); b = Node(2); c = Node(1) >>> a.next = b; b.next = c >>> remove_dups_wothout_set(a) >>> a.next.val 2 """ current = head while current: runner = current while runner.next: if runner.next.val == current.val: runner.next = runner.next.next else: runner = runner.next current = current.next ================================================ FILE: algorithms/linked_list/remove_range.py ================================================ """ Remove Range from Linked List Given a linked list and a start and end index, remove the elements at those indexes (inclusive) from the list. Reference: https://en.wikipedia.org/wiki/Linked_list Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def remove_range(head: object | None, start: int, end: int) -> object | None: """Remove nodes from index start to end (inclusive) from a linked list. Args: head: Head node of the linked list (must have .next attr). start: Starting index of the range to remove. end: Ending index of the range to remove (inclusive). Returns: The (possibly new) head of the modified list. Examples: >>> remove_range(None, 0, 0) is None True """ assert start <= end if start == 0: for _ in range(end + 1): if head is not None: head = head.next else: current = head for _ in range(start - 1): current = current.next for _ in range(end - start + 1): if current is not None and current.next is not None: current.next = current.next.next return head ================================================ FILE: algorithms/linked_list/reverse.py ================================================ """ Reverse Linked List Reverse a singly linked list. Both iterative and recursive solutions are provided. Reference: https://leetcode.com/problems/reverse-linked-list/ Complexity: Time: O(n) Space: O(1) iterative, O(n) recursive """ from __future__ import annotations def reverse_list(head: object | None) -> object | None: """Reverse a singly linked list iteratively. Args: head: Head node of the linked list (must have .next attr). Returns: The new head of the reversed list. Examples: >>> reverse_list(None) is None True """ if not head or not head.next: return head prev = None while head: current = head head = head.next current.next = prev prev = current return prev def reverse_list_recursive(head: object | None) -> object | None: """Reverse a singly linked list recursively. Args: head: Head node of the linked list (must have .next attr). Returns: The new head of the reversed list. Examples: >>> reverse_list_recursive(None) is None True """ if head is None or head.next is None: return head rest = head.next head.next = None reversed_rest = reverse_list_recursive(rest) rest.next = head return reversed_rest ================================================ FILE: algorithms/linked_list/rotate_list.py ================================================ """ Rotate List Given a linked list, rotate the list to the right by k places, where k is non-negative. Reference: https://leetcode.com/problems/rotate-list/ Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations def rotate_right(head: object | None, k: int) -> object | None: """Rotate a linked list to the right by k positions. Args: head: Head node of the linked list (must have .val and .next attrs). k: Number of positions to rotate right (non-negative). Returns: The new head of the rotated list. Examples: >>> rotate_right(None, 5) is None True """ if not head or not head.next: return head current = head length = 1 while current.next: current = current.next length += 1 current.next = head k = k % length for _ in range(length - k): current = current.next head = current.next current.next = None return head ================================================ FILE: algorithms/linked_list/swap_in_pairs.py ================================================ """ Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return the new head. Only node links are changed, not node values. Reference: https://leetcode.com/problems/swap-nodes-in-pairs/ Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations class Node: def __init__(self, x: int) -> None: self.val = x self.next: Node | None = None def swap_pairs(head: Node | None) -> Node | None: """Swap every two adjacent nodes in a linked list. Args: head: Head of the linked list. Returns: The new head after pairwise swapping. Examples: >>> a = Node(1); b = Node(2); a.next = b >>> result = swap_pairs(a) >>> result.val 2 """ if not head: return head sentinel = Node(0) sentinel.next = head current = sentinel while current.next and current.next.next: first = current.next second = current.next.next first.next = second.next current.next = second current.next.next = first current = current.next.next return sentinel.next ================================================ FILE: algorithms/map/__init__.py ================================================ """Map-based algorithm implementations.""" from __future__ import annotations from algorithms.data_structures.hash_table import HashTable, ResizableHashTable from algorithms.data_structures.separate_chaining_hash_table import ( SeparateChainingHashTable, ) from .is_anagram import is_anagram from .is_isomorphic import is_isomorphic from .longest_common_subsequence import max_common_sub_string from .longest_palindromic_subsequence import longest_palindromic_subsequence from .randomized_set import RandomizedSet from .valid_sudoku import is_valid_sudoku from .word_pattern import word_pattern __all__ = [ "HashTable", "ResizableHashTable", "is_anagram", "is_isomorphic", "is_valid_sudoku", "longest_palindromic_subsequence", "max_common_sub_string", "RandomizedSet", "SeparateChainingHashTable", "word_pattern", ] ================================================ FILE: algorithms/map/is_anagram.py ================================================ """ Is Anagram Determine whether two strings are anagrams of each other by comparing character frequency maps. Reference: https://leetcode.com/problems/valid-anagram/description/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def is_anagram(s: str, t: str) -> bool: """Check if string t is an anagram of string s. Args: s: First string. t: Second string. Returns: True if t is an anagram of s, False otherwise. Examples: >>> is_anagram("anagram", "nagaram") True >>> is_anagram("rat", "car") False """ freq_s: dict[str, int] = {} freq_t: dict[str, int] = {} for char in s: freq_s[char] = freq_s.get(char, 0) + 1 for char in t: freq_t[char] = freq_t.get(char, 0) + 1 return freq_s == freq_t ================================================ FILE: algorithms/map/is_isomorphic.py ================================================ """ Isomorphic Strings Determine if two strings are isomorphic. Two strings are isomorphic if characters in s can be mapped to characters in t while preserving order, with a one-to-one mapping. Reference: https://leetcode.com/problems/isomorphic-strings/description/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def is_isomorphic(s: str, t: str) -> bool: """Check if two strings are isomorphic. Args: s: Source string. t: Target string. Returns: True if s and t are isomorphic, False otherwise. Examples: >>> is_isomorphic("egg", "add") True >>> is_isomorphic("foo", "bar") False """ if len(s) != len(t): return False mapping: dict[str, str] = {} mapped_values: set[str] = set() for i in range(len(s)): if s[i] not in mapping: if t[i] in mapped_values: return False mapping[s[i]] = t[i] mapped_values.add(t[i]) else: if mapping[s[i]] != t[i]: return False return True ================================================ FILE: algorithms/map/longest_common_subsequence.py ================================================ """ Longest Common Substring Given two strings where the second contains all distinct characters, find the longest common substring using index mapping. Reference: https://en.wikipedia.org/wiki/Longest_common_substring_problem Complexity: Time: O(n log n) expected, O(n * m) worst case Space: O(n) """ from __future__ import annotations def max_common_sub_string(s1: str, s2: str) -> str: """Find the longest common substring between s1 and s2. Assumes s2 has all unique characters, enabling an index-based matching approach. Args: s1: First input string. s2: Second input string with all distinct characters. Returns: The longest common substring. Examples: >>> max_common_sub_string("abcdef", "acdbef") 'ef' """ char_index = {s2[i]: i for i in range(len(s2))} max_length = 0 best_substring = "" i = 0 while i < len(s1): if s1[i] in char_index: j = char_index[s1[i]] k = i while j < len(s2) and k < len(s1) and s1[k] == s2[j]: k += 1 j += 1 if k - i > max_length: max_length = k - i best_substring = s1[i:k] i = k else: i += 1 return best_substring ================================================ FILE: algorithms/map/longest_palindromic_subsequence.py ================================================ """ Longest Palindromic Substring Find the length of the longest palindromic substring using dynamic programming with two rolling arrays. Reference: https://en.wikipedia.org/wiki/Longest_palindromic_substring Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations def longest_palindromic_subsequence(s: str) -> int: """Return the length of the longest palindromic substring in s. Args: s: The input string. Returns: Length of the longest palindromic substring. Examples: >>> longest_palindromic_subsequence("babad") 3 >>> longest_palindromic_subsequence("cbbd") 2 """ length = len(s) previous_row = [0] * length current_row = [0] * length longest_length = 0 for end in range(length): for start in range(end + 1): if end - start <= 1: if s[start] == s[end]: current_row[start] = 1 span = end - start + 1 if longest_length < span: longest_length = span else: if s[start] == s[end] and previous_row[start + 1]: current_row[start] = 1 span = end - start + 1 if longest_length < span: longest_length = span previous_row = current_row current_row = [0] * length return longest_length ================================================ FILE: algorithms/map/randomized_set.py ================================================ """ Randomized Set Design a data structure that supports insert, remove, and getRandom in average O(1) time. Uses a list for random access and a dictionary for O(1) lookup/removal. Reference: https://leetcode.com/problems/insert-delete-getrandom-o1/ Complexity: Time: O(1) average for insert, remove, get_random Space: O(n) """ from __future__ import annotations import random class RandomizedSet: """A set supporting O(1) insert, remove, and random element access. Examples: >>> rs = RandomizedSet() >>> rs.insert(1) True >>> rs.insert(1) False >>> rs.remove(1) True """ def __init__(self) -> None: """Initialize the randomized set.""" self.nums: list[int] = [] self.idxs: dict[int, int] = {} def insert(self, val: int) -> bool: """Insert a value into the set. Args: val: Value to insert. Returns: True if the value was inserted, False if already present. """ if val not in self.idxs: self.nums.append(val) self.idxs[val] = len(self.nums) - 1 return True return False def remove(self, val: int) -> bool: """Remove a value from the set. Args: val: Value to remove. Returns: True if the value was removed, False if not present. """ if val in self.idxs: idx, last = self.idxs[val], self.nums[-1] self.nums[idx], self.idxs[last] = last, idx self.nums.pop() self.idxs.pop(val, 0) return True return False def get_random(self) -> int: """Return a random element from the set. Returns: A randomly chosen element. """ idx = random.randint(0, len(self.nums) - 1) return self.nums[idx] ================================================ FILE: algorithms/map/valid_sudoku.py ================================================ """ Valid Sudoku Determine if a partially filled 9x9 Sudoku board is valid. A board is valid if each row, column, and 3x3 sub-box contains no duplicate digits. Reference: https://leetcode.com/problems/valid-sudoku/ Complexity: Time: O(1) (board is always 9x9) Space: O(1) """ from __future__ import annotations def is_valid_sudoku(board: list[list[str]]) -> bool: """Check whether a Sudoku board configuration is valid. Args: board: 9x9 grid where empty cells are represented by '.'. Returns: True if the board is valid, False otherwise. Examples: >>> is_valid_sudoku([['.' for _ in range(9)] for _ in range(9)]) True """ seen: list[tuple[str, ...]] = [] for i, row in enumerate(board): for j, cell in enumerate(row): if cell != ".": seen += [(cell, j), (i, cell), (i // 3, j // 3, cell)] return len(seen) == len(set(seen)) ================================================ FILE: algorithms/map/word_pattern.py ================================================ """ Word Pattern Given a pattern and a string, determine if the string follows the same pattern via a bijection between pattern letters and words. Reference: https://leetcode.com/problems/word-pattern/description/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def word_pattern(pattern: str, string: str) -> bool: """Check if a string follows the given pattern. Args: pattern: A pattern string of lowercase letters. string: A space-separated string of words. Returns: True if the string follows the pattern, False otherwise. Examples: >>> word_pattern("abba", "dog cat cat dog") True >>> word_pattern("abba", "dog cat cat fish") False """ mapping: dict[str, str] = {} mapped_values: set[str] = set() words = string.split() if len(words) != len(pattern): return False for i in range(len(pattern)): if pattern[i] not in mapping: if words[i] in mapped_values: return False mapping[pattern[i]] = words[i] mapped_values.add(words[i]) else: if mapping[pattern[i]] != words[i]: return False return True ================================================ FILE: algorithms/math/__init__.py ================================================ """ Collection of mathematical algorithms and functions. """ from __future__ import annotations # Module-level imports for backward compatibility (tests use module.function syntax) from algorithms.math import ( chinese_remainder_theorem, # noqa: E402 fft, # noqa: E402 hailstone, # noqa: E402 modular_inverse, # type: ignore[no-redef] # noqa: E402 ) from algorithms.math.base_conversion import base_to_int, int_to_base from algorithms.math.combination import combination, combination_memo from algorithms.math.cosine_similarity import cosine_similarity from algorithms.math.decimal_to_binary_ip import ( decimal_to_binary_ip, decimal_to_binary_util, ) from algorithms.math.diffie_hellman_key_exchange import ( alice_private_key, alice_public_key, alice_shared_key, bob_private_key, bob_public_key, bob_shared_key, diffie_hellman_key_exchange, ) from algorithms.math.distance_between_two_points import distance_between_two_points from algorithms.math.euler_totient import euler_totient from algorithms.math.extended_gcd import extended_gcd from algorithms.math.factorial import factorial, factorial_recur from algorithms.math.find_order_simple import find_order from algorithms.math.find_primitive_root_simple import find_primitive_root from algorithms.math.gcd import gcd, gcd_bit, lcm, trailing_zero from algorithms.math.generate_strobogrammtic import ( gen_strobogrammatic, strobogrammatic_in_range, ) from algorithms.math.goldbach import goldbach, verify_goldbach from algorithms.math.is_strobogrammatic import is_strobogrammatic, is_strobogrammatic2 from algorithms.math.krishnamurthy_number import krishnamurthy_number from algorithms.math.linear_regression import linear_regression, r_squared, rmse from algorithms.math.magic_number import magic_number from algorithms.math.manhattan_distance import manhattan_distance from algorithms.math.modular_exponential import modular_exponential from algorithms.math.next_bigger import next_bigger from algorithms.math.next_perfect_square import find_next_square, find_next_square2 from algorithms.math.nth_digit import find_nth_digit from algorithms.math.num_digits import num_digits from algorithms.math.num_perfect_squares import num_perfect_squares from algorithms.math.power import power, power_recur from algorithms.math.prime_check import prime_check from algorithms.math.primes_sieve_of_eratosthenes import get_primes from algorithms.math.pythagoras import pythagoras from algorithms.math.rabin_miller import is_prime from algorithms.math.recursive_binomial_coefficient import ( recursive_binomial_coefficient, ) from algorithms.math.rsa import decrypt, encrypt, generate_key from algorithms.math.sqrt_precision_factor import square_root from algorithms.math.summing_digits import sum_dig_pow from algorithms.math.surface_area_of_torus import surface_area_of_torus __all__ = [ "base_to_int", "int_to_base", "chinese_remainder_theorem", "combination", "combination_memo", "cosine_similarity", "decimal_to_binary_ip", "decimal_to_binary_util", "alice_private_key", "alice_public_key", "alice_shared_key", "bob_private_key", "bob_public_key", "bob_shared_key", "diffie_hellman_key_exchange", "distance_between_two_points", "euler_totient", "extended_gcd", "factorial", "factorial_recur", "fft", "find_order", "find_primitive_root", "gcd", "gcd_bit", "lcm", "trailing_zero", "gen_strobogrammatic", "strobogrammatic_in_range", "hailstone", "is_strobogrammatic", "is_strobogrammatic2", "krishnamurthy_number", "magic_number", "modular_exponential", "modular_inverse", "next_bigger", "find_next_square", "find_next_square2", "find_nth_digit", "num_digits", "num_perfect_squares", "power", "power_recur", "prime_check", "get_primes", "pythagoras", "is_prime", "recursive_binomial_coefficient", "decrypt", "encrypt", "generate_key", "square_root", "sum_dig_pow", "surface_area_of_torus", "linear_regression", "r_squared", "rmse", "manhattan_distance", "goldbach", "verify_goldbach", ] ================================================ FILE: algorithms/math/base_conversion.py ================================================ """ Integer Base Conversion Convert integers between arbitrary bases (2-36). Supports conversion from integer to string representation in a given base, and vice versa. Reference: https://en.wikipedia.org/wiki/Positional_notation Complexity: Time: O(log_base(num)) for both directions Space: O(log_base(num)) """ from __future__ import annotations import string def int_to_base(num: int, base: int) -> str: """Convert a base-10 integer to a string in the given base. Args: num: The integer to convert. base: The target base (2-36). Returns: String representation of num in the given base. Examples: >>> int_to_base(5, 2) '101' >>> int_to_base(255, 16) 'FF' >>> int_to_base(0, 2) '0' """ is_negative = False if num == 0: return "0" if num < 0: is_negative = True num *= -1 digit = string.digits + string.ascii_uppercase res = "" while num > 0: res += digit[num % base] num //= base if is_negative: return "-" + res[::-1] return res[::-1] def base_to_int(str_to_convert: str, base: int) -> int: """Convert a string in a given base to a base-10 integer. Args: str_to_convert: The string representation of the number. base: The base of the input string (2-36). Returns: The base-10 integer value. Examples: >>> base_to_int('101', 2) 5 >>> base_to_int('FF', 16) 255 """ digit = {} for ind, char in enumerate(string.digits + string.ascii_uppercase): digit[char] = ind multiplier = 1 res = 0 for char in str_to_convert[::-1]: res += digit[char] * multiplier multiplier *= base return res ================================================ FILE: algorithms/math/chinese_remainder_theorem.py ================================================ """ Chinese Remainder Theorem Solves a system of simultaneous congruences using the Chinese Remainder Theorem. Given pairwise coprime moduli, finds the smallest positive integer satisfying all congruences. Reference: https://en.wikipedia.org/wiki/Chinese_remainder_theorem Complexity: Time: O(n * m) where n is the number of equations and m is the solution Space: O(1) """ from __future__ import annotations from algorithms.math.gcd import gcd def solve_chinese_remainder(nums: list[int], rems: list[int]) -> int: """Find the smallest x satisfying x % nums[i] == rems[i] for all i. Args: nums: List of pairwise coprime moduli, each greater than 1. rems: List of remainders corresponding to each modulus. Returns: The smallest positive integer satisfying all congruences. Raises: Exception: If inputs are invalid or moduli are not pairwise coprime. Examples: >>> solve_chinese_remainder([3, 7, 10], [2, 3, 3]) 143 """ if not len(nums) == len(rems): raise Exception("nums and rems should have equal length") if not len(nums) > 0: raise Exception("Lists nums and rems need to contain at least one element") for num in nums: if not num > 1: raise Exception("All numbers in nums needs to be > 1") if not _check_coprime(nums): raise Exception("All pairs of numbers in nums are not coprime") k = len(nums) x = 1 while True: i = 0 while i < k: if x % nums[i] != rems[i]: break i += 1 if i == k: return x x += 1 def _check_coprime(list_to_check: list[int]) -> bool: """Check whether all pairs of numbers in the list are coprime. Args: list_to_check: List of integers to check for pairwise coprimality. Returns: True if all pairs are coprime, False otherwise. """ for ind, num in enumerate(list_to_check): for num2 in list_to_check[ind + 1 :]: if gcd(num, num2) != 1: return False return True ================================================ FILE: algorithms/math/combination.py ================================================ """ Combinations (nCr) Calculate the number of ways to choose r items from n items (binomial coefficient) using recursive and memoized approaches. Reference: https://en.wikipedia.org/wiki/Combination Complexity: Time: O(2^n) naive recursive, O(n*r) memoized Space: O(n) recursive stack, O(n*r) memoized """ from __future__ import annotations def combination(n: int, r: int) -> int: """Calculate nCr using naive recursion. Args: n: Total number of items. r: Number of items to choose. Returns: The number of combinations. Examples: >>> combination(5, 2) 10 >>> combination(10, 5) 252 """ if n == r or r == 0: return 1 return combination(n - 1, r - 1) + combination(n - 1, r) def combination_memo(n: int, r: int) -> int: """Calculate nCr using memoization. Args: n: Total number of items. r: Number of items to choose. Returns: The number of combinations. Examples: >>> combination_memo(50, 10) 10272278170 """ memo: dict[tuple[int, int], int] = {} def _recur(n: int, r: int) -> int: if n == r or r == 0: return 1 if (n, r) not in memo: memo[(n, r)] = _recur(n - 1, r - 1) + _recur(n - 1, r) return memo[(n, r)] return _recur(n, r) ================================================ FILE: algorithms/math/cosine_similarity.py ================================================ """ Cosine Similarity Calculate the cosine similarity between two vectors, which measures the cosine of the angle between them. Values range from -1 (opposite) to 1 (identical direction). Reference: https://en.wikipedia.org/wiki/Cosine_similarity Complexity: Time: O(n) Space: O(1) """ from __future__ import annotations import math def _l2_distance(vec: list[float]) -> float: """Calculate the L2 (Euclidean) norm of a vector. Args: vec: Input vector as a list of numbers. Returns: The L2 norm of the vector. """ norm = 0.0 for element in vec: norm += element * element norm = math.sqrt(norm) return norm def cosine_similarity(vec1: list[float], vec2: list[float]) -> float: """Calculate cosine similarity between two vectors. Args: vec1: First vector. vec2: Second vector (must be same length as vec1). Returns: Cosine similarity value between -1 and 1. Raises: ValueError: If vectors have different lengths. Examples: >>> cosine_similarity([1, 1, 1], [1, 2, -1]) 0.4714045207910317 """ if len(vec1) != len(vec2): raise ValueError( "The two vectors must be the same length. Got shape " + str(len(vec1)) + " and " + str(len(vec2)) ) norm_a = _l2_distance(vec1) norm_b = _l2_distance(vec2) similarity = 0.0 for vec1_element, vec2_element in zip(vec1, vec2, strict=False): similarity += vec1_element * vec2_element similarity /= norm_a * norm_b return similarity ================================================ FILE: algorithms/math/decimal_to_binary_ip.py ================================================ """ Decimal to Binary IP Conversion Convert an IP address from dotted-decimal notation to its binary representation. Reference: https://en.wikipedia.org/wiki/IP_address Complexity: Time: O(1) (fixed 4 octets, 8 bits each) Space: O(1) """ from __future__ import annotations def decimal_to_binary_util(val: str) -> str: """Convert a single decimal octet (0-255) to an 8-bit binary string. Args: val: String representation of an octet value. Returns: 8-character binary string. Examples: >>> decimal_to_binary_util('192') '11000000' """ bits = [128, 64, 32, 16, 8, 4, 2, 1] val_int = int(val) binary_rep = "" for bit in bits: if val_int >= bit: binary_rep += str(1) val_int -= bit else: binary_rep += str(0) return binary_rep def decimal_to_binary_ip(ip: str) -> str: """Convert a dotted-decimal IP address to binary representation. Args: ip: IP address in dotted-decimal format (e.g., '192.168.0.1'). Returns: Binary representation with dot-separated octets. Examples: >>> decimal_to_binary_ip('192.168.0.1') '11000000.10101000.00000000.00000001' """ values = ip.split(".") binary_list = [] for val in values: binary_list.append(decimal_to_binary_util(val)) return ".".join(binary_list) ================================================ FILE: algorithms/math/diffie_hellman_key_exchange.py ================================================ """ Diffie-Hellman Key Exchange Implements the Diffie-Hellman key exchange protocol, which enables two parties to establish a shared secret over an insecure channel using discrete logarithm properties. Reference: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange Complexity: Time: O(p) for primitive root finding, O(log(p)) for key generation Space: O(p) for primitive root list """ from __future__ import annotations import math import secrets def _prime_check(num: int) -> bool: """Check whether a number is prime. Args: num: The integer to test. Returns: True if num is prime, False otherwise. """ if num <= 1: return False if num == 2 or num == 3: return True if num % 2 == 0 or num % 3 == 0: return False j = 5 while j * j <= num: if num % j == 0 or num % (j + 2) == 0: return False j += 6 return True def _find_order(a: int, n: int) -> int: """Find the multiplicative order of a modulo n. Args: a: The base integer. n: The modulus. Returns: The smallest positive k such that a^k = 1 (mod n), or -1 if none exists. """ if (a == 1) & (n == 1): return 1 if math.gcd(a, n) != 1: return -1 for i in range(1, n): if pow(a, i) % n == 1: return i return -1 def _euler_totient(n: int) -> int: """Compute Euler's totient function phi(n). Args: n: A positive integer. Returns: The number of integers from 1 to n that are coprime with n. """ result = n for i in range(2, int(n**0.5) + 1): if n % i == 0: while n % i == 0: n //= i result -= result // i if n > 1: result -= result // n return result def _find_primitive_root(n: int) -> list[int]: """Find all primitive roots of n. Args: n: A positive integer. Returns: List of all primitive roots of n, or empty list if none exist. """ if n == 1: return [0] phi = _euler_totient(n) p_root_list = [] for i in range(1, n): if math.gcd(i, n) != 1: continue order = _find_order(i, n) if order == phi: p_root_list.append(i) return p_root_list def alice_private_key(p: int) -> int: """Generate Alice's private key in range [1, p-1]. Args: p: A large prime number. Returns: A random private key. """ return secrets.randbelow(p - 1) + 1 def alice_public_key(a_pr_k: int, a: int, p: int) -> int: """Calculate Alice's public key. Args: a_pr_k: Alice's private key. a: The primitive root (generator). p: The prime modulus. Returns: Alice's public key. """ return pow(a, a_pr_k, p) def bob_private_key(p: int) -> int: """Generate Bob's private key in range [1, p-1]. Args: p: A large prime number. Returns: A random private key. """ return secrets.randbelow(p - 1) + 1 def bob_public_key(b_pr_k: int, a: int, p: int) -> int: """Calculate Bob's public key. Args: b_pr_k: Bob's private key. a: The primitive root (generator). p: The prime modulus. Returns: Bob's public key. """ return pow(a, b_pr_k, p) def alice_shared_key(b_pu_k: int, a_pr_k: int, p: int) -> int: """Calculate Alice's shared secret key. Args: b_pu_k: Bob's public key. a_pr_k: Alice's private key. p: The prime modulus. Returns: The shared secret key. """ return pow(b_pu_k, a_pr_k, p) def bob_shared_key(a_pu_k: int, b_pr_k: int, p: int) -> int: """Calculate Bob's shared secret key. Args: a_pu_k: Alice's public key. b_pr_k: Bob's private key. p: The prime modulus. Returns: The shared secret key. """ return pow(a_pu_k, b_pr_k, p) def diffie_hellman_key_exchange(a: int, p: int, option: int | None = None) -> bool: """Perform Diffie-Hellman key exchange and verify shared keys match. Args: a: The primitive root (generator). p: A large prime number. option: Unused, kept for API compatibility. Returns: True if both parties compute the same shared key, False if inputs are invalid. Examples: >>> diffie_hellman_key_exchange(3, 353) True """ if _prime_check(p) is False: return False try: p_root_list = _find_primitive_root(p) p_root_list.index(a) except ValueError: return False a_pr_k = alice_private_key(p) a_pu_k = alice_public_key(a_pr_k, a, p) b_pr_k = bob_private_key(p) b_pu_k = bob_public_key(b_pr_k, a, p) a_sh_k = alice_shared_key(b_pu_k, a_pr_k, p) b_sh_k = bob_shared_key(a_pu_k, b_pr_k, p) return a_sh_k == b_sh_k ================================================ FILE: algorithms/math/distance_between_two_points.py ================================================ """ Distance Between Two Points in 2D Space Calculate the Euclidean distance between two points using the distance formula derived from the Pythagorean theorem. Reference: https://en.wikipedia.org/wiki/Euclidean_distance Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations from math import sqrt def distance_between_two_points(x1: float, y1: float, x2: float, y2: float) -> float: """Calculate the Euclidean distance between two points in 2D space. Args: x1: x-coordinate of the first point. y1: y-coordinate of the first point. x2: x-coordinate of the second point. y2: y-coordinate of the second point. Returns: The Euclidean distance between the two points. Examples: >>> distance_between_two_points(0, 0, 3, 4) 5.0 >>> distance_between_two_points(-1, -1, 2, 3) 5.0 """ return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) ================================================ FILE: algorithms/math/euler_totient.py ================================================ """ Euler's Totient Function Compute Euler's totient function phi(n), which counts the number of integers from 1 to n inclusive that are coprime to n. Reference: https://en.wikipedia.org/wiki/Euler%27s_totient_function Complexity: Time: O(sqrt(n)) Space: O(1) """ from __future__ import annotations def euler_totient(n: int) -> int: """Compute Euler's totient function phi(n). Args: n: A positive integer. Returns: The count of integers in [1, n] coprime to n. Examples: >>> euler_totient(8) 4 >>> euler_totient(21) 12 """ result = n for i in range(2, int(n**0.5) + 1): if n % i == 0: while n % i == 0: n //= i result -= result // i if n > 1: result -= result // n return result ================================================ FILE: algorithms/math/extended_gcd.py ================================================ """ Extended Euclidean Algorithm Find coefficients s and t (Bezout's identity) such that: num1 * s + num2 * t = gcd(num1, num2). Reference: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm Complexity: Time: O(log(min(num1, num2))) Space: O(1) """ from __future__ import annotations def extended_gcd(num1: int, num2: int) -> tuple[int, int, int]: """Compute the extended GCD of two integers. Args: num1: First integer. num2: Second integer. Returns: A tuple (s, t, g) where num1 * s + num2 * t = g = gcd(num1, num2). Examples: >>> extended_gcd(8, 2) (0, 1, 2) >>> extended_gcd(13, 17) (0, 1, 17) """ old_s, s = 1, 0 old_t, t = 0, 1 old_r, r = num1, num2 while r != 0: quotient = old_r / r old_r, r = r, old_r - quotient * r old_s, s = s, old_s - quotient * s old_t, t = t, old_t - quotient * t return old_s, old_t, old_r ================================================ FILE: algorithms/math/factorial.py ================================================ """ Factorial Compute the factorial of a non-negative integer, with optional modular arithmetic support. Reference: https://en.wikipedia.org/wiki/Factorial Complexity: Time: O(n) Space: O(1) iterative, O(n) recursive """ from __future__ import annotations def factorial(n: int, mod: int | None = None) -> int: """Calculate n! iteratively, optionally modulo mod. Args: n: A non-negative integer. mod: Optional positive integer modulus. Returns: n! or n! % mod if mod is provided. Raises: ValueError: If n is negative or mod is not a positive integer. Examples: >>> factorial(5) 120 >>> factorial(10) 3628800 """ if not (isinstance(n, int) and n >= 0): raise ValueError("'n' must be a non-negative integer.") if mod is not None and not (isinstance(mod, int) and mod > 0): raise ValueError("'mod' must be a positive integer") result = 1 if n == 0: return 1 for i in range(2, n + 1): result *= i if mod: result %= mod return result def factorial_recur(n: int, mod: int | None = None) -> int: """Calculate n! recursively, optionally modulo mod. Args: n: A non-negative integer. mod: Optional positive integer modulus. Returns: n! or n! % mod if mod is provided. Raises: ValueError: If n is negative or mod is not a positive integer. Examples: >>> factorial_recur(5) 120 """ if not (isinstance(n, int) and n >= 0): raise ValueError("'n' must be a non-negative integer.") if mod is not None and not (isinstance(mod, int) and mod > 0): raise ValueError("'mod' must be a positive integer") if n == 0: return 1 result = n * factorial(n - 1, mod) if mod: result %= mod return result ================================================ FILE: algorithms/math/fft.py ================================================ """ Fast Fourier Transform (Cooley-Tukey) Compute the Discrete Fourier Transform of a sequence using the Cooley-Tukey radix-2 decimation-in-time algorithm. Input length must be a power of 2. Reference: https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Complexity: Time: O(n log n) Space: O(n log n) """ from __future__ import annotations from cmath import exp, pi def fft(x: list[complex]) -> list[complex]: """Compute the FFT of a sequence using the Cooley-Tukey algorithm. Args: x: Input array of complex values. Length must be a power of 2. Returns: The Discrete Fourier Transform of x. Examples: >>> fft([1.0, 1.0, 1.0, 1.0]) [(4+0j), 0j, 0j, 0j] """ n = len(x) if n == 1: return x even = fft(x[0::2]) odd = fft(x[1::2]) y = [0 for _ in range(n)] for k in range(n // 2): q = exp(-2j * pi * k / n) * odd[k] y[k] = even[k] + q y[k + n // 2] = even[k] - q return y ================================================ FILE: algorithms/math/find_order_simple.py ================================================ """ Multiplicative Order Find the multiplicative order of a modulo n, which is the smallest positive integer k such that a^k = 1 (mod n). Requires gcd(a, n) = 1. Reference: https://en.wikipedia.org/wiki/Multiplicative_order Complexity: Time: O(n log n) Space: O(1) """ from __future__ import annotations import math def find_order(a: int, n: int) -> int: """Find the multiplicative order of a modulo n. Args: a: The base integer. n: The modulus. Returns: The smallest positive k where a^k = 1 (mod n), or -1 if a and n are not coprime. Examples: >>> find_order(3, 7) 6 >>> find_order(1, 1) 1 """ if (a == 1) & (n == 1): return 1 if math.gcd(a, n) != 1: return -1 for i in range(1, n): if pow(a, i) % n == 1: return i return -1 ================================================ FILE: algorithms/math/find_primitive_root_simple.py ================================================ """ Primitive Root Finder Find all primitive roots of a positive integer n. A primitive root modulo n is an integer whose multiplicative order modulo n equals Euler's totient of n. Reference: https://en.wikipedia.org/wiki/Primitive_root_modulo_n Complexity: Time: O(n^2 log n) Space: O(n) """ from __future__ import annotations import math def _find_order(a: int, n: int) -> int: """Find the multiplicative order of a modulo n. Args: a: The base integer. n: The modulus. Returns: The smallest positive k where a^k = 1 (mod n), or -1 if none exists. """ if (a == 1) & (n == 1): return 1 if math.gcd(a, n) != 1: return -1 for i in range(1, n): if pow(a, i) % n == 1: return i return -1 def _euler_totient(n: int) -> int: """Compute Euler's totient function phi(n). Args: n: A positive integer. Returns: The count of integers in [1, n] coprime to n. """ result = n for i in range(2, int(n**0.5) + 1): if n % i == 0: while n % i == 0: n //= i result -= result // i if n > 1: result -= result // n return result def find_primitive_root(n: int) -> list[int]: """Find all primitive roots of n. Args: n: A positive integer. Returns: List of all primitive roots of n. Returns [0] for n=1, or an empty list if no primitive roots exist. Examples: >>> find_primitive_root(5) [2, 3] >>> find_primitive_root(1) [0] """ if n == 1: return [0] phi = _euler_totient(n) p_root_list = [] for i in range(1, n): if math.gcd(i, n) == 1: order = _find_order(i, n) if order == phi: p_root_list.append(i) return p_root_list ================================================ FILE: algorithms/math/gcd.py ================================================ """ Greatest Common Divisor and Least Common Multiple Compute the GCD and LCM of two integers using Euclid's algorithm and a bitwise variant. Reference: https://en.wikipedia.org/wiki/Euclidean_algorithm Complexity: Time: O(log(min(a, b))) for gcd, O(log(min(a, b))) for lcm Space: O(1) """ from __future__ import annotations def gcd(a: int, b: int) -> int: """Compute the greatest common divisor using Euclid's algorithm. Args: a: First integer (non-zero). b: Second integer (non-zero). Returns: The greatest common divisor of a and b. Raises: ValueError: If inputs are not integers or either is zero. Examples: >>> gcd(8, 12) 4 >>> gcd(13, 17) 1 """ a_int = isinstance(a, int) b_int = isinstance(b, int) a = abs(a) b = abs(b) if not (a_int or b_int): raise ValueError("Input arguments are not integers") if (a == 0) or (b == 0): raise ValueError("One or more input arguments equals zero") while b != 0: a, b = b, a % b return a def lcm(a: int, b: int) -> int: """Compute the lowest common multiple of two integers. Args: a: First integer (non-zero). b: Second integer (non-zero). Returns: The lowest common multiple of a and b. Examples: >>> lcm(8, 12) 24 """ return abs(a) * abs(b) / gcd(a, b) def trailing_zero(x: int) -> int: """Count the number of trailing zeros in the binary representation. Args: x: A positive integer. Returns: Number of trailing zero bits. Examples: >>> trailing_zero(34) 1 >>> trailing_zero(40) 3 """ count = 0 while x and not x & 1: count += 1 x >>= 1 return count def gcd_bit(a: int, b: int) -> int: """Compute GCD using the binary (Stein's) algorithm. Args: a: First non-negative integer. b: Second non-negative integer. Returns: The greatest common divisor of a and b. Examples: >>> gcd_bit(8, 12) 4 """ tza = trailing_zero(a) tzb = trailing_zero(b) a >>= tza b >>= tzb while b: if a < b: a, b = b, a a -= b a >>= trailing_zero(a) return a << min(tza, tzb) ================================================ FILE: algorithms/math/generate_strobogrammtic.py ================================================ """ Generate Strobogrammatic Numbers A strobogrammatic number looks the same when rotated 180 degrees. Generate all strobogrammatic numbers of a given length or count them within a range. Reference: https://en.wikipedia.org/wiki/Strobogrammatic_number Complexity: Time: O(5^(n/2)) for generation Space: O(5^(n/2)) """ from __future__ import annotations def gen_strobogrammatic(n: int) -> list[str]: """Generate all strobogrammatic numbers of length n. Args: n: The desired length of strobogrammatic numbers. Returns: List of strobogrammatic number strings. Examples: >>> gen_strobogrammatic(2) ['88', '11', '96', '69'] """ return _helper(n, n) def _helper(n: int, length: int) -> list[str]: """Recursively build strobogrammatic numbers of length n. Args: n: Remaining length to fill. length: Original target length. Returns: List of strobogrammatic number strings. """ if n == 0: return [""] if n == 1: return ["1", "0", "8"] middles = _helper(n - 2, length) result = [] for middle in middles: if n != length: result.append("0" + middle + "0") result.append("8" + middle + "8") result.append("1" + middle + "1") result.append("9" + middle + "6") result.append("6" + middle + "9") return result def strobogrammatic_in_range(low: str, high: str) -> int: """Count strobogrammatic numbers within the given range [low, high]. Args: low: Lower bound as a string. high: Upper bound as a string. Returns: Count of strobogrammatic numbers in the range. Examples: >>> strobogrammatic_in_range("10", "100") 4 """ res: list[str] = [] count = 0 low_len = len(low) high_len = len(high) for i in range(low_len, high_len + 1): res.extend(_helper2(i, i)) for perm in res: if len(perm) == low_len and int(perm) < int(low): continue if len(perm) == high_len and int(perm) > int(high): continue count += 1 return count def _helper2(n: int, length: int) -> list[str]: """Recursively build strobogrammatic numbers including leading zeros. Args: n: Remaining length to fill. length: Original target length. Returns: List of strobogrammatic number strings. """ if n == 0: return [""] if n == 1: return ["0", "8", "1"] mids = _helper(n - 2, length) res = [] for mid in mids: if n != length: res.append("0" + mid + "0") res.append("1" + mid + "1") res.append("6" + mid + "9") res.append("9" + mid + "6") res.append("8" + mid + "8") return res ================================================ FILE: algorithms/math/goldbach.py ================================================ """ Goldbach's Conjecture Every even integer greater than 2 can be expressed as the sum of two primes. This module provides a function to find such a pair of primes and a helper to verify the conjecture over a range. Reference: https://en.wikipedia.org/wiki/Goldbach%27s_conjecture Complexity: Time: O(n * sqrt(n)) per call (sieve could be used for batch queries) Space: O(1) per call """ from __future__ import annotations def _is_prime(n: int) -> bool: """Return ``True`` if *n* is a prime number. Examples: >>> _is_prime(7) True >>> _is_prime(10) False """ if n < 2: return False if n < 4: return True if n % 2 == 0 or n % 3 == 0: return False i = 5 while i * i <= n: if n % i == 0 or n % (i + 2) == 0: return False i += 6 return True def goldbach(n: int) -> tuple[int, int]: """Return two primes whose sum equals *n*. Args: n: An even integer greater than 2. Returns: A tuple ``(p, q)`` with ``p <= q`` and ``p + q == n`` where both ``p`` and ``q`` are prime. Raises: ValueError: If *n* is not an even integer greater than 2. Examples: >>> goldbach(4) (2, 2) >>> goldbach(28) (5, 23) >>> p, q = goldbach(100) >>> p + q == 100 and _is_prime(p) and _is_prime(q) True """ if n <= 2 or n % 2 != 0: msg = f"n must be an even integer greater than 2, got {n}" raise ValueError(msg) for i in range(2, n // 2 + 1): if _is_prime(i) and _is_prime(n - i): return (i, n - i) # Should never be reached if Goldbach's conjecture holds msg = f"no prime pair found for {n}" # pragma: no cover raise RuntimeError(msg) # pragma: no cover def verify_goldbach(limit: int) -> bool: """Verify Goldbach's conjecture for all even numbers from 4 to *limit*. Args: limit: Upper bound (inclusive) for verification. Returns: ``True`` if every even number in range can be expressed as a sum of two primes. Examples: >>> verify_goldbach(100) True >>> verify_goldbach(1000) True """ return all(goldbach(n) is not None for n in range(4, limit + 1, 2)) ================================================ FILE: algorithms/math/hailstone.py ================================================ """ Hailstone Sequence (Collatz Conjecture) Generate the hailstone sequence starting from n: if n is even, next is n/2; if n is odd, next is 3n + 1. The sequence ends when it reaches 1. Reference: https://en.wikipedia.org/wiki/Collatz_conjecture Complexity: Time: O(unknown) - conjectured to always terminate Space: O(sequence length) """ from __future__ import annotations def hailstone(n: int) -> list[int]: """Generate the hailstone sequence from n to 1. Args: n: The starting positive integer. Returns: The complete hailstone sequence from n down to 1. Examples: >>> hailstone(8) [8, 4, 2, 1] >>> hailstone(10) [10, 5, 16, 8, 4, 2, 1] """ sequence = [n] while n > 1: n = 3 * n + 1 if n % 2 != 0 else int(n / 2) sequence.append(n) return sequence ================================================ FILE: algorithms/math/is_strobogrammatic.py ================================================ """ Strobogrammatic Number Check Determine whether a number (as a string) is strobogrammatic, meaning it looks the same when rotated 180 degrees. Reference: https://en.wikipedia.org/wiki/Strobogrammatic_number Complexity: Time: O(n) where n is the length of the number string Space: O(1) for is_strobogrammatic, O(n) for is_strobogrammatic2 """ from __future__ import annotations def is_strobogrammatic(num: str) -> bool: """Check if a number string is strobogrammatic using two pointers. Args: num: String representation of the number. Returns: True if num is strobogrammatic, False otherwise. Examples: >>> is_strobogrammatic("69") True >>> is_strobogrammatic("14") False """ comb = "00 11 88 69 96" i = 0 j = len(num) - 1 while i <= j: if comb.find(num[i] + num[j]) == -1: return False i += 1 j -= 1 return True def is_strobogrammatic2(num: str) -> bool: """Check if a number string is strobogrammatic using string reversal. Args: num: String representation of the number. Returns: True if num is strobogrammatic, False otherwise. Examples: >>> is_strobogrammatic2("69") True >>> is_strobogrammatic2("14") False """ return num == num[::-1].replace("6", "#").replace("9", "6").replace("#", "9") ================================================ FILE: algorithms/math/krishnamurthy_number.py ================================================ """ Krishnamurthy Number A Krishnamurthy number is a number whose sum of the factorials of its digits equals the number itself (e.g., 145 = 1! + 4! + 5!). Reference: https://en.wikipedia.org/wiki/Factorion Complexity: Time: O(d * m) where d is number of digits and m is max digit value Space: O(1) """ from __future__ import annotations def _find_factorial(n: int) -> int: """Calculate the factorial of a non-negative integer. Args: n: A non-negative integer. Returns: The factorial of n. """ fact = 1 while n != 0: fact *= n n -= 1 return fact def krishnamurthy_number(n: int) -> bool: """Check if n is a Krishnamurthy number (factorion). Args: n: The integer to check. Returns: True if n is a Krishnamurthy number, False otherwise. Examples: >>> krishnamurthy_number(145) True >>> krishnamurthy_number(357) False """ if n == 0: return False sum_of_digits = 0 temp = n while temp != 0: sum_of_digits += _find_factorial(temp % 10) temp //= 10 return sum_of_digits == n ================================================ FILE: algorithms/math/linear_regression.py ================================================ """Simple linear regression — fit a line to (x, y) data. Computes the ordinary least-squares regression line y = mx + b without external libraries. Inspired by PR #871 (MakanFar). """ from __future__ import annotations import math def linear_regression(x: list[float], y: list[float]) -> tuple[float, float]: """Return (slope, intercept) for the best-fit line. >>> m, b = linear_regression([1, 2, 3, 4, 5], [2, 4, 5, 4, 5]) >>> round(m, 4) 0.6 >>> round(b, 4) 2.2 """ n = len(x) if n != len(y) or n < 2: msg = "x and y must have at least 2 equal-length elements" raise ValueError(msg) sum_x = sum(x) sum_y = sum(y) sum_xy = sum(xi * yi for xi, yi in zip(x, y, strict=False)) sum_x2 = sum(xi * xi for xi in x) denom = n * sum_x2 - sum_x * sum_x if denom == 0: msg = "Vertical line — slope is undefined" raise ValueError(msg) slope = (n * sum_xy - sum_x * sum_y) / denom intercept = (sum_y - slope * sum_x) / n return slope, intercept def r_squared(x: list[float], y: list[float]) -> float: """Return the R-squared (coefficient of determination) for the fit.""" slope, intercept = linear_regression(x, y) y_mean = sum(y) / len(y) ss_tot = sum((yi - y_mean) ** 2 for yi in y) ss_res = sum( (yi - (slope * xi + intercept)) ** 2 for xi, yi in zip(x, y, strict=False) ) if ss_tot == 0: return 1.0 return 1.0 - ss_res / ss_tot def rmse(x: list[float], y: list[float]) -> float: """Return the root mean squared error for the fit.""" slope, intercept = linear_regression(x, y) n = len(y) return math.sqrt( sum((yi - (slope * xi + intercept)) ** 2 for xi, yi in zip(x, y, strict=False)) / n ) ================================================ FILE: algorithms/math/magic_number.py ================================================ """ Magic Number A magic number is a number where recursively summing its digits eventually yields 1. For example, 199 -> 1+9+9=19 -> 1+9=10 -> 1+0=1. Reference: https://en.wikipedia.org/wiki/Digital_root Complexity: Time: O(log n) amortized Space: O(1) """ from __future__ import annotations def magic_number(n: int) -> bool: """Check if n is a magic number (digital root equals 1). Args: n: The integer to check. Returns: True if the digital root of n is 1, False otherwise. Examples: >>> magic_number(1234) True >>> magic_number(111) False """ total_sum = 0 while n > 0 or total_sum > 9: if n == 0: n = total_sum total_sum = 0 total_sum += n % 10 n //= 10 return total_sum == 1 ================================================ FILE: algorithms/math/manhattan_distance.py ================================================ """Manhattan distance — L1 distance between two points. Also known as taxicab distance or city-block distance, it is the sum of absolute differences of coordinates. Inspired by PR #877 (ChinZhengSheng). """ from __future__ import annotations def manhattan_distance(a: tuple[float, ...], b: tuple[float, ...]) -> float: """Return the Manhattan (L1) distance between points *a* and *b*. Works in any number of dimensions. >>> manhattan_distance((1, 2), (4, 6)) 7 >>> manhattan_distance((0, 0, 0), (1, 2, 3)) 6 """ return sum(abs(x - y) for x, y in zip(a, b, strict=False)) ================================================ FILE: algorithms/math/modular_exponential.py ================================================ """ Modular Exponentiation Compute (base ^ exponent) % mod efficiently using binary exponentiation (repeated squaring). Reference: https://en.wikipedia.org/wiki/Modular_exponentiation Complexity: Time: O(log exponent) Space: O(1) """ from __future__ import annotations def modular_exponential(base: int, exponent: int, mod: int) -> int: """Compute (base ^ exponent) % mod using binary exponentiation. Args: base: The base value. exponent: The exponent (must be non-negative). mod: The modulus. Returns: The result of (base ^ exponent) % mod. Raises: ValueError: If exponent is negative. Examples: >>> modular_exponential(5, 117, 19) 1 """ if exponent < 0: raise ValueError("Exponent must be positive.") base %= mod result = 1 while exponent > 0: if exponent & 1: result = (result * base) % mod exponent = exponent >> 1 base = (base * base) % mod return result ================================================ FILE: algorithms/math/modular_inverse.py ================================================ """ Modular Multiplicative Inverse Find x such that a * x = 1 (mod m) using the Extended Euclidean Algorithm. Requires a and m to be coprime. Reference: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse Complexity: Time: O(log(min(a, m))) Space: O(1) """ from __future__ import annotations def _extended_gcd(a: int, b: int) -> tuple[int, int, int]: """Compute the extended GCD of two integers. Args: a: First integer. b: Second integer. Returns: A tuple (s, t, g) where a * s + b * t = g = gcd(a, b). """ old_s, s = 1, 0 old_t, t = 0, 1 old_r, r = a, b while r != 0: quotient = old_r // r old_r, r = r, old_r - quotient * r old_s, s = s, old_s - quotient * s old_t, t = t, old_t - quotient * t return old_s, old_t, old_r def modular_inverse(a: int, m: int) -> int: """Find x such that a * x = 1 (mod m). Args: a: The integer to find the inverse of. m: The modulus (must be coprime with a). Returns: The modular multiplicative inverse of a modulo m. Raises: ValueError: If a and m are not coprime. Examples: >>> modular_inverse(2, 19) 10 """ s, _, g = _extended_gcd(a, m) if g != 1: raise ValueError("a and m must be coprime") return s % m ================================================ FILE: algorithms/math/next_bigger.py ================================================ """ Next Bigger Number with Same Digits Given a number, find the next higher number that uses the exact same set of digits. This is equivalent to finding the next permutation. Reference: https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order Complexity: Time: O(n) where n is the number of digits Space: O(n) """ from __future__ import annotations def next_bigger(num: int) -> int: """Find the next higher number with the exact same digits. Args: num: A positive integer. Returns: The next higher number with the same digits, or -1 if no such number exists. Examples: >>> next_bigger(38276) 38627 >>> next_bigger(99999) -1 """ digits = [int(i) for i in str(num)] idx = len(digits) - 1 while idx >= 1 and digits[idx - 1] >= digits[idx]: idx -= 1 if idx == 0: return -1 pivot = digits[idx - 1] swap_idx = len(digits) - 1 while pivot >= digits[swap_idx]: swap_idx -= 1 digits[swap_idx], digits[idx - 1] = digits[idx - 1], digits[swap_idx] digits[idx:] = digits[: idx - 1 : -1] return int("".join(str(x) for x in digits)) ================================================ FILE: algorithms/math/next_perfect_square.py ================================================ """ Next Perfect Square Given a number, find the next perfect square if the input is itself a perfect square. Otherwise, return -1. Reference: https://en.wikipedia.org/wiki/Square_number Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations def find_next_square(sq: float) -> float: """Find the next perfect square after sq. Args: sq: A non-negative number to check. Returns: The next perfect square if sq is a perfect square, otherwise -1. Examples: >>> find_next_square(121) 144 >>> find_next_square(10) -1 """ root = sq**0.5 if root.is_integer(): return (root + 1) ** 2 return -1 def find_next_square2(sq: float) -> float: """Find the next perfect square using modulo check. Args: sq: A non-negative number to check. Returns: The next perfect square if sq is a perfect square, otherwise -1. Examples: >>> find_next_square2(121) 144 >>> find_next_square2(10) -1 """ root = sq**0.5 return -1 if root % 1 else (root + 1) ** 2 ================================================ FILE: algorithms/math/nth_digit.py ================================================ """ Find the Nth Digit Find the nth digit in the infinite sequence 1, 2, 3, ..., 9, 10, 11, 12, ... by determining which number contains it and extracting the specific digit. Reference: https://en.wikipedia.org/wiki/Positional_notation Complexity: Time: O(log n) Space: O(log n) for string conversion """ from __future__ import annotations def find_nth_digit(n: int) -> int: """Find the nth digit in the sequence of natural numbers. Args: n: The 1-based position of the digit to find. Returns: The digit at position n. Examples: >>> find_nth_digit(11) 0 """ length = 1 count = 9 start = 1 while n > length * count: n -= length * count length += 1 count *= 10 start *= 10 start += (n - 1) / length s = str(start) return int(s[(n - 1) % length]) ================================================ FILE: algorithms/math/num_digits.py ================================================ """ Number of Digits Count the number of digits in an integer using logarithmic computation for O(1) time complexity. Reference: https://en.wikipedia.org/wiki/Logarithm Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations import math def num_digits(n: int) -> int: """Count the number of digits in an integer. Args: n: An integer (negative values use their absolute value). Returns: The number of digits in n. Examples: >>> num_digits(12) 2 >>> num_digits(0) 1 >>> num_digits(-254) 3 """ n = abs(n) if n == 0: return 1 return int(math.log10(n)) + 1 ================================================ FILE: algorithms/math/num_perfect_squares.py ================================================ """ Minimum Perfect Squares Sum Determine the minimum number of perfect squares that sum to a given integer. By Lagrange's four-square theorem, the answer is always between 1 and 4. Reference: https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem Complexity: Time: O(sqrt(n)) Space: O(1) """ from __future__ import annotations import math def num_perfect_squares(number: int) -> int: """Find the minimum count of perfect squares that sum to number. Args: number: A positive integer. Returns: An integer between 1 and 4 representing the minimum count. Examples: >>> num_perfect_squares(9) 1 >>> num_perfect_squares(10) 2 >>> num_perfect_squares(12) 3 >>> num_perfect_squares(31) 4 """ if int(math.sqrt(number)) ** 2 == number: return 1 while number > 0 and number % 4 == 0: number /= 4 if number % 8 == 7: return 4 for i in range(1, int(math.sqrt(number)) + 1): if int(math.sqrt(number - i**2)) ** 2 == number - i**2: return 2 return 3 ================================================ FILE: algorithms/math/polynomial.py ================================================ """ Polynomial and Monomial Arithmetic A symbolic algebra system for polynomials and monomials supporting addition, subtraction, multiplication, division, substitution, and polynomial long division with Fraction-based exact arithmetic. Reference: https://en.wikipedia.org/wiki/Polynomial Complexity: Time: Varies by operation Space: O(number of monomials) """ from __future__ import annotations from collections.abc import Iterable from fractions import Fraction from functools import reduce from numbers import Rational class Monomial: """A monomial represented by a coefficient and variable-to-power mapping.""" def __init__( self, variables: dict[int, int], coeff: int | float | Fraction | None = None ) -> None: """Create a monomial with the given variables and coefficient. Args: variables: Dictionary mapping variable indices to their powers. coeff: The coefficient (defaults to 0 if empty, 1 otherwise). Examples: >>> Monomial({1: 1}) # (a_1)^1 >>> Monomial({1: 3, 2: 2}, 12) # 12(a_1)^3(a_2)^2 """ self.variables = dict() if coeff is None: coeff = Fraction(0, 1) if len(variables) == 0 else Fraction(1, 1) elif coeff == 0: self.coeff = Fraction(0, 1) return if len(variables) == 0: self.coeff = Monomial._rationalize_if_possible(coeff) return for i in variables: if variables[i] != 0: self.variables[i] = variables[i] self.coeff = Monomial._rationalize_if_possible(coeff) @staticmethod def _rationalize_if_possible( num: int | float | Fraction, ) -> Fraction | float: """Convert numbers to Fraction when possible. Args: num: A numeric value. Returns: A Fraction if the input is Rational, otherwise the original value. """ if isinstance(num, Rational): res = Fraction(num, 1) return Fraction(res.numerator, res.denominator) else: return num def equal_upto_scalar(self, other: object) -> bool: """Check if other is a monomial equivalent to self up to scalar multiple. Args: other: Another Monomial to compare. Returns: True if both have the same variables with the same powers. Raises: ValueError: If other is not a Monomial. """ if not isinstance(other, Monomial): raise ValueError("Can only compare monomials.") return other.variables == self.variables def __add__(self, other: int | float | Fraction) -> Monomial: """Add two monomials or a monomial with a scalar. Args: other: A Monomial, int, float, or Fraction to add. Returns: The resulting Monomial. Raises: ValueError: If monomials have different variables. """ if isinstance(other, (int, float, Fraction)): return self.__add__(Monomial({}, Monomial._rationalize_if_possible(other))) if not isinstance(other, Monomial): raise ValueError("Can only add monomials, ints, floats, or Fractions.") if self.variables == other.variables: mono = {i: self.variables[i] for i in self.variables} return Monomial( mono, Monomial._rationalize_if_possible(self.coeff + other.coeff) ).clean() raise ValueError( f"Cannot add {str(other)} to {self.__str__()} " "because they don't have same variables." ) def __eq__(self, other: object) -> bool: """Check equality of two monomials. Args: other: Another Monomial to compare. Returns: True if both monomials are equal. """ if not isinstance(other, Monomial): return NotImplemented return self.equal_upto_scalar(other) and self.coeff == other.coeff def __mul__(self, other: int | float | Fraction) -> Monomial: """Multiply two monomials or a monomial with a scalar. Args: other: A Monomial, int, float, or Fraction to multiply. Returns: The resulting Monomial. Raises: ValueError: If other is not a valid type. """ if isinstance(other, (float, int, Fraction)): mono = {i: self.variables[i] for i in self.variables} return Monomial( mono, Monomial._rationalize_if_possible(self.coeff * other) ).clean() if not isinstance(other, Monomial): raise ValueError("Can only multiply monomials, ints, floats, or Fractions.") else: mono = {i: self.variables[i] for i in self.variables} for i in other.variables: if i in mono: mono[i] += other.variables[i] else: mono[i] = other.variables[i] temp = dict() for k in mono: if mono[k] != 0: temp[k] = mono[k] return Monomial( temp, Monomial._rationalize_if_possible(self.coeff * other.coeff) ).clean() def inverse(self) -> Monomial: """Compute the multiplicative inverse of this monomial. Returns: The inverse Monomial. Raises: ValueError: If the coefficient is zero. """ mono = {i: self.variables[i] for i in self.variables if self.variables[i] != 0} for i in mono: mono[i] *= -1 if self.coeff == 0: raise ValueError("Coefficient must not be 0.") return Monomial(mono, Monomial._rationalize_if_possible(1 / self.coeff)).clean() def __truediv__(self, other: int | float | Fraction) -> Monomial: """Divide this monomial by another monomial or scalar. Args: other: A Monomial, int, float, or Fraction divisor. Returns: The resulting Monomial. Raises: ValueError: If dividing by zero. """ if isinstance(other, (int, float, Fraction)): mono = {i: self.variables[i] for i in self.variables} if other == 0: raise ValueError("Cannot divide by 0.") return Monomial( mono, Monomial._rationalize_if_possible(self.coeff / other) ).clean() o = other.inverse() return self.__mul__(o) def __floordiv__(self, other: int | float | Fraction) -> Monomial: """Floor division (same as true division for monomials). Args: other: A Monomial, int, float, or Fraction divisor. Returns: The resulting Monomial. """ return self.__truediv__(other) def clone(self) -> Monomial: """Create a deep copy of this monomial. Returns: A new Monomial with the same variables and coefficient. """ temp_variables = {i: self.variables[i] for i in self.variables} return Monomial( temp_variables, Monomial._rationalize_if_possible(self.coeff) ).clean() def clean(self) -> Monomial: """Remove variables with zero power. Returns: A cleaned Monomial. """ temp_variables = { i: self.variables[i] for i in self.variables if self.variables[i] != 0 } return Monomial(temp_variables, Monomial._rationalize_if_possible(self.coeff)) def __sub__(self, other: int | float | Fraction) -> Monomial: """Subtract a value from this monomial. Args: other: A Monomial, int, float, or Fraction to subtract. Returns: The resulting Monomial. Raises: ValueError: If monomials have different variables. """ if isinstance(other, (int, float, Fraction)): mono = { i: self.variables[i] for i in self.variables if self.variables[i] != 0 } if len(mono) != 0: raise ValueError("Can only subtract like monomials.") other_term = Monomial(mono, Monomial._rationalize_if_possible(other)) return self.__sub__(other_term) if not isinstance(other, Monomial): raise ValueError("Can only subtract monomials") return self.__add__(other.__mul__(Fraction(-1, 1))) def __hash__(self) -> int: """Hash based on the underlying variables. Returns: An integer hash value. """ arr = [] for i in sorted(self.variables): if self.variables[i] > 0: for _ in range(self.variables[i]): arr.append(i) return hash(tuple(arr)) def all_variables(self) -> set: """Get the set of all variable indices in this monomial. Returns: A set of variable indices. """ return set(sorted(self.variables.keys())) def substitute( self, substitutions: int | float | Fraction | dict[int, int | float | Fraction], ) -> Fraction: """Evaluate the monomial by substituting values for variables. Args: substitutions: A single value applied to all variables, or a dict mapping variable indices to values. Returns: The evaluated result. Raises: ValueError: If some variables are not given values. """ if isinstance(substitutions, (int, float, Fraction)): substitutions = { v: Monomial._rationalize_if_possible(substitutions) for v in self.all_variables() } else: if not self.all_variables().issubset(set(substitutions.keys())): raise ValueError("Some variables didn't receive their values.") if self.coeff == 0: return Fraction(0, 1) ans = Monomial._rationalize_if_possible(self.coeff) for k in self.variables: ans *= Monomial._rationalize_if_possible( substitutions[k] ** self.variables[k] ) return Monomial._rationalize_if_possible(ans) def __str__(self) -> str: """Get a string representation of the monomial. Returns: A human-readable string. """ if len(self.variables) == 0: return str(self.coeff) result = str(self.coeff) result += "(" for i in self.variables: temp = f"a_{str(i)}" if self.variables[i] > 1: temp = "(" + temp + f")**{self.variables[i]}" elif self.variables[i] < 0: temp = "(" + temp + f")**(-{-self.variables[i]})" elif self.variables[i] == 0: continue else: temp = "(" + temp + ")" result += temp return result + ")" class Polynomial: """A polynomial represented as a set of Monomial terms.""" def __init__( self, monomials: Iterable[int | float | Fraction | Monomial] ) -> None: """Create a polynomial from an iterable of monomials or scalars. Args: monomials: An iterable of Monomial, int, float, or Fraction values. Raises: ValueError: If an element is not a valid type. """ self.monomials: set = set() for m in monomials: if any(map(lambda x: isinstance(m, x), [int, float, Fraction])): self.monomials |= {Monomial({}, m)} elif isinstance(m, Monomial): self.monomials |= {m} else: raise ValueError( "Iterable should have monomials, int, float, or Fraction." ) self.monomials -= {Monomial({}, 0)} @staticmethod def _rationalize_if_possible( num: int | float | Fraction, ) -> Fraction | float: """Convert numbers to Fraction when possible. Args: num: A numeric value. Returns: A Fraction if the input is Rational, otherwise the original value. """ if isinstance(num, Rational): res = Fraction(num, 1) return Fraction(res.numerator, res.denominator) else: return num def __add__(self, other: int | float | Fraction | Monomial) -> Polynomial: """Add a polynomial, monomial, or scalar to this polynomial. Args: other: Value to add. Returns: The resulting Polynomial. Raises: ValueError: If other is not a valid type. """ if isinstance(other, (int, float, Fraction)): return self.__add__( Monomial({}, Polynomial._rationalize_if_possible(other)) ) elif isinstance(other, Monomial): monos = {m.clone() for m in self.monomials} for _own_monos in monos: if _own_monos.equal_upto_scalar(other): scalar = _own_monos.coeff monos -= {_own_monos} temp_variables = {i: other.variables[i] for i in other.variables} monos |= { Monomial( temp_variables, Polynomial._rationalize_if_possible(scalar + other.coeff), ) } return Polynomial([z for z in monos]) monos |= {other.clone()} return Polynomial([z for z in monos]) elif isinstance(other, Polynomial): temp = list(z for z in {m.clone() for m in self.all_monomials()}) p = Polynomial(temp) for o in other.all_monomials(): p = p.__add__(o.clone()) return p else: raise ValueError( "Can only add int, float, Fraction, Monomials, " "or Polynomials to Polynomials." ) def __sub__(self, other: int | float | Fraction | Monomial) -> Polynomial: """Subtract a polynomial, monomial, or scalar from this polynomial. Args: other: Value to subtract. Returns: The resulting Polynomial. Raises: ValueError: If other is not a valid type. """ if isinstance(other, (int, float, Fraction)): return self.__sub__( Monomial({}, Polynomial._rationalize_if_possible(other)) ) elif isinstance(other, Monomial): monos = {m.clone() for m in self.all_monomials()} for _own_monos in monos: if _own_monos.equal_upto_scalar(other): scalar = _own_monos.coeff monos -= {_own_monos} temp_variables = {i: other.variables[i] for i in other.variables} monos |= { Monomial( temp_variables, Polynomial._rationalize_if_possible(scalar - other.coeff), ) } return Polynomial([z for z in monos]) to_insert = other.clone() to_insert.coeff *= -1 monos |= {to_insert} return Polynomial([z for z in monos]) elif isinstance(other, Polynomial): p = Polynomial(list(z for z in {m.clone() for m in self.all_monomials()})) for o in other.all_monomials(): p = p.__sub__(o.clone()) return p else: raise ValueError( "Can only subtract int, float, Fraction, " "Monomials, or Polynomials from Polynomials." ) def __mul__(self, other: int | float | Fraction | Monomial) -> Polynomial: """Multiply this polynomial by another polynomial, monomial, or scalar. Args: other: Value to multiply by. Returns: The resulting Polynomial. Raises: ValueError: If other is not a valid type. """ if isinstance(other, (int, float, Fraction, Monomial)): result = Polynomial([]) monos = {m.clone() for m in self.all_monomials()} for m in monos: result = result.__add__(m.clone() * other) return result elif isinstance(other, Polynomial): temp_self = {m.clone() for m in self.all_monomials()} temp_other = {m.clone() for m in other.all_monomials()} result = Polynomial([]) for i in temp_self: for j in temp_other: result = result.__add__(i * j) return result else: raise ValueError( "Can only multiple int, float, Fraction, " "Monomials, or Polynomials with Polynomials." ) def __floordiv__(self, other: int | float | Fraction | Monomial) -> Polynomial: """Floor division (same as true division for polynomials). Args: other: Divisor value. Returns: The resulting Polynomial. """ return self.__truediv__(other) def __truediv__(self, other: int | float | Fraction | Monomial) -> Polynomial: """Divide this polynomial by another value. Args: other: Divisor (int, float, Fraction, Monomial, or Polynomial). Returns: The quotient Polynomial. Raises: ValueError: If other is not a valid type. """ if isinstance(other, (int, float, Fraction)): return self.__truediv__(Monomial({}, other)) elif isinstance(other, Monomial): poly_temp = reduce( lambda acc, val: acc + val, map(lambda x: x / other, [z for z in self.all_monomials()]), Polynomial([Monomial({}, 0)]), ) return poly_temp elif isinstance(other, Polynomial): quotient, remainder = self.poly_long_division(other) return quotient raise ValueError( "Can only divide a polynomial by an int, float, " "Fraction, Monomial, or Polynomial." ) def clone(self) -> Polynomial: """Create a deep copy of this polynomial. Returns: A new Polynomial with cloned monomials. """ return Polynomial(list({m.clone() for m in self.all_monomials()})) def variables(self) -> set: """Get all variable indices present in this polynomial. Returns: A set of variable indices. """ res = set() for i in self.all_monomials(): res |= {j for j in i.variables} res = list(res) return set(res) def all_monomials(self) -> Iterable[Monomial]: """Get all non-zero monomials in this polynomial. Returns: A set of Monomial terms. """ return {m for m in self.monomials if m != Monomial({}, 0)} def __eq__(self, other: object) -> bool: """Check equality of two polynomials. Args: other: Another Polynomial, Monomial, or scalar. Returns: True if both represent the same polynomial. Raises: ValueError: If other is not a valid type. """ if isinstance(other, (int, float, Fraction)): other_poly = Polynomial([Monomial({}, other)]) return self.__eq__(other_poly) elif isinstance(other, Monomial): return self.__eq__(Polynomial([other])) elif isinstance(other, Polynomial): return self.all_monomials() == other.all_monomials() else: raise ValueError( "Can only compare a polynomial with an int, " "float, Fraction, Monomial, or another Polynomial." ) def subs( self, substitutions: int | float | Fraction | dict[int, int | float | Fraction], ) -> int | float | Fraction: """Evaluate the polynomial by substituting values for variables. Args: substitutions: A single value applied to all variables, or a dict mapping variable indices to values. Returns: The evaluated result. Raises: ValueError: If some variables are not given values. """ if isinstance(substitutions, (int, float, Fraction)): substitutions = { i: Polynomial._rationalize_if_possible(substitutions) for i in set(self.variables()) } return self.subs(substitutions) elif not isinstance(substitutions, dict): raise ValueError("The substitutions should be a dictionary.") if not self.variables().issubset(set(substitutions.keys())): raise ValueError("Some variables didn't receive their values.") ans = 0 for m in self.all_monomials(): ans += Polynomial._rationalize_if_possible(m.substitute(substitutions)) return Polynomial._rationalize_if_possible(ans) def __str__(self) -> str: """Get a formatted string representation of the polynomial. Returns: A human-readable string. """ sorted_monos = sorted( self.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), reverse=True, ) return " + ".join(str(m) for m in sorted_monos if m.coeff != Fraction(0, 1)) def poly_long_division(self, other: Polynomial) -> tuple[Polynomial, Polynomial]: """Perform polynomial long division. Args: other: The divisor Polynomial. Returns: A tuple (quotient, remainder). Raises: ValueError: If other is not a Polynomial or is zero. """ if not isinstance(other, Polynomial): raise ValueError("Can only divide by another Polynomial.") if len(other.all_monomials()) == 0: raise ValueError("Cannot divide by zero polynomial.") quotient = Polynomial([]) remainder = self.clone() divisor_monos = sorted( other.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), reverse=True, ) divisor_lead = divisor_monos[0] while remainder.all_monomials() and max( remainder.variables(), default=-1 ) >= max(other.variables(), default=-1): remainder_monos = sorted( remainder.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), reverse=True, ) remainder_lead = remainder_monos[0] if not all( remainder_lead.variables.get(var, 0) >= divisor_lead.variables.get(var, 0) for var in divisor_lead.variables ): break lead_quotient = remainder_lead / divisor_lead quotient = quotient + Polynomial([lead_quotient]) remainder = remainder - (Polynomial([lead_quotient]) * other) return quotient, remainder ================================================ FILE: algorithms/math/polynomial_division.py ================================================ """Polynomial long division. Divide polynomial *dividend* by *divisor* and return (quotient, remainder). Polynomials are represented as lists of coefficients from highest to lowest degree. E.g. [1, -3, 2] represents x^2 - 3x + 2. Inspired by PR #840 (KTH-Software-Engineering-DD2480). """ from __future__ import annotations def polynomial_division( dividend: list[float], divisor: list[float] ) -> tuple[list[float], list[float]]: """Perform polynomial long division. Returns (quotient_coefficients, remainder_coefficients). >>> polynomial_division([1, -3, 2], [1, -1]) ([1.0, -2.0], [0.0]) >>> polynomial_division([1, 0, -4], [1, -2]) ([1.0, 2.0], [0.0]) """ if not divisor or all(c == 0 for c in divisor): msg = "Cannot divide by zero polynomial" raise ZeroDivisionError(msg) dividend = [float(c) for c in dividend] divisor = [float(c) for c in divisor] remainder = list(dividend) quotient: list[float] = [] divisor_lead = divisor[0] len_diff = len(dividend) - len(divisor) for i in range(len_diff + 1): coeff = remainder[i] / divisor_lead quotient.append(coeff) for j in range(len(divisor)): remainder[i + j] -= coeff * divisor[j] # The remainder is the tail of the array remainder = remainder[len_diff + 1 :] if not remainder: remainder = [0.0] return quotient, remainder ================================================ FILE: algorithms/math/power.py ================================================ """ Binary Exponentiation Compute a^n efficiently using binary exponentiation (exponentiation by squaring), with optional modular arithmetic. Reference: https://en.wikipedia.org/wiki/Exponentiation_by_squaring Complexity: Time: O(log n) Space: O(1) iterative, O(log n) recursive """ from __future__ import annotations def power(a: int, n: int, mod: int | None = None) -> int: """Compute a^n iteratively using binary exponentiation. Args: a: The base. n: The exponent. mod: Optional modulus for modular exponentiation. Returns: a^n, or a^n % mod if mod is specified. Examples: >>> power(2, 3) 8 >>> power(10, 3, 5) 0 """ ans = 1 while n: if n & 1: ans = ans * a a = a * a if mod: ans %= mod a %= mod n >>= 1 return ans def power_recur(a: int, n: int, mod: int | None = None) -> int: """Compute a^n recursively using binary exponentiation. Args: a: The base. n: The exponent. mod: Optional modulus for modular exponentiation. Returns: a^n, or a^n % mod if mod is specified. Examples: >>> power_recur(2, 3) 8 """ if n == 0: ans = 1 elif n == 1: ans = a else: ans = power_recur(a, n // 2, mod) ans = ans * ans if n % 2: ans = ans * a if mod: ans %= mod return ans ================================================ FILE: algorithms/math/prime_check.py ================================================ """ Primality Test Check whether a given integer is prime using trial division with 6k +/- 1 optimization. Reference: https://en.wikipedia.org/wiki/Primality_test Complexity: Time: O(sqrt(n)) Space: O(1) """ from __future__ import annotations def prime_check(n: int) -> bool: """Check whether n is a prime number. Args: n: The integer to test. Returns: True if n is prime, False otherwise. Examples: >>> prime_check(7) True >>> prime_check(4) False """ if n <= 1: return False if n == 2 or n == 3: return True if n % 2 == 0 or n % 3 == 0: return False j = 5 while j * j <= n: if n % j == 0 or n % (j + 2) == 0: return False j += 6 return True ================================================ FILE: algorithms/math/primes_sieve_of_eratosthenes.py ================================================ """ Sieve of Eratosthenes Generate all prime numbers less than n using an optimized sieve that skips even numbers. Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes Complexity: Time: O(n log log n) Space: O(n) """ from __future__ import annotations def get_primes(n: int) -> list[int]: """Return all primes less than n using the Sieve of Eratosthenes. Args: n: Upper bound (exclusive). Must be a positive integer. Returns: A sorted list of all primes less than n. Raises: ValueError: If n is not positive. Examples: >>> get_primes(7) [2, 3, 5, 7] """ if n <= 0: raise ValueError("'n' must be a positive integer.") sieve_size = (n // 2 - 1) if n % 2 == 0 else (n // 2) sieve = [True for _ in range(sieve_size)] primes: list[int] = [] if n >= 2: primes.append(2) for i in range(sieve_size): if sieve[i]: value_at_i = i * 2 + 3 primes.append(value_at_i) for j in range(i, sieve_size, value_at_i): sieve[j] = False return primes ================================================ FILE: algorithms/math/pythagoras.py ================================================ """ Pythagorean Theorem Given the lengths of two sides of a right-angled triangle, compute the length of the third side using the Pythagorean theorem. Reference: https://en.wikipedia.org/wiki/Pythagorean_theorem Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations def pythagoras( opposite: float | str, adjacent: float | str, hypotenuse: float | str ) -> str: """Compute the unknown side of a right triangle. Pass "?" as the unknown side to calculate it from the other two. Args: opposite: Length of the opposite side, or "?" if unknown. adjacent: Length of the adjacent side, or "?" if unknown. hypotenuse: Length of the hypotenuse, or "?" if unknown. Returns: A string describing the computed side and its value. Raises: ValueError: If the arguments are invalid. Examples: >>> pythagoras(3, 4, "?") 'Hypotenuse = 5.0' """ try: if opposite == "?": return "Opposite = " + str(((hypotenuse**2) - (adjacent**2)) ** 0.5) if adjacent == "?": return "Adjacent = " + str(((hypotenuse**2) - (opposite**2)) ** 0.5) if hypotenuse == "?": return "Hypotenuse = " + str(((opposite**2) + (adjacent**2)) ** 0.5) return "You already know the answer!" except Exception as err: raise ValueError("invalid argument(s) were given.") from err ================================================ FILE: algorithms/math/rabin_miller.py ================================================ """ Rabin-Miller Primality Test A probabilistic primality test where returning False guarantees the number is composite, and returning True means the number is probably prime with a 4^(-k) chance of error. Reference: https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Complexity: Time: O(k * log^2(n)) Space: O(1) """ from __future__ import annotations import secrets def is_prime(n: int, k: int) -> bool: """Test if n is probably prime using the Rabin-Miller algorithm. Args: n: The integer to test for primality. k: The number of rounds of testing (higher = more accurate). Returns: True if n is probably prime, False if n is definitely composite. Examples: >>> is_prime(7, 2) True >>> is_prime(6, 2) False """ def _pow2_factor(num: int) -> tuple[int, int]: """Factor num into 2^power * odd_part. Args: num: The integer to factor. Returns: A tuple (power, odd_part). """ power = 0 while num % 2 == 0: num //= 2 power += 1 return power, num def _valid_witness(a: int) -> bool: """Check if a is a witness for the compositeness of n. Args: a: The potential witness value. Returns: True if a proves n is composite, False otherwise. """ x = pow(int(a), int(d), int(n)) if x == 1 or x == n - 1: return False for _ in range(r - 1): x = pow(int(x), 2, int(n)) if x == 1: return True if x == n - 1: return False return True if n < 5: return n == 2 or n == 3 r, d = _pow2_factor(n - 1) return all( not _valid_witness(secrets.randbelow(n - 4) + 2) for _ in range(k) ) ================================================ FILE: algorithms/math/recursive_binomial_coefficient.py ================================================ """ Recursive Binomial Coefficient Calculate the binomial coefficient C(n, k) using a recursive formula with the identity C(n, k) = (n/k) * C(n-1, k-1). Reference: https://en.wikipedia.org/wiki/Binomial_coefficient Complexity: Time: O(k) Space: O(k) recursive stack """ from __future__ import annotations def recursive_binomial_coefficient(n: int, k: int) -> int: """Calculate C(n, k) recursively, where n >= k. Args: n: Total number of items. k: Number of items to choose. Returns: The binomial coefficient C(n, k). Raises: ValueError: If k > n. Examples: >>> recursive_binomial_coefficient(5, 0) 1 >>> recursive_binomial_coefficient(8, 2) 28 """ if k > n: raise ValueError("Invalid Inputs, ensure that n >= k") if k == 0 or n == k: return 1 if k > n / 2: return recursive_binomial_coefficient(n, n - k) return int((n / k) * recursive_binomial_coefficient(n - 1, k - 1)) ================================================ FILE: algorithms/math/rsa.py ================================================ """ RSA Encryption Algorithm Implements RSA key generation, encryption, and decryption. RSA uses separate public and private keys where ((x^e)^d) % n == x % n. Reference: https://en.wikipedia.org/wiki/RSA_(cryptosystem) Complexity: Time: O(k^3) for key generation (k = bit length) Space: O(k) """ from __future__ import annotations import secrets def _extended_gcd(a: int, b: int) -> tuple[int, int, int]: old_r, r = a, b old_s, s = 1, 0 old_t, t = 0, 1 while r != 0: q = old_r // r old_r, r = r, old_r - q * r old_s, s = s, old_s - q * s old_t, t = t, old_t - q * t return old_r, old_s, old_t def _modinv(a: int, m: int) -> int: g, x, _ = _extended_gcd(a, m) if g != 1: raise ValueError(f"Modular inverse does not exist: gcd({a}, {m}) = {g}") return x % m def generate_key(k: int, seed: int | None = None) -> tuple[int, int, int]: """Generate an RSA key triplet (n, e, d). Args: k: The number of bits in the modulus n. seed: Optional random seed for reproducibility (ignored, kept for API compatibility). Returns: A tuple (n, e, d) where n is the modulus, e is the encryption exponent, and d is the decryption exponent. Examples: >>> n, e, d = generate_key(16) """ def _gen_prime(k: int, seed: int | None = None) -> int: """Generate a random prime with k bits. Args: k: The number of bits. seed: Unused, kept for API compatibility. Returns: A prime number. """ def _is_prime(num: int) -> bool: if num == 2: return True return all( num % i != 0 for i in range(2, int(num**0.5) + 1) ) while True: key = secrets.randbelow(int(2**k) - int(2 ** (k - 1))) + int(2 ** (k - 1)) if _is_prime(key): return key p_size = k // 2 q_size = k - p_size e = _gen_prime(k, seed) while True: p = _gen_prime(p_size, seed) if p % e != 1: break while True: q = _gen_prime(q_size, seed) if q % e != 1: break n = p * q totient = (p - 1) * (q - 1) d = _modinv(e, totient) return int(n), int(e), int(d) def encrypt(data: int, e: int, n: int) -> int: """Encrypt data using RSA public key. Args: data: The plaintext integer. e: The encryption exponent. n: The modulus. Returns: The encrypted integer. Examples: >>> encrypt(7, 23, 143) 2 """ return pow(int(data), int(e), int(n)) def decrypt(data: int, d: int, n: int) -> int: """Decrypt data using RSA private key. Args: data: The encrypted integer. d: The decryption exponent. n: The modulus. Returns: The decrypted plaintext integer. Examples: >>> decrypt(2, 47, 143) 7 """ return pow(int(data), int(d), int(n)) ================================================ FILE: algorithms/math/sqrt_precision_factor.py ================================================ """ Square Root by Newton's Method Compute the square root of a positive number using Newton's method (Babylonian method) with a configurable precision factor. Reference: https://en.wikipedia.org/wiki/Newton%27s_method#Square_root Complexity: Time: O(log(1/epsilon)) iterations for convergence Space: O(1) """ from __future__ import annotations def square_root(n: float, epsilon: float = 0.001) -> float: """Compute the square root of n with maximum absolute error epsilon. Args: n: A positive number. epsilon: Maximum allowed absolute error. Returns: An approximation of sqrt(n). Examples: >>> abs(square_root(5, 0.001) - 2.236) < 0.002 True """ guess = n / 2 while abs(guess * guess - n) > epsilon: guess = (guess + (n / guess)) / 2 return guess ================================================ FILE: algorithms/math/summing_digits.py ================================================ """ Summing Digits Power Find all integers in a range where each digit raised to its positional power (1-indexed) sums to the number itself (e.g., 89 = 8^1 + 9^2). Reference: https://en.wikipedia.org/wiki/Perfect_digit-to-digit_invariant Complexity: Time: O((high - low) * d) where d is the number of digits Space: O(result size) """ from __future__ import annotations def sum_dig_pow(low: int, high: int) -> list[int]: """Find numbers where digits raised to positional powers equal the number. Args: low: Lower bound of the range (inclusive). high: Upper bound of the range (inclusive). Returns: List of matching numbers in the range. Examples: >>> sum_dig_pow(1, 10) [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> sum_dig_pow(1, 100) [1, 2, 3, 4, 5, 6, 7, 8, 9, 89] """ result = [] for number in range(low, high + 1): exponent = 1 summation = 0 number_as_string = str(number) tokens = list(map(int, number_as_string)) for k in tokens: summation = summation + (k**exponent) exponent += 1 if summation == number: result.append(number) return result ================================================ FILE: algorithms/math/surface_area_of_torus.py ================================================ """ Surface Area of a Torus Calculate the surface area of a torus given its major and minor radii using the formula A = 4 * pi^2 * R * r. Reference: https://en.wikipedia.org/wiki/Torus Complexity: Time: O(1) Space: O(1) """ from __future__ import annotations from math import pi def surface_area_of_torus(major_radius: float, minor_radius: float) -> float: """Calculate the surface area of a torus. Args: major_radius: Distance from the center of the tube to the center of the torus (R). minor_radius: Radius of the tube (r). Returns: The surface area of the torus. Raises: ValueError: If either radius is negative. Examples: >>> surface_area_of_torus(3.0, 1.0) 118.4352528130723 """ if major_radius < 0 or minor_radius < 0: raise ValueError("Radii must be non-negative") return 4 * pi**2 * major_radius * minor_radius ================================================ FILE: algorithms/math/symmetry_group_cycle_index.py ================================================ """ Symmetry Group Cycle Index Compute the cycle index polynomial of the symmetric group S_n and use it to count distinct configurations of grids under row/column permutations via Burnside's lemma. Reference: https://en.wikipedia.org/wiki/Cycle_index#Symmetric_group_Sn Complexity: Time: O(n!) for cycle index computation Space: O(n!) for memoization """ from __future__ import annotations from fractions import Fraction from algorithms.math.gcd import lcm from algorithms.math.polynomial import Monomial, Polynomial def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: """Compute the cycle product of two monomials from cycle indices. Args: m1: First monomial from a cycle index. m2: Second monomial from a cycle index. Returns: The resultant monomial from the Cartesian product merging. """ assert isinstance(m1, Monomial) and isinstance(m2, Monomial) a_vars = m1.variables b_vars = m2.variables result_variables: dict[int, int] = dict() for i in a_vars: for j in b_vars: k = lcm(i, j) g = (i * j) // k if k in result_variables: result_variables[k] += a_vars[i] * b_vars[j] * g else: result_variables[k] = a_vars[i] * b_vars[j] * g return Monomial(result_variables, Fraction(m1.coeff * m2.coeff, 1)) def cycle_product_for_two_polynomials( p1: Polynomial, p2: Polynomial, q: float | int | Fraction ) -> float | int | Fraction: """Compute the product of two cycle indices and evaluate at q. Args: p1: First cycle index polynomial. p2: Second cycle index polynomial. q: The value to substitute for all variables. Returns: The evaluated result. """ ans = Fraction(0, 1) for m1 in p1.monomials: for m2 in p2.monomials: ans += cycle_product(m1, m2).substitute(q) return ans def _cycle_index_sym_helper(n: int, memo: dict[int, Polynomial]) -> Polynomial: """Recursively compute the cycle index of S_n with memoization. Args: n: The order of the symmetric group. memo: Dictionary of precomputed cycle indices. Returns: The cycle index polynomial of S_n. """ if n in memo: return memo[n] ans = Polynomial([Monomial({}, Fraction(0, 1))]) for t in range(1, n + 1): ans = ans.__add__( Polynomial([Monomial({t: 1}, Fraction(1, 1))]) * _cycle_index_sym_helper(n - t, memo) ) ans *= Fraction(1, n) memo[n] = ans return memo[n] def get_cycle_index_sym(n: int) -> Polynomial: """Compute the cycle index of the symmetric group S_n. Args: n: The order of the symmetric group (non-negative integer). Returns: The cycle index as a Polynomial. Raises: ValueError: If n is negative. Examples: >>> get_cycle_index_sym(1) # doctest: +SKIP Polynomial(...) """ if n < 0: raise ValueError("n should be a non-negative integer.") memo = { 0: Polynomial([Monomial({}, Fraction(1, 1))]), 1: Polynomial([Monomial({1: 1}, Fraction(1, 1))]), 2: Polynomial( [Monomial({1: 2}, Fraction(1, 2)), Monomial({2: 1}, Fraction(1, 2))] ), 3: Polynomial( [ Monomial({1: 3}, Fraction(1, 6)), Monomial({1: 1, 2: 1}, Fraction(1, 2)), Monomial({3: 1}, Fraction(1, 3)), ] ), 4: Polynomial( [ Monomial({1: 4}, Fraction(1, 24)), Monomial({2: 1, 1: 2}, Fraction(1, 4)), Monomial({3: 1, 1: 1}, Fraction(1, 3)), Monomial({2: 2}, Fraction(1, 8)), Monomial({4: 1}, Fraction(1, 4)), ] ), } result = _cycle_index_sym_helper(n, memo) return result ================================================ FILE: algorithms/matrix/__init__.py ================================================ from algorithms.matrix import ( # noqa: F401 bomb_enemy, cholesky_matrix_decomposition, copy_transform, count_paths, crout_matrix_decomposition, matrix_exponentiation, matrix_inversion, multiply, rotate_image, search_in_sorted_matrix, sort_matrix_diagonally, sparse_dot_vector, sparse_mul, spiral_traversal, sudoku_validator, sum_sub_squares, ) __all__ = [ "bomb_enemy", "cholesky_matrix_decomposition", "copy_transform", "count_paths", "crout_matrix_decomposition", "matrix_exponentiation", "matrix_inversion", "multiply", "rotate_image", "search_in_sorted_matrix", "sort_matrix_diagonally", "sparse_dot_vector", "sparse_mul", "spiral_traversal", "sudoku_validator", "sum_sub_squares", ] ================================================ FILE: algorithms/matrix/bomb_enemy.py ================================================ """ Bomb Enemy Given a 2D grid, each cell is either a wall 'W', an enemy 'E' or empty '0' (the number zero). Return the maximum enemies you can kill using one bomb. The bomb kills all the enemies in the same row and column from the planted point until it hits the wall since it is too strong to be destroyed. You can only place the bomb at an empty cell. Reference: https://leetcode.com/problems/bomb-enemy/ Complexity: Time: O(m * n) Space: O(n) """ from __future__ import annotations def max_killed_enemies(grid: list[list[str]]) -> int: """Return the maximum enemies killed by placing one bomb. Args: grid: 2D grid of 'W', 'E', and '0' characters. Returns: Maximum number of enemies killed. Examples: >>> max_killed_enemies([["0","E","0","0"],["E","0","W","E"],["0","E","0","0"]]) 3 """ if not grid: return 0 rows, cols = len(grid), len(grid[0]) max_killed = 0 row_enemies, col_enemies = 0, [0] * cols for i in range(rows): for j in range(cols): if j == 0 or grid[i][j - 1] == "W": row_enemies = _row_kills(grid, i, j) if i == 0 or grid[i - 1][j] == "W": col_enemies[j] = _col_kills(grid, i, j) if grid[i][j] == "0": max_killed = max(max_killed, row_enemies + col_enemies[j]) return max_killed def _row_kills(grid: list[list[str]], row: int, col: int) -> int: """Count enemies killed in a row starting from the given column. Args: grid: 2D grid of characters. row: Row index. col: Starting column index. Returns: Number of enemies in the row segment. """ count = 0 num_cols = len(grid[0]) while col < num_cols and grid[row][col] != "W": if grid[row][col] == "E": count += 1 col += 1 return count def _col_kills(grid: list[list[str]], row: int, col: int) -> int: """Count enemies killed in a column starting from the given row. Args: grid: 2D grid of characters. row: Starting row index. col: Column index. Returns: Number of enemies in the column segment. """ count = 0 num_rows = len(grid) while row < num_rows and grid[row][col] != "W": if grid[row][col] == "E": count += 1 row += 1 return count ================================================ FILE: algorithms/matrix/cholesky_matrix_decomposition.py ================================================ """ Cholesky Matrix Decomposition Decompose a Hermitian positive-definite matrix A into a lower-triangular matrix V such that V * V^T = A. Mainly used for numerical solution of linear equations Ax = b. Reference: https://en.wikipedia.org/wiki/Cholesky_decomposition Complexity: Time: O(n^3) Space: O(n^2) """ from __future__ import annotations import math def cholesky_decomposition(matrix: list[list[float]]) -> list[list[float]] | None: """Compute the Cholesky decomposition of a positive-definite matrix. Args: matrix: Hermitian positive-definite matrix (n x n). Returns: Lower-triangular matrix V such that V * V^T = matrix, or None if the matrix cannot be decomposed. Examples: >>> cholesky_decomposition([[4, 12, -16], [12, 37, -43], [-16, -43, 98]]) [[2.0, 0.0, 0.0], [6.0, 1.0, 0.0], [-8.0, 5.0, 3.0]] """ size = len(matrix) for row in matrix: if len(row) != size: return None result = [[0.0] * size for _ in range(size)] for j in range(size): diagonal_sum = sum(result[j][k] ** 2 for k in range(j)) diagonal_sum = matrix[j][j] - diagonal_sum if diagonal_sum <= 0: return None result[j][j] = math.sqrt(diagonal_sum) for i in range(j + 1, size): off_diagonal_sum = sum(result[i][k] * result[j][k] for k in range(j)) result[i][j] = (matrix[i][j] - off_diagonal_sum) / result[j][j] return result ================================================ FILE: algorithms/matrix/copy_transform.py ================================================ """ Copy Transform Rotate and invert a matrix by creating transformed copies. Provides clockwise rotation, counterclockwise rotation, top-left inversion (transpose), and bottom-left inversion (anti-transpose). Reference: https://en.wikipedia.org/wiki/Transpose Complexity: Time: O(m * n) Space: O(m * n) """ from __future__ import annotations def rotate_clockwise(matrix: list[list[int]]) -> list[list[int]]: """Rotate a matrix 90 degrees clockwise via copy. Args: matrix: 2D list of integers. Returns: New matrix rotated 90 degrees clockwise. Examples: >>> rotate_clockwise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[7, 4, 1], [8, 5, 2], [9, 6, 3]] """ result: list[list[int]] = [] for row in reversed(matrix): for i, elem in enumerate(row): try: result[i].append(elem) except IndexError: result.insert(i, []) result[i].append(elem) return result def rotate_counterclockwise(matrix: list[list[int]]) -> list[list[int]]: """Rotate a matrix 90 degrees counterclockwise via copy. Args: matrix: 2D list of integers. Returns: New matrix rotated 90 degrees counterclockwise. Examples: >>> rotate_counterclockwise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[3, 6, 9], [2, 5, 8], [1, 4, 7]] """ result: list[list[int]] = [] for row in matrix: for i, elem in enumerate(reversed(row)): try: result[i].append(elem) except IndexError: result.insert(i, []) result[i].append(elem) return result def top_left_invert(matrix: list[list[int]]) -> list[list[int]]: """Transpose a matrix (reflect over the main diagonal). Args: matrix: 2D list of integers. Returns: Transposed matrix. Examples: >>> top_left_invert([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[1, 4, 7], [2, 5, 8], [3, 6, 9]] """ result: list[list[int]] = [] for row in matrix: for i, elem in enumerate(row): try: result[i].append(elem) except IndexError: result.insert(i, []) result[i].append(elem) return result def bottom_left_invert(matrix: list[list[int]]) -> list[list[int]]: """Anti-transpose a matrix (reflect over the anti-diagonal). Args: matrix: 2D list of integers. Returns: Anti-transposed matrix. Examples: >>> bottom_left_invert([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[9, 6, 3], [8, 5, 2], [7, 4, 1]] """ result: list[list[int]] = [] for row in reversed(matrix): for i, elem in enumerate(reversed(row)): try: result[i].append(elem) except IndexError: result.insert(i, []) result[i].append(elem) return result ================================================ FILE: algorithms/matrix/count_paths.py ================================================ """ Count Paths Count the number of unique paths from the top-left corner to the bottom-right corner of an m x n grid. Movement is restricted to right or down only. Uses dynamic programming. Reference: https://leetcode.com/problems/unique-paths/ Complexity: Time: O(m * n) Space: O(m * n) """ from __future__ import annotations def count_paths(rows: int, cols: int) -> int: """Count unique paths from top-left to bottom-right of an m x n grid. Args: rows: Number of rows in the grid. cols: Number of columns in the grid. Returns: Number of unique paths, or -1 if dimensions are invalid. Examples: >>> count_paths(3, 3) 6 """ if rows < 1 or cols < 1: return -1 count = [[None for _ in range(cols)] for _ in range(rows)] for i in range(cols): count[0][i] = 1 for j in range(rows): count[j][0] = 1 for i in range(1, rows): for j in range(1, cols): count[i][j] = count[i - 1][j] + count[i][j - 1] return count[rows - 1][cols - 1] ================================================ FILE: algorithms/matrix/crout_matrix_decomposition.py ================================================ """ Crout Matrix Decomposition Decompose a matrix A into lower-triangular matrix L and upper-triangular matrix U such that L * U = A. L has non-zero elements only on and below the diagonal; U has non-zero elements only on and above the diagonal with ones on the diagonal. Reference: https://en.wikipedia.org/wiki/Crout_matrix_decomposition Complexity: Time: O(n^3) Space: O(n^2) """ from __future__ import annotations def crout_matrix_decomposition( matrix: list[list[float]], ) -> tuple[list[list[float]], list[list[float]]]: """Perform Crout decomposition of a square matrix. Args: matrix: Square matrix of size n x n. Returns: Tuple (L, U) of lower and upper triangular matrices. Examples: >>> crout_matrix_decomposition([[9, 9], [7, 7]]) ([[9.0, 0.0], [7.0, 0.0]], [[1.0, 1.0], [0.0, 1.0]]) """ size = len(matrix) lower = [[0.0] * size for _ in range(size)] upper = [[0.0] * size for _ in range(size)] for j in range(size): upper[j][j] = 1.0 for i in range(j, size): alpha = float(matrix[i][j]) for k in range(j): alpha -= lower[i][k] * upper[k][j] lower[i][j] = float(alpha) for i in range(j + 1, size): temp = float(matrix[j][i]) for k in range(j): temp -= float(lower[j][k] * upper[k][i]) if int(lower[j][j]) == 0: lower[j][j] = float(0.1**40) upper[j][i] = float(temp / lower[j][j]) return (lower, upper) ================================================ FILE: algorithms/matrix/matrix_exponentiation.py ================================================ """ Matrix Exponentiation Compute the n-th power of a square matrix using repeated squaring (exponentiation by squaring). Useful for computing Fibonacci numbers, linear recurrences, and graph path counting. Reference: https://en.wikipedia.org/wiki/Exponentiation_by_squaring Complexity: Time: O(d^3 * log n) where d is the matrix dimension Space: O(d^2) """ from __future__ import annotations def multiply(mat_a: list[list[int]], mat_b: list[list[int]]) -> list[list[int]]: """Multiply two square matrices. Args: mat_a: First square matrix (n x n). mat_b: Second square matrix (n x n). Returns: Product matrix of mat_a and mat_b. Examples: >>> multiply([[1, 0], [0, 1]], [[2, 3], [4, 5]]) [[2, 3], [4, 5]] """ size = len(mat_a) result = [[0] * size for _ in range(size)] for i in range(size): for j in range(size): for k in range(size): result[i][j] += mat_a[i][k] * mat_b[k][j] return result def identity(size: int) -> list[list[int]]: """Return the identity matrix of the given size. Args: size: Dimension of the identity matrix. Returns: Identity matrix of size n x n. Examples: >>> identity(3) [[1, 0, 0], [0, 1, 0], [0, 0, 1]] """ result = [[0] * size for _ in range(size)] for i in range(size): result[i][i] = 1 return result def matrix_exponentiation(mat: list[list[int]], power: int) -> list[list[int]]: """Compute mat raised to the given power by repeated squaring. Args: mat: Square matrix to exponentiate. power: Non-negative integer exponent. Returns: Matrix mat^power. Examples: >>> matrix_exponentiation([[1, 0], [0, 1]], 5) [[1, 0], [0, 1]] """ if power == 0: return identity(len(mat)) elif power % 2 == 1: return multiply(matrix_exponentiation(mat, power - 1), mat) else: half = matrix_exponentiation(mat, power // 2) return multiply(half, half) ================================================ FILE: algorithms/matrix/matrix_inversion.py ================================================ """ Matrix Inversion Compute the inverse of an invertible n x n matrix A, returning an n x n matrix B such that A * B = B * A = I (the identity matrix). Uses cofactor expansion: compute the matrix of minors with checkerboard signs, adjugate (transpose), and multiply by 1/determinant. Reference: https://en.wikipedia.org/wiki/Invertible_matrix Complexity: Time: O(n! * n) (cofactor expansion) Space: O(n^2) """ from __future__ import annotations import fractions def invert_matrix( matrix: list[list[int | float]], ) -> list[list[int | float | fractions.Fraction]]: """Invert an n x n matrix. Args: matrix: Square matrix of size n x n (n >= 2). Returns: Inverted matrix, or an error sentinel: [[-1]] if not a valid matrix, [[-2]] if not square, [[-3]] if too small, [[-4]] if singular. Examples: >>> invert_matrix([[1, 1], [1, 2]]) [[2, -1], [-1, 1]] """ if not _array_is_matrix(matrix): return [[-1]] elif len(matrix) != len(matrix[0]): return [[-2]] elif len(matrix) < 2: return [[-3]] elif get_determinant(matrix) == 0: return [[-4]] elif len(matrix) == 2: multiplier = 1 / get_determinant(matrix) inverted = [[multiplier] * len(matrix) for _ in range(len(matrix))] inverted[0][1] = inverted[0][1] * -1 * matrix[0][1] inverted[1][0] = inverted[1][0] * -1 * matrix[1][0] inverted[0][0] = multiplier * matrix[1][1] inverted[1][1] = multiplier * matrix[0][0] return inverted else: matrix_of_minors = _get_matrix_of_minors(matrix) multiplier = fractions.Fraction(1, get_determinant(matrix)) inverted = _transpose_and_multiply(matrix_of_minors, multiplier) return inverted def get_determinant(matrix: list[list[int | float]]) -> int | float: """Recursively compute the determinant of an n x n matrix. Args: matrix: Square matrix of size n x n (n >= 2). Returns: Determinant value. Examples: >>> get_determinant([[1, 2], [3, 4]]) -2 """ if len(matrix) == 2: return (matrix[0][0] * matrix[1][1]) - (matrix[0][1] * matrix[1][0]) sign = 1 det = 0 for i in range(len(matrix)): det += sign * matrix[0][i] * get_determinant(_get_minor(matrix, 0, i)) sign *= -1 return det def _get_matrix_of_minors( matrix: list[list[int | float]], ) -> list[list[int | float]]: """Compute the matrix of minors with alternating signs (cofactor matrix). Args: matrix: Square matrix of size n x n. Returns: Cofactor matrix. """ size = len(matrix) result = [[0] * size for _ in range(size)] for row in range(size): for col in range(len(matrix[0])): sign = 1 if (row + col) % 2 == 0 else -1 result[row][col] = sign * get_determinant(_get_minor(matrix, row, col)) return result def _get_minor( matrix: list[list[int | float]], row: int, col: int ) -> list[list[int | float]]: """Extract the minor by removing the given row and column. Args: matrix: Square matrix. row: Row index to remove. col: Column index to remove. Returns: Sub-matrix with the specified row and column removed. """ minors = [] for i in range(len(matrix)): if i != row: new_row = matrix[i][:col] new_row.extend(matrix[i][col + 1 :]) minors.append(new_row) return minors def _transpose_and_multiply( matrix: list[list[int | float]], multiplier: int | float | fractions.Fraction = 1, ) -> list[list[int | float | fractions.Fraction]]: """Transpose the matrix and multiply each element by a scalar. Args: matrix: Square matrix to transpose. multiplier: Scalar to multiply each element by. Returns: Transposed and scaled matrix. """ for row in range(len(matrix)): for col in range(row + 1): temp = matrix[row][col] * multiplier matrix[row][col] = matrix[col][row] * multiplier matrix[col][row] = temp return matrix def _array_is_matrix(matrix: list[list]) -> bool: """Check whether a 2D list has consistent row lengths. Args: matrix: 2D list to validate. Returns: True if all rows have the same length, False otherwise. """ if len(matrix) == 0: return False first_col = len(matrix[0]) return all(len(row) == first_col for row in matrix) ================================================ FILE: algorithms/matrix/multiply.py ================================================ """ Matrix Multiplication Multiply two compatible matrices and return their product. The number of columns in the multiplicand must equal the number of rows in the multiplier. Reference: https://en.wikipedia.org/wiki/Matrix_multiplication Complexity: Time: O(m * n * p) for (m x n) * (n x p) Space: O(m * p) """ from __future__ import annotations def multiply( multiplicand: list[list[int]], multiplier: list[list[int]] ) -> list[list[int]]: """Multiply two matrices. Args: multiplicand: Matrix of size m x n. multiplier: Matrix of size n x p. Returns: Product matrix of size m x p. Raises: Exception: If the matrices are not compatible for multiplication. Examples: >>> multiply([[1, 2, 3], [2, 1, 1]], [[1], [2], [3]]) [[14], [7]] """ multiplicand_rows, multiplicand_cols = len(multiplicand), len(multiplicand[0]) multiplier_rows, multiplier_cols = len(multiplier), len(multiplier[0]) if multiplicand_cols != multiplier_rows: raise Exception("Multiplicand matrix not compatible with Multiplier matrix.") result = [[0] * multiplier_cols for _ in range(multiplicand_rows)] for i in range(multiplicand_rows): for j in range(multiplier_cols): for k in range(len(multiplier)): result[i][j] += multiplicand[i][k] * multiplier[k][j] return result ================================================ FILE: algorithms/matrix/rotate_image.py ================================================ """ Rotate Image Rotate an n x n 2D matrix representing an image by 90 degrees clockwise, in-place. First reverse the rows top-to-bottom, then transpose. Reference: https://leetcode.com/problems/rotate-image/ Complexity: Time: O(n^2) Space: O(1) """ from __future__ import annotations def rotate(mat: list[list[int]]) -> list[list[int]]: """Rotate a square matrix 90 degrees clockwise in-place. Args: mat: Square matrix of size n x n. Returns: The same matrix, rotated 90 degrees clockwise. Examples: >>> rotate([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[7, 4, 1], [8, 5, 2], [9, 6, 3]] """ if not mat: return mat mat.reverse() for i in range(len(mat)): for j in range(i): mat[i][j], mat[j][i] = mat[j][i], mat[i][j] return mat ================================================ FILE: algorithms/matrix/search_in_sorted_matrix.py ================================================ """ Search in Sorted Matrix Search for a key in a matrix that is sorted row-wise and column-wise in non-decreasing order. Start from the bottom-left corner and move up or right depending on the comparison. Reference: https://leetcode.com/problems/search-a-2d-matrix-ii/ Complexity: Time: O(m + n) Space: O(1) """ from __future__ import annotations def search_in_a_sorted_matrix( mat: list[list[int]], rows: int, cols: int, key: int ) -> bool: """Search for a key in a row-wise and column-wise sorted matrix. Args: mat: Sorted matrix of size m x n. rows: Number of rows. cols: Number of columns. key: Value to search for. Returns: True if the key is found, False otherwise. Examples: >>> search_in_a_sorted_matrix([[2, 5, 7], [4, 8, 13], [9, 11, 15]], 3, 3, 13) True >>> search_in_a_sorted_matrix([[2, 5, 7], [4, 8, 13], [9, 11, 15]], 3, 3, 6) False """ i, j = rows - 1, 0 while i >= 0 and j < cols: if key == mat[i][j]: return True if key < mat[i][j]: i -= 1 else: j += 1 return False ================================================ FILE: algorithms/matrix/sort_matrix_diagonally.py ================================================ """ Sort Matrix Diagonally Given an m x n matrix of integers, sort each diagonal from top-left to bottom-right in ascending order and return the sorted matrix. Uses a min-heap for each diagonal. Reference: https://leetcode.com/problems/sort-the-matrix-diagonally/ Complexity: Time: O((m + n) * k * log k) where k = min(m, n) Space: O(min(m, n)) """ from __future__ import annotations from heapq import heappop, heappush def sort_diagonally(mat: list[list[int]]) -> list[list[int]]: """Sort each top-left to bottom-right diagonal in ascending order. Args: mat: Matrix of size m x n. Returns: The matrix with each diagonal sorted. Examples: >>> sort_diagonally([[3, 3, 1, 1], [2, 2, 1, 2], [1, 1, 1, 2]]) [[1, 1, 1, 1], [1, 2, 2, 2], [1, 2, 3, 3]] """ if len(mat) == 1 or len(mat[0]) == 1: return mat num_rows = len(mat) num_cols = len(mat[0]) for i in range(num_rows + num_cols - 1): if i + 1 < num_rows: heap: list[int] = [] row = num_rows - (i + 1) col = 0 while row < num_rows: heappush(heap, mat[row][col]) row += 1 col += 1 row = num_rows - (i + 1) col = 0 while heap: mat[row][col] = heappop(heap) row += 1 col += 1 else: heap = [] row = 0 col = i - (num_rows - 1) while col < num_cols and row < num_rows: heappush(heap, mat[row][col]) row += 1 col += 1 row = 0 col = i - (num_rows - 1) while heap: mat[row][col] = heappop(heap) row += 1 col += 1 return mat ================================================ FILE: algorithms/matrix/sparse_dot_vector.py ================================================ """ Sparse Dot Vector Compute the dot product of two large sparse vectors efficiently by converting them to index-value pair representations and merging. Reference: https://leetcode.com/problems/dot-product-of-two-sparse-vectors/ Complexity: Time: O(n) for conversion, O(k) for dot product where k = non-zero count Space: O(k) """ from __future__ import annotations def vector_to_index_value_list( vector: list[float], ) -> list[tuple[int, float]]: """Convert a dense vector to a sparse index-value list. Args: vector: Dense vector of floats. Returns: List of (index, value) tuples for non-zero elements. Examples: >>> vector_to_index_value_list([0.0, 2.0, 0.0, 3.0]) [(1, 2.0), (3, 3.0)] """ return [(i, v) for i, v in enumerate(vector) if v != 0.0] def dot_product( iv_list1: list[tuple[int, float]], iv_list2: list[tuple[int, float]], ) -> float: """Compute the dot product of two sparse index-value lists. Args: iv_list1: First sparse vector as index-value pairs. iv_list2: Second sparse vector as index-value pairs. Returns: Dot product of the two vectors. Examples: >>> dot_product([(0, 1.0), (1, 2.0), (2, 3.0)], [(1, 2.0), (2, 2.0)]) 10.0 """ product = 0 p1 = len(iv_list1) - 1 p2 = len(iv_list2) - 1 while p1 >= 0 and p2 >= 0: i1, v1 = iv_list1[p1] i2, v2 = iv_list2[p2] if i1 < i2: p1 -= 1 elif i2 < i1: p2 -= 1 else: product += v1 * v2 p1 -= 1 p2 -= 1 return product ================================================ FILE: algorithms/matrix/sparse_mul.py ================================================ """ Sparse Matrix Multiplication Given two sparse matrices A and B, return their product A * B. Skips zero elements for efficiency. A's column count must equal B's row count. Reference: https://leetcode.com/problems/sparse-matrix-multiplication/ Complexity: Time: O(m * n * p) worst case, better with sparsity Space: O(m * p) """ from __future__ import annotations def sparse_multiply( mat_a: list[list[int]], mat_b: list[list[int]] ) -> list[list[int]] | None: """Multiply two sparse matrices, skipping zero elements. Args: mat_a: First matrix of size m x n. mat_b: Second matrix of size n x p. Returns: Product matrix of size m x p, or None if either input is None. Raises: Exception: If the matrices have incompatible dimensions. Examples: >>> sparse_multiply([[1, 0, 0], [-1, 0, 3]], [[7, 0, 0], [0, 0, 0], [0, 0, 1]]) [[7, 0, 0], [-7, 0, 3]] """ if mat_a is None or mat_b is None: return None rows_a, cols_a = len(mat_a), len(mat_a[0]) cols_b = len(mat_b[0]) if len(mat_b) != cols_a: raise Exception("A's column number must be equal to B's row number.") result = [[0] * cols_b for _ in range(rows_a)] for i, row in enumerate(mat_a): for k, elem_a in enumerate(row): if elem_a: for j, elem_b in enumerate(mat_b[k]): if elem_b: result[i][j] += elem_a * elem_b return result ================================================ FILE: algorithms/matrix/spiral_traversal.py ================================================ """ Spiral Traversal Return all elements of an m x n matrix in spiral order, traversing right, down, left, and up repeatedly while shrinking the boundaries. Reference: https://leetcode.com/problems/spiral-matrix/ Complexity: Time: O(m * n) Space: O(m * n) """ from __future__ import annotations def spiral_traversal(matrix: list[list[int]]) -> list[int]: """Return matrix elements in spiral order. Args: matrix: 2D list of integers (m x n). Returns: List of elements in spiral order. Examples: >>> spiral_traversal([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [1, 2, 3, 6, 9, 8, 7, 4, 5] """ result: list[int] = [] if len(matrix) == 0: return result row_begin = 0 row_end = len(matrix) - 1 col_begin = 0 col_end = len(matrix[0]) - 1 while row_begin <= row_end and col_begin <= col_end: for i in range(col_begin, col_end + 1): result.append(matrix[row_begin][i]) row_begin += 1 for i in range(row_begin, row_end + 1): result.append(matrix[i][col_end]) col_end -= 1 if row_begin <= row_end: for i in range(col_end, col_begin - 1, -1): result.append(matrix[row_end][i]) row_end -= 1 if col_begin <= col_end: for i in range(row_end, row_begin - 1, -1): result.append(matrix[i][col_begin]) col_begin += 1 return result ================================================ FILE: algorithms/matrix/sudoku_validator.py ================================================ """ Sudoku Validator Validate whether a completed 9x9 Sudoku board is a valid solution. Each row, column, and 3x3 sub-box must contain digits 1-9 without repetition. Boards containing zeroes are considered invalid. Reference: https://en.wikipedia.org/wiki/Sudoku Complexity: Time: O(1) (fixed 9x9 board) Space: O(1) """ from __future__ import annotations from collections import defaultdict def valid_solution_hashtable(board: list[list[int]]) -> bool: """Validate a Sudoku solution using hash tables. Args: board: 9x9 grid of integers (1-9 for valid, 0 for empty). Returns: True if the board is a valid Sudoku solution. Examples: >>> board = [[5,3,4,6,7,8,9,1,2],[6,7,2,1,9,5,3,4,8], ... [1,9,8,3,4,2,5,6,7],[8,5,9,7,6,1,4,2,3], ... [4,2,6,8,5,3,7,9,1],[7,1,3,9,2,4,8,5,6], ... [9,6,1,5,3,7,2,8,4],[2,8,7,4,1,9,6,3,5], ... [3,4,5,2,8,6,1,7,9]] >>> valid_solution_hashtable(board) True """ for i in range(len(board)): row_seen = defaultdict(int) col_seen = defaultdict(int) for j in range(len(board[0])): value_row = board[i][j] value_col = board[j][i] if not value_row or value_col == 0: return False if value_row in row_seen: return False else: row_seen[value_row] += 1 if value_col in col_seen: return False else: col_seen[value_col] += 1 for i in range(3): for j in range(3): grid_sum = 0 for k in range(3): for m in range(3): grid_sum += board[i * 3 + k][j * 3 + m] if grid_sum != 45: return False return True def valid_solution(board: list[list[int]]) -> bool: """Validate a Sudoku solution using sorted comparison. Args: board: 9x9 grid of integers (1-9 for valid, 0 for empty). Returns: True if the board is a valid Sudoku solution. Examples: >>> board = [[5,3,4,6,7,8,9,1,2],[6,7,2,1,9,5,3,4,8], ... [1,9,8,3,4,2,5,6,7],[8,5,9,7,6,1,4,2,3], ... [4,2,6,8,5,3,7,9,1],[7,1,3,9,2,4,8,5,6], ... [9,6,1,5,3,7,2,8,4],[2,8,7,4,1,9,6,3,5], ... [3,4,5,2,8,6,1,7,9]] >>> valid_solution(board) True """ correct = [1, 2, 3, 4, 5, 6, 7, 8, 9] for row in board: if sorted(row) != correct: return False for column in zip(*board, strict=False): if sorted(column) != correct: return False for i in range(3): for j in range(3): region = [] for line in board[i * 3 : (i + 1) * 3]: region += line[j * 3 : (j + 1) * 3] if sorted(region) != correct: return False return True def valid_solution_set(board: list[list[int]]) -> bool: """Validate a Sudoku solution using set comparison. Args: board: 9x9 grid of integers (1-9 for valid, 0 for empty). Returns: True if the board is a valid Sudoku solution. Examples: >>> board = [[5,3,4,6,7,8,9,1,2],[6,7,2,1,9,5,3,4,8], ... [1,9,8,3,4,2,5,6,7],[8,5,9,7,6,1,4,2,3], ... [4,2,6,8,5,3,7,9,1],[7,1,3,9,2,4,8,5,6], ... [9,6,1,5,3,7,2,8,4],[2,8,7,4,1,9,6,3,5], ... [3,4,5,2,8,6,1,7,9]] >>> valid_solution_set(board) True """ valid = set(range(1, 10)) for row in board: if set(row) != valid: return False for col in [[row[i] for row in board] for i in range(9)]: if set(col) != valid: return False for x in range(3): for y in range(3): if ( set( sum( [ row[x * 3 : (x + 1) * 3] for row in board[y * 3 : (y + 1) * 3] ], [], ) ) != valid ): return False return True ================================================ FILE: algorithms/matrix/sum_sub_squares.py ================================================ """ Sum of Sub-Squares Given a square matrix of size n x n and an integer k, compute the sum of all k x k sub-squares and return the results as a matrix. Reference: https://www.geeksforgeeks.org/given-n-x-n-square-matrix-find-sum-sub-squares-size-k-x-k/ Complexity: Time: O(n^2 * k^2) Space: O((n - k + 1)^2) """ from __future__ import annotations def sum_sub_squares(matrix: list[list[int]], k: int) -> list[list[int]] | None: """Compute sums of all k x k sub-squares in the matrix. Args: matrix: Square matrix of size n x n. k: Side length of the sub-squares. Returns: Matrix of sub-square sums, or None if k > n. Examples: >>> sum_sub_squares([[1, 1, 1], [2, 2, 2], [3, 3, 3]], 2) [[6, 6], [9, 9]] """ size = len(matrix) if k > size: return None result_size = size - k + 1 result = [[0] * result_size for _ in range(result_size)] for i in range(result_size): for j in range(result_size): total = 0 for p in range(i, k + i): for q in range(j, k + j): total += matrix[p][q] result[i][j] = total return result ================================================ FILE: algorithms/py.typed ================================================ ================================================ FILE: algorithms/queue/__init__.py ================================================ """Queue-based algorithm implementations.""" from __future__ import annotations from algorithms.data_structures.priority_queue import PriorityQueue, PriorityQueueNode from algorithms.data_structures.queue import ( AbstractQueue, ArrayQueue, LinkedListQueue, QueueNode, ) from .max_sliding_window import max_sliding_window from .moving_average import MovingAverage from .reconstruct_queue import reconstruct_queue from .zigzagiterator import ZigZagIterator __all__ = [ "AbstractQueue", "ArrayQueue", "LinkedListQueue", "MovingAverage", "PriorityQueue", "PriorityQueueNode", "QueueNode", "ZigZagIterator", "max_sliding_window", "reconstruct_queue", ] ================================================ FILE: algorithms/queue/max_sliding_window.py ================================================ """ Max Sliding Window (Deque-based) Given an array and a window size k, find the maximum element in each sliding window using a monotonic deque that stores indices of elements in decreasing order of their values. Reference: https://leetcode.com/problems/sliding-window-maximum/ Complexity: Time: O(n) Space: O(k) """ from __future__ import annotations import collections def max_sliding_window(arr: list[int], k: int) -> list[int]: """Find the maximum in each sliding window of size k. Uses a deque to maintain indices of useful elements in decreasing order. The front of the deque always holds the index of the current window maximum. Args: arr: Input array of integers. k: Window size. Returns: List of maximum values for each window position. Examples: >>> max_sliding_window([1, 3, -1, -3, 5, 3, 6, 7], 3) [3, 3, 5, 5, 6, 7] """ index_deque: collections.deque[int] = collections.deque() result: list[int] = [] for i, value in enumerate(arr): while index_deque and arr[index_deque[-1]] < value: index_deque.pop() index_deque.append(i) if index_deque[0] == i - k: index_deque.popleft() if i >= k - 1: result.append(arr[index_deque[0]]) return result ================================================ FILE: algorithms/queue/moving_average.py ================================================ """ Moving Average from Data Stream Calculate the moving average of integers in a sliding window of fixed size using a bounded deque. Reference: https://leetcode.com/problems/moving-average-from-data-stream/ Complexity: Time: O(1) per call to next Space: O(size) """ from __future__ import annotations from collections import deque class MovingAverage: """Computes the moving average over a sliding window. Examples: >>> m = MovingAverage(3) >>> m.next(1) 1.0 >>> m.next(10) 5.5 """ def __init__(self, size: int) -> None: """Initialize the moving average calculator. Args: size: The window size for the moving average. """ self.queue: deque[int] = deque(maxlen=size) def next(self, val: int) -> float: """Add a value and return the current moving average. Args: val: The next integer in the data stream. Returns: The current moving average as a float. """ self.queue.append(val) return sum(self.queue) / len(self.queue) ================================================ FILE: algorithms/queue/reconstruct_queue.py ================================================ """ Reconstruct Queue by Height Given a list of people described by (height, k) pairs where k is the number of taller-or-equal people in front, reconstruct the queue by sorting and inserting. Reference: https://leetcode.com/problems/queue-reconstruction-by-height/ Complexity: Time: O(n^2) Space: O(n) """ from __future__ import annotations def reconstruct_queue(people: list[list[int]]) -> list[list[int]]: """Reconstruct the queue from (height, k) pairs. Args: people: List of [height, k] pairs. Returns: The reconstructed queue as a list of [height, k] pairs. Examples: >>> reconstruct_queue([[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]) [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]] """ queue: list[list[int]] = [] people.sort(key=lambda x: (-x[0], x[1])) for height, count in people: queue.insert(count, [height, count]) return queue ================================================ FILE: algorithms/queue/zigzagiterator.py ================================================ """ Zigzag Iterator Interleave elements from two lists in a zigzag fashion. Elements are yielded alternately from each list until both are exhausted. Reference: https://leetcode.com/problems/zigzag-iterator/ Complexity: Time: O(n) total across all next() calls Space: O(n) """ from __future__ import annotations from collections import deque class ZigZagIterator: """Iterator that interleaves elements from two lists. Examples: >>> it = ZigZagIterator([1, 2], [3, 4, 5]) >>> it.next() 1 >>> it.next() 3 """ def __init__(self, v1: list[int], v2: list[int]) -> None: """Initialize with two lists. Args: v1: First input list. v2: Second input list. """ self.queue: deque[list[int]] = deque(lst for lst in (v1, v2) if lst) def next(self) -> int: """Return the next element in zigzag order. Returns: The next interleaved element. """ current_list = self.queue.popleft() ret = current_list.pop(0) if current_list: self.queue.append(current_list) return ret def has_next(self) -> bool: """Check if there are more elements. Returns: True if elements remain, False otherwise. """ return bool(self.queue) ================================================ FILE: algorithms/searching/__init__.py ================================================ """Collection of search algorithms: finding the needle in a haystack.""" from algorithms.searching.binary_search import binary_search, binary_search_recur from algorithms.searching.exponential_search import exponential_search from algorithms.searching.find_min_rotate import find_min_rotate, find_min_rotate_recur from algorithms.searching.first_occurrence import first_occurrence from algorithms.searching.generalized_binary_search import binary_search_first_true from algorithms.searching.interpolation_search import interpolation_search from algorithms.searching.jump_search import jump_search from algorithms.searching.last_occurrence import last_occurrence from algorithms.searching.linear_search import linear_search from algorithms.searching.next_greatest_letter import ( next_greatest_letter, next_greatest_letter_v1, next_greatest_letter_v2, ) from algorithms.searching.search_insert import search_insert from algorithms.searching.search_range import search_range from algorithms.searching.search_rotate import search_rotate, search_rotate_recur from algorithms.searching.sentinel_search import sentinel_search from algorithms.searching.ternary_search import ternary_search from algorithms.searching.two_sum import two_sum, two_sum1, two_sum2 __all__ = [ "binary_search", "binary_search_first_true", "binary_search_recur", "find_min_rotate", "find_min_rotate_recur", "first_occurrence", "interpolation_search", "jump_search", "last_occurrence", "linear_search", "next_greatest_letter", "next_greatest_letter_v1", "next_greatest_letter_v2", "search_insert", "search_range", "search_rotate", "search_rotate_recur", "ternary_search", "two_sum", "two_sum1", "two_sum2", "exponential_search", "sentinel_search", ] ================================================ FILE: algorithms/searching/binary_search.py ================================================ """ Binary Search Search for an element in a sorted array by repeatedly dividing the search interval in half. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(1) best / O(log n) average / O(log n) worst Space: O(1) iterative, O(log n) recursive """ from __future__ import annotations def binary_search(array: list[int], query: int) -> int: """Search for *query* in a sorted *array* using iterative binary search. Args: array: Sorted list of integers in ascending order. query: Value to search for. Returns: Index of *query* in *array*, or -1 if not found. Examples: >>> binary_search([1, 2, 3, 4, 5], 3) 2 >>> binary_search([1, 2, 3, 4, 5], 6) -1 """ low, high = 0, len(array) - 1 while low <= high: mid = low + (high - low) // 2 val = array[mid] if val == query: return mid if val < query: low = mid + 1 else: high = mid - 1 return -1 def binary_search_recur(array: list[int], low: int, high: int, val: int) -> int: """Search for *val* in a sorted *array* using recursive binary search. Args: array: Sorted list of integers in ascending order. low: Lower bound index of the current search range. high: Upper bound index of the current search range. val: Value to search for. Returns: Index of *val* in *array*, or -1 if not found. Examples: >>> binary_search_recur([1, 2, 3, 4, 5], 0, 4, 3) 2 >>> binary_search_recur([1, 2, 3, 4, 5], 0, 4, 6) -1 """ if low > high: return -1 mid = low + (high - low) // 2 if val < array[mid]: return binary_search_recur(array, low, mid - 1, val) if val > array[mid]: return binary_search_recur(array, mid + 1, high, val) return mid ================================================ FILE: algorithms/searching/exponential_search.py ================================================ """Exponential search — locate an element in a sorted array. First finds a range where the target may lie by doubling the index, then performs binary search within that range. Useful when the target is near the beginning of a large or unbounded list. Time: O(log i) where i is the index of the target. Inspired by PR #867 (yuviii99). """ from __future__ import annotations def exponential_search(arr: list[int], target: int) -> int: """Return the index of *target* in sorted *arr*, or -1 if absent.""" if not arr: return -1 if arr[0] == target: return 0 bound = 1 while bound < len(arr) and arr[bound] <= target: bound *= 2 low = bound // 2 high = min(bound, len(arr) - 1) while low <= high: mid = (low + high) // 2 if arr[mid] == target: return mid elif arr[mid] < target: low = mid + 1 else: high = mid - 1 return -1 ================================================ FILE: algorithms/searching/find_min_rotate.py ================================================ """ Find Minimum in Rotated Sorted Array Find the minimum element in a sorted array that has been rotated at some unknown pivot. Assumes no duplicates exist in the array. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(1) best / O(log n) average / O(log n) worst Space: O(1) iterative, O(log n) recursive """ from __future__ import annotations def find_min_rotate(array: list[int]) -> int: """Find the minimum element in a rotated sorted array (iterative). Args: array: A sorted list of unique integers that has been rotated. Returns: The minimum value in the array. Examples: >>> find_min_rotate([4, 5, 6, 7, 0, 1, 2]) 0 >>> find_min_rotate([1, 2, 3]) 1 """ low = 0 high = len(array) - 1 while low < high: mid = (low + high) // 2 if array[mid] > array[high]: low = mid + 1 else: high = mid return array[low] def find_min_rotate_recur(array: list[int], low: int, high: int) -> int: """Find the minimum element in a rotated sorted array (recursive). Args: array: A sorted list of unique integers that has been rotated. low: Lower bound index of the current search range. high: Upper bound index of the current search range. Returns: The minimum value in the array. Examples: >>> find_min_rotate_recur([4, 5, 6, 7, 0, 1, 2], 0, 6) 0 """ mid = (low + high) // 2 if mid == low: return array[low] if array[mid] > array[high]: return find_min_rotate_recur(array, mid + 1, high) return find_min_rotate_recur(array, low, mid) ================================================ FILE: algorithms/searching/first_occurrence.py ================================================ """ First Occurrence Find the index of the first occurrence of a target value in a sorted array using binary search. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(1) best / O(log n) average / O(log n) worst Space: O(1) """ from __future__ import annotations def first_occurrence(array: list[int], query: int) -> int: """Find the index of the first occurrence of *query* in *array*. Args: array: Sorted list of integers in ascending order. query: Value to search for. Returns: Index of the first occurrence of *query*, or -1 if not found. Examples: >>> first_occurrence([1, 2, 2, 2, 3, 4], 2) 1 >>> first_occurrence([1, 2, 3, 4, 5], 6) -1 """ low, high = 0, len(array) - 1 while low <= high: mid = low + (high - low) // 2 if low == high: break if array[mid] < query: low = mid + 1 else: high = mid if array[low] == query: return low return -1 ================================================ FILE: algorithms/searching/generalized_binary_search.py ================================================ """ Generalized Binary Search Find the smallest value in a numeric range for which a monotonic boolean predicate evaluates to True. Instead of searching for a specific value in an array, this version accepts an arbitrary predicate, allowing the same binary search logic to be reused across many problem domains. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(log n) Space: O(1) """ from __future__ import annotations from collections.abc import Callable def binary_search_first_true( low: int, high: int, predicate: Callable[[int], bool], ) -> int: """Find the smallest *x* in [low, high] where *predicate(x)* is True. The predicate must be monotonic: once it returns True for some value, it must return True for all larger values in the range. Args: low: Lower bound of the search range (inclusive). high: Upper bound of the search range (inclusive). predicate: A monotonic boolean function. Returns: The smallest *x* for which *predicate(x)* is True, or -1 if no such value exists in the range. Examples: >>> binary_search_first_true(0, 10, lambda x: x >= 7) 7 >>> binary_search_first_true(0, 10, lambda x: x * x >= 25) 5 >>> binary_search_first_true(0, 5, lambda x: x > 10) -1 """ result = -1 while low <= high: mid = low + (high - low) // 2 if predicate(mid): result = mid high = mid - 1 else: low = mid + 1 return result if __name__ == "__main__": print(binary_search_first_true(0, 10, lambda x: x >= 7)) # 7 print(binary_search_first_true(0, 10, lambda x: x * x >= 25)) # 5 print(binary_search_first_true(0, 5, lambda x: x > 10)) # -1 ================================================ FILE: algorithms/searching/interpolation_search.py ================================================ """ Interpolation Search Search for a target value in a uniformly distributed sorted array by estimating the position of the target using linear interpolation. Reference: https://en.wikipedia.org/wiki/Interpolation_search Complexity: Time: O(1) best / O(log log n) average / O(n) worst Space: O(1) """ from __future__ import annotations def interpolation_search(array: list[int], search_key: int) -> int: """Search for *search_key* in a sorted *array* using interpolation search. Args: array: Sorted list of integers in ascending order. search_key: Value to search for. Returns: Index of *search_key* in *array*, or -1 if not found. Examples: >>> interpolation_search([-25, -12, -1, 10, 12, 15, 20, 41, 55], -1) 2 >>> interpolation_search([5, 10, 12, 14, 17, 20, 21], 55) -1 >>> interpolation_search([5, 10, 12, 14, 17, 20, 21], -5) -1 """ high = len(array) - 1 low = 0 while (low <= high) and (array[low] <= search_key <= array[high]): pos = low + int( ((search_key - array[low]) * (high - low)) / (array[high] - array[low]) ) if array[pos] == search_key: return pos if array[pos] < search_key: low = pos + 1 else: high = pos - 1 return -1 if __name__ == "__main__": import doctest doctest.testmod() ================================================ FILE: algorithms/searching/jump_search.py ================================================ """ Jump Search Search for a target value in a sorted array by jumping ahead in fixed-size blocks and then performing a linear search within the identified block. Reference: https://en.wikipedia.org/wiki/Jump_search Complexity: Time: O(1) best / O(sqrt n) average / O(sqrt n) worst Space: O(1) """ from __future__ import annotations import math def jump_search(array: list[int], target: int) -> int: """Search for *target* in a sorted *array* using jump search. Args: array: Sorted list of integers in ascending order. target: Value to search for. Returns: Index of *target* in *array*, or -1 if not found. Examples: >>> jump_search([1, 2, 3, 4, 5, 6, 7, 8, 9], 5) 4 >>> jump_search([1, 2, 3, 4, 5], 0) -1 """ length = len(array) block_size = int(math.sqrt(length)) block_prev = 0 block = block_size if array[length - 1] < target: return -1 while block <= length and array[block - 1] < target: block_prev = block block += block_size while array[block_prev] < target: block_prev += 1 if block_prev == min(block, length): return -1 if array[block_prev] == target: return block_prev return -1 ================================================ FILE: algorithms/searching/last_occurrence.py ================================================ """ Last Occurrence Find the index of the last occurrence of a target value in a sorted array using binary search. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(1) best / O(log n) average / O(log n) worst Space: O(1) """ from __future__ import annotations def last_occurrence(array: list[int], query: int) -> int: """Find the index of the last occurrence of *query* in *array*. Args: array: Sorted list of integers in ascending order. query: Value to search for. Returns: Index of the last occurrence of *query*, or -1 if not found. Examples: >>> last_occurrence([1, 2, 2, 2, 3, 4], 2) 3 >>> last_occurrence([1, 2, 3, 4, 5], 6) -1 """ low, high = 0, len(array) - 1 while low <= high: mid = (high + low) // 2 if (array[mid] == query and mid == len(array) - 1) or ( array[mid] == query and array[mid + 1] > query ): return mid if array[mid] <= query: low = mid + 1 else: high = mid - 1 return -1 ================================================ FILE: algorithms/searching/linear_search.py ================================================ """ Linear Search Search for a target value in an array by checking every element sequentially. The array does not need to be sorted. Reference: https://en.wikipedia.org/wiki/Linear_search Complexity: Time: O(1) best / O(n) average / O(n) worst Space: O(1) """ from __future__ import annotations def linear_search(array: list[int], query: int) -> int: """Search for *query* in *array* using linear search. Args: array: List of integers (order does not matter). query: Value to search for. Returns: Index of *query* in *array*, or -1 if not found. Examples: >>> linear_search([5, 1, 3, 2, 4], 3) 2 >>> linear_search([5, 1, 3, 2, 4], 6) -1 """ for i, value in enumerate(array): if value == query: return i return -1 ================================================ FILE: algorithms/searching/next_greatest_letter.py ================================================ """ Next Greatest Letter Given a sorted list of lowercase letters and a target letter, find the smallest letter in the list that is larger than the target. Letters wrap around, so if the target is greater than or equal to the last letter the answer is the first letter. Reference: https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/ Complexity: next_greatest_letter -- O(log n) time, O(1) space (bisect) next_greatest_letter_v1 -- O(log n) time, O(1) space (manual binary search) next_greatest_letter_v2 -- O(n) time, O(1) space (brute force) """ from __future__ import annotations import bisect def next_greatest_letter(letters: list[str], target: str) -> str: """Find the smallest letter greater than *target* using ``bisect``. Args: letters: Sorted list of lowercase letters. target: The letter to exceed. Returns: The smallest letter in *letters* that is strictly greater than *target*, wrapping around if necessary. Examples: >>> next_greatest_letter(["c", "f", "j"], "a") 'c' >>> next_greatest_letter(["c", "f", "j"], "c") 'f' """ index = bisect.bisect(letters, target) return letters[index % len(letters)] def next_greatest_letter_v1(letters: list[str], target: str) -> str: """Find the smallest letter greater than *target* using binary search. Args: letters: Sorted list of lowercase letters. target: The letter to exceed. Returns: The smallest letter in *letters* that is strictly greater than *target*, wrapping around if necessary. Examples: >>> next_greatest_letter_v1(["c", "f", "j"], "d") 'f' """ if letters[0] > target: return letters[0] if letters[len(letters) - 1] <= target: return letters[0] left, right = 0, len(letters) - 1 while left <= right: mid = left + (right - left) // 2 if letters[mid] > target: right = mid - 1 else: left = mid + 1 return letters[left] def next_greatest_letter_v2(letters: list[str], target: str) -> str: """Find the smallest letter greater than *target* using brute force. Args: letters: Sorted list of lowercase letters. target: The letter to exceed. Returns: The smallest letter in *letters* that is strictly greater than *target*, wrapping around if necessary. Examples: >>> next_greatest_letter_v2(["c", "f", "j"], "d") 'f' """ for letter in letters: if letter > target: return letter return letters[0] ================================================ FILE: algorithms/searching/search_insert.py ================================================ """ Search Insert Position Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(1) best / O(log n) average / O(log n) worst Space: O(1) """ from __future__ import annotations def search_insert(array: list[int], val: int) -> int: """Return the index of *val* or the position where it should be inserted. Args: array: Sorted list of integers in ascending order. val: Value to search for or insert. Returns: Index of *val* in *array*, or the index at which *val* would be inserted to keep *array* sorted. Examples: >>> search_insert([1, 3, 5, 6], 5) 2 >>> search_insert([1, 3, 5, 6], 2) 1 >>> search_insert([1, 3, 5, 6], 7) 4 >>> search_insert([1, 3, 5, 6], 0) 0 """ low = 0 high = len(array) - 1 while low <= high: mid = low + (high - low) // 2 if val > array[mid]: low = mid + 1 else: high = mid - 1 return low ================================================ FILE: algorithms/searching/search_range.py ================================================ """ Search Range Given a sorted array of integers and a target value, find the starting and ending positions of the target. Returns [-1, -1] if the target is not found. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(log n + k) where k is the number of occurrences in the worst case for the backward scan, O(log n) for the initial search Space: O(1) """ from __future__ import annotations def search_range(nums: list[int], target: int) -> list[int]: """Find the first and last positions of *target* in *nums*. Args: nums: Sorted list of integers in ascending order. target: Value to search for. Returns: A two-element list ``[first, last]`` of indices, or ``[-1, -1]`` if *target* is not present. Examples: >>> search_range([5, 7, 7, 8, 8, 8, 10], 8) [3, 5] >>> search_range([5, 7, 7, 8, 8, 8, 10], 11) [-1, -1] """ low = 0 high = len(nums) - 1 while low < high: mid = low + (high - low) // 2 if target <= nums[mid]: high = mid else: low = mid + 1 for j in range(len(nums) - 1, -1, -1): if nums[j] == target: return [low, j] return [-1, -1] ================================================ FILE: algorithms/searching/search_rotate.py ================================================ """ Search in Rotated Sorted Array Search for a target value in an array that was sorted in ascending order and then rotated at some unknown pivot. One half of the array is always in sorted order; we identify that half and decide which side to search. Reference: https://en.wikipedia.org/wiki/Binary_search_algorithm Complexity: Time: O(1) best / O(log n) average / O(log n) worst Space: O(1) iterative, O(log n) recursive """ from __future__ import annotations def search_rotate(array: list[int], val: int) -> int: """Search for *val* in a rotated sorted *array* (iterative). Args: array: A sorted list of integers that has been rotated at an unknown pivot. val: Value to search for. Returns: Index of *val* in *array*, or -1 if not found. Examples: >>> search_rotate([4, 5, 6, 7, 0, 1, 2], 0) 4 >>> search_rotate([4, 5, 6, 7, 0, 1, 2], 3) -1 """ low, high = 0, len(array) - 1 while low <= high: mid = (low + high) // 2 if val == array[mid]: return mid if array[low] <= array[mid]: if array[low] <= val <= array[mid]: high = mid - 1 else: low = mid + 1 else: if array[mid] <= val <= array[high]: low = mid + 1 else: high = mid - 1 return -1 def search_rotate_recur( array: list[int], low: int, high: int, val: int, ) -> int: """Search for *val* in a rotated sorted *array* (recursive). Args: array: A sorted list of integers that has been rotated at an unknown pivot. low: Lower bound index of the current search range. high: Upper bound index of the current search range. val: Value to search for. Returns: Index of *val* in *array*, or -1 if not found. Examples: >>> search_rotate_recur([4, 5, 6, 7, 0, 1, 2], 0, 6, 0) 4 """ if low >= high: return -1 mid = (low + high) // 2 if val == array[mid]: return mid if array[low] <= array[mid]: if array[low] <= val <= array[mid]: return search_rotate_recur(array, low, mid - 1, val) return search_rotate_recur(array, mid + 1, high, val) if array[mid] <= val <= array[high]: return search_rotate_recur(array, mid + 1, high, val) return search_rotate_recur(array, low, mid - 1, val) ================================================ FILE: algorithms/searching/sentinel_search.py ================================================ """Sentinel linear search — a small optimisation over naive linear search. By placing the target at the end of the array (as a sentinel), we can remove the bounds check from the inner loop, roughly halving comparisons. Time: O(n) — same asymptotic complexity but fewer comparisons in practice. Inspired by PR #907 (Abhishek-Pashte). """ from __future__ import annotations def sentinel_search(arr: list[int], target: int) -> int: """Return the index of *target* in *arr*, or -1 if absent. Modifies (and restores) the last element temporarily. """ n = len(arr) if n == 0: return -1 last = arr[-1] arr[-1] = target i = 0 while arr[i] != target: i += 1 arr[-1] = last if i < n - 1 or arr[-1] == target: return i return -1 ================================================ FILE: algorithms/searching/ternary_search.py ================================================ """ Ternary Search Search for a target value in a sorted array by dividing the search range into three equal parts instead of two. At each step two midpoints are computed and the search range is narrowed to one third. Reference: https://en.wikipedia.org/wiki/Ternary_search Complexity: Time: O(1) best / O(log3 n) average / O(log3 n) worst Space: O(1) """ from __future__ import annotations def ternary_search(left: int, right: int, key: int, array: list[int]) -> int: """Search for *key* in a sorted *array* using ternary search. Args: left: Lower bound index of the search range (inclusive). right: Upper bound index of the search range (inclusive). key: Value to search for. array: Sorted list of integers in ascending order. Returns: Index of *key* in *array*, or -1 if not found within the range. Examples: >>> ternary_search(0, 8, 5, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 4 >>> ternary_search(0, 4, 0, [1, 2, 3, 4, 5]) -1 """ while right >= left: mid1 = left + (right - left) // 3 mid2 = right - (right - left) // 3 if key == array[mid1]: return mid1 if key == array[mid2]: return mid2 if key < array[mid1]: right = mid1 - 1 elif key > array[mid2]: left = mid2 + 1 else: left = mid1 + 1 right = mid2 - 1 return -1 ================================================ FILE: algorithms/searching/two_sum.py ================================================ """ Two Sum Given a sorted array of integers and a target sum, find the 1-based indices of the two numbers that add up to the target. Three approaches are provided: binary search, hash table, and two pointers. Reference: https://en.wikipedia.org/wiki/Subset_sum_problem Complexity: two_sum -- O(n log n) time, O(1) space (binary search) two_sum1 -- O(n) time, O(n) space (hash table) two_sum2 -- O(n) time, O(1) space (two pointers) """ from __future__ import annotations def two_sum(numbers: list[int], target: int) -> list[int] | None: """Find two numbers that add up to *target* using binary search. Args: numbers: Sorted list of integers in ascending order. target: Desired sum of the two numbers. Returns: A list of two 1-based indices ``[i, j]`` such that ``numbers[i-1] + numbers[j-1] == target``, or ``None`` if no pair exists. Examples: >>> two_sum([2, 7, 11, 15], 9) [1, 2] >>> two_sum([1, 2, 3], 7) is None True """ for i, number in enumerate(numbers): second_val = target - number low, high = i + 1, len(numbers) - 1 while low <= high: mid = low + (high - low) // 2 if second_val == numbers[mid]: return [i + 1, mid + 1] if second_val > numbers[mid]: low = mid + 1 else: high = mid - 1 return None def two_sum1(numbers: list[int], target: int) -> list[int] | None: """Find two numbers that add up to *target* using a hash table. Args: numbers: List of integers (need not be sorted). target: Desired sum of the two numbers. Returns: A list of two 1-based indices ``[i, j]`` such that ``numbers[i-1] + numbers[j-1] == target``, or ``None`` if no pair exists. Examples: >>> two_sum1([2, 7, 11, 15], 9) [1, 2] """ seen: dict[int, int] = {} for i, num in enumerate(numbers): if target - num in seen: return [seen[target - num] + 1, i + 1] seen[num] = i return None def two_sum2(numbers: list[int], target: int) -> list[int] | None: """Find two numbers that add up to *target* using two pointers. Args: numbers: Sorted list of integers in ascending order. target: Desired sum of the two numbers. Returns: A list of two 1-based indices ``[i, j]`` such that ``numbers[i-1] + numbers[j-1] == target``, or ``None`` if no pair exists. Examples: >>> two_sum2([2, 7, 11, 15], 9) [1, 2] """ left = 0 right = len(numbers) - 1 while left < right: current_sum = numbers[left] + numbers[right] if current_sum == target: return [left + 1, right + 1] if current_sum > target: right = right - 1 else: left = left + 1 return None ================================================ FILE: algorithms/set/__init__.py ================================================ from .find_keyboard_row import find_keyboard_row from .randomized_set import RandomizedSet from .set_covering import greedy_set_cover, optimal_set_cover __all__ = [ "RandomizedSet", "find_keyboard_row", "greedy_set_cover", "optimal_set_cover", ] ================================================ FILE: algorithms/set/find_keyboard_row.py ================================================ """ Keyboard Row Filter Given a list of words, return the words that can be typed using letters from only one row of an American QWERTY keyboard. Reference: https://leetcode.com/problems/keyboard-row/description/ Complexity: Time: O(n * m) where n is the number of words and m is average word length Space: O(n) """ from __future__ import annotations _KEYBOARD_ROWS: list[set[str]] = [ set("qwertyuiop"), set("asdfghjkl"), set("zxcvbnm"), ] def find_keyboard_row(words: list[str]) -> list[str]: """Return words that can be typed using one keyboard row. Args: words: A list of words to check. Returns: A list of words each typable on a single keyboard row. Examples: >>> find_keyboard_row(["Hello", "Alaska", "Dad", "Peace"]) ['Alaska', 'Dad'] """ result: list[str] = [] for word in words: for row in _KEYBOARD_ROWS: if set(word.lower()).issubset(row): result.append(word) return result ================================================ FILE: algorithms/set/randomized_set.py ================================================ """ Randomized Set A data structure that supports insert, remove, and get-random-element operations, all in average O(1) time. Reference: https://leetcode.com/problems/insert-delete-getrandom-o1/ Complexity: Time: O(1) average for insert, remove, and random_element Space: O(n) """ from __future__ import annotations import random class RandomizedSet: """A set supporting O(1) insert, remove, and random access.""" def __init__(self) -> None: self.elements: list[int] = [] self.index_map: dict[int, int] = {} def insert(self, new_one: int) -> None: """Insert a value into the set if not already present. Args: new_one: The value to insert. """ if new_one in self.index_map: return self.index_map[new_one] = len(self.elements) self.elements.append(new_one) def remove(self, old_one: int) -> None: """Remove a value from the set if present. Args: old_one: The value to remove. """ if old_one not in self.index_map: return index = self.index_map[old_one] last = self.elements.pop() self.index_map.pop(old_one) if index == len(self.elements): return self.elements[index] = last self.index_map[last] = index def random_element(self) -> int: """Return a random element from the set. Returns: A randomly chosen element. """ return random.choice(self.elements) ================================================ FILE: algorithms/set/set_covering.py ================================================ """ Set Cover Problem Given a universe U of n elements, a collection S of subsets of U, and a cost for each subset, find the minimum-cost sub-collection that covers all of U. Reference: https://en.wikipedia.org/wiki/Set_cover_problem Complexity: optimal_set_cover: Time: O(2^m * n) where m is the number of subsets Space: O(2^m) greedy_set_cover: Time: O(m * n) Space: O(n) """ from __future__ import annotations from itertools import chain, combinations def _powerset(iterable: list[str]) -> chain[tuple[str, ...]]: """Generate all subsets of the iterable. Args: iterable: Input collection. Returns: An iterator over all subsets (the power set). """ items = list(iterable) return chain.from_iterable(combinations(items, r) for r in range(len(items) + 1)) def optimal_set_cover( universe: set[int], subsets: dict[str, set[int]], costs: dict[str, int], ) -> tuple[str, ...] | None: """Find the minimum-cost exact set cover via brute force. Warning: O(2^m) complexity -- do not use on large inputs. Args: universe: The set of all elements to cover. subsets: Mapping of subset name to its elements. costs: Mapping of subset name to its cost. Returns: A tuple of subset names forming the optimal cover, or None if no cover exists. Examples: >>> universe = {1, 2, 3, 4, 5} >>> subsets = {'S1': {4, 1, 3}, 'S2': {2, 5}, 'S3': {1, 4, 3, 2}} >>> costs = {'S1': 5, 'S2': 10, 'S3': 3} >>> optimal_set_cover(universe, subsets, costs) ('S2', 'S3') """ pset = _powerset(list(subsets.keys())) best_set: tuple[str, ...] | None = None best_cost = float("inf") for subset in pset: covered: set[int] = set() cost = 0 for name in subset: covered.update(subsets[name]) cost += costs[name] if len(covered) == len(universe) and cost < best_cost: best_set = subset best_cost = cost return best_set def greedy_set_cover( universe: set[int], subsets: dict[str, set[int]], costs: dict[str, int], ) -> list[str] | None: """Find an approximate set cover using a greedy approach. Args: universe: The set of all elements to cover. subsets: Mapping of subset name to its elements. costs: Mapping of subset name to its cost. Returns: A list of subset names forming the greedy cover, or None if the subsets do not cover the universe. Examples: >>> universe = {1, 2, 3, 4, 5} >>> subsets = {'S1': {4, 1, 3}, 'S2': {2, 5}, 'S3': {1, 4, 3, 2}} >>> costs = {'S1': 5, 'S2': 10, 'S3': 3} >>> greedy_set_cover(universe, subsets, costs) ['S3', 'S2'] """ all_elements = set(e for s in subsets.values() for e in s) if all_elements != universe: return None covered: set[int] = set() cover_sets: list[str] = [] while covered != universe: min_cost_elem_ratio = float("inf") min_set: str | None = None for name, elements in subsets.items(): new_elements = len(elements - covered) if new_elements != 0: cost_elem_ratio = costs[name] / new_elements if cost_elem_ratio < min_cost_elem_ratio: min_cost_elem_ratio = cost_elem_ratio min_set = name cover_sets.append(min_set) covered |= subsets[min_set] return cover_sets ================================================ FILE: algorithms/sorting/__init__.py ================================================ """Sorting algorithms.""" from algorithms.sorting.bead_sort import bead_sort from algorithms.sorting.bitonic_sort import bitonic_sort from algorithms.sorting.bogo_sort import bogo_sort from algorithms.sorting.bubble_sort import bubble_sort from algorithms.sorting.bucket_sort import bucket_sort from algorithms.sorting.cocktail_shaker_sort import cocktail_shaker_sort from algorithms.sorting.comb_sort import comb_sort from algorithms.sorting.counting_sort import counting_sort from algorithms.sorting.cycle_sort import cycle_sort from algorithms.sorting.exchange_sort import exchange_sort from algorithms.sorting.gnome_sort import gnome_sort from algorithms.sorting.heap_sort import max_heap_sort, min_heap_sort from algorithms.sorting.insertion_sort import insertion_sort from algorithms.sorting.meeting_rooms import can_attend_meetings from algorithms.sorting.merge_sort import merge_sort from algorithms.sorting.pancake_sort import pancake_sort from algorithms.sorting.pigeonhole_sort import pigeonhole_sort from algorithms.sorting.quick_sort import quick_sort from algorithms.sorting.radix_sort import radix_sort from algorithms.sorting.selection_sort import selection_sort from algorithms.sorting.shell_sort import shell_sort from algorithms.sorting.sort_colors import sort_colors from algorithms.sorting.stooge_sort import stooge_sort from algorithms.sorting.wiggle_sort import wiggle_sort __all__ = [ "bead_sort", "bitonic_sort", "bogo_sort", "bubble_sort", "bucket_sort", "can_attend_meetings", "cocktail_shaker_sort", "comb_sort", "counting_sort", "cycle_sort", "exchange_sort", "gnome_sort", "insertion_sort", "max_heap_sort", "merge_sort", "min_heap_sort", "pancake_sort", "pigeonhole_sort", "quick_sort", "radix_sort", "selection_sort", "shell_sort", "sort_colors", "stooge_sort", "wiggle_sort", ] ================================================ FILE: algorithms/sorting/bead_sort.py ================================================ """ Bead Sort Bead sort (also known as gravity sort) simulates how beads settle under gravity on an abacus. It only works with non-negative integers. Reference: https://en.wikipedia.org/wiki/Bead_sort Complexity: Time: O(n) best / O(n * max_value) average / O(n * max_value) worst Space: O(n * max_value) """ from __future__ import annotations def bead_sort(array: list[int]) -> list[int]: """Sort an array of non-negative integers using bead sort. Args: array: List of non-negative integers to sort. Returns: A sorted list. Raises: ValueError: If any element is negative. Examples: >>> bead_sort([6, 3, 4, 1, 5, 2]) [1, 2, 3, 4, 5, 6] """ if any(num < 0 for num in array): raise ValueError("Bead sort only works with non-negative integers.") max_value = max(array) if array else 0 grid = [[0] * len(array) for _ in range(max_value)] # Drop beads (place beads in columns) for col, num in enumerate(array): for row in range(num): grid[row][col] = 1 # Let the beads "fall" (count beads in each row) for row in grid: bead_count = sum(row) for col in range(len(array)): row[col] = 1 if col < bead_count else 0 # Read sorted values from the grid (rightmost column has fewest beads) n = len(array) sorted_array = [0] * n for col in range(n): sorted_array[col] = sum(grid[row][n - 1 - col] for row in range(max_value)) return sorted_array ================================================ FILE: algorithms/sorting/bitonic_sort.py ================================================ """ Bitonic Sort Bitonic sort is a comparison-based sorting algorithm designed for parallel execution. This implementation is sequential. The input size must be a power of two. Reference: https://en.wikipedia.org/wiki/Bitonic_sorter Complexity: Time: O(n log^2 n) best / O(n log^2 n) average / O(n log^2 n) worst Space: O(n log^2 n) """ from __future__ import annotations def bitonic_sort(array: list[int], reverse: bool = False) -> list[int]: """Sort an array using bitonic sort. Args: array: List of integers to sort. reverse: If True, sort in descending order. Returns: A sorted list. Raises: ValueError: If the array length is not a power of two. Examples: >>> bitonic_sort([4, 2, 1, 3]) [1, 2, 3, 4] """ n = len(array) if n <= 1: return array if not (n and not (n & (n - 1))): raise ValueError("the size of input should be power of two") left = bitonic_sort(array[: n // 2], True) right = bitonic_sort(array[n // 2 :], False) return _bitonic_merge(left + right, reverse) def _compare(array: list[int], reverse: bool) -> list[int]: """Compare and swap elements across the two halves of *array*.""" half = len(array) // 2 for i in range(half): if reverse != (array[i] > array[i + half]): array[i], array[i + half] = array[i + half], array[i] return array def _bitonic_merge(array: list[int], reverse: bool) -> list[int]: """Recursively merge a bitonic sequence into sorted order.""" n = len(array) if n <= 1: return array array = _compare(array, reverse) left = _bitonic_merge(array[: n // 2], reverse) right = _bitonic_merge(array[n // 2 :], reverse) return left + right ================================================ FILE: algorithms/sorting/bogo_sort.py ================================================ """ Bogo Sort Bogo sort repeatedly shuffles the array at random until it happens to be sorted. It is extremely inefficient and used only for educational purposes. Reference: https://en.wikipedia.org/wiki/Bogosort Complexity: Time: O(n) best / O(n * n!) average / O(infinity) worst Space: O(1) """ from __future__ import annotations import random def bogo_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using bogo sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> bogo_sort([3, 1, 2]) [1, 2, 3] """ while not _is_sorted(array): random.shuffle(array) return array def _is_sorted(array: list[int]) -> bool: """Return True if *array* is in non-decreasing order.""" return all( array[i] <= array[i + 1] for i in range(len(array) - 1) ) ================================================ FILE: algorithms/sorting/bubble_sort.py ================================================ """ Bubble Sort Bubble sort repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. Reference: https://en.wikipedia.org/wiki/Bubble_sort Complexity: Time: O(n) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def bubble_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using bubble sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> bubble_sort([3, 1, 2]) [1, 2, 3] """ n = len(array) swapped = True passes = 0 while swapped: swapped = False for i in range(1, n - passes): if array[i - 1] > array[i]: array[i - 1], array[i] = array[i], array[i - 1] swapped = True passes += 1 return array ================================================ FILE: algorithms/sorting/bucket_sort.py ================================================ """ Bucket Sort Bucket sort distributes elements into a number of buckets, sorts each bucket individually (here using insertion sort), and then concatenates all buckets. Reference: https://en.wikipedia.org/wiki/Bucket_sort Complexity: Time: O(n + k) best / O(n + k) average / O(n^2) worst Space: O(n + k) """ from __future__ import annotations def bucket_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using bucket sort. Args: array: List of non-negative integers to sort. Returns: A sorted list. Examples: >>> bucket_sort([3, 1, 2, 4]) [1, 2, 3, 4] """ num_buckets = len(array) buckets: list[list[int]] = [[] for _ in range(num_buckets)] max_value = max(array) + 1 for value in array: index = value * num_buckets // max_value buckets[index].append(value) sorted_list: list[int] = [] for bucket in buckets: sorted_list.extend(_insertion_sort(bucket)) return sorted_list def _insertion_sort(array: list[int]) -> list[int]: """Sort *array* in-place using insertion sort and return it.""" for i in range(1, len(array)): key = array[i] j = i - 1 while j >= 0 and array[j] > key: array[j + 1] = array[j] j -= 1 array[j + 1] = key return array ================================================ FILE: algorithms/sorting/cocktail_shaker_sort.py ================================================ """ Cocktail Shaker Sort Cocktail shaker sort is a variation of bubble sort that traverses the list alternately from left-to-right and right-to-left. Reference: https://en.wikipedia.org/wiki/Cocktail_shaker_sort Complexity: Time: O(n) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def cocktail_shaker_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using cocktail shaker sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> cocktail_shaker_sort([3, 1, 2]) [1, 2, 3] """ n = len(array) swapped = True while swapped: swapped = False for i in range(1, n): if array[i - 1] > array[i]: array[i - 1], array[i] = array[i], array[i - 1] swapped = True if not swapped: return array swapped = False for i in range(n - 1, 0, -1): if array[i - 1] > array[i]: array[i - 1], array[i] = array[i], array[i - 1] swapped = True return array ================================================ FILE: algorithms/sorting/comb_sort.py ================================================ """ Comb Sort Comb sort improves on bubble sort by using a gap sequence that shrinks by a factor of approximately 1.3 on each pass, eliminating small values near the end of the list (known as "turtles") more quickly. Reference: https://en.wikipedia.org/wiki/Comb_sort Complexity: Time: O(n log n) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def comb_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using comb sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> comb_sort([3, 1, 2]) [1, 2, 3] """ n = len(array) gap = n shrink_factor = 1.3 is_sorted = False while not is_sorted: gap = int(gap / shrink_factor) if gap <= 1: gap = 1 is_sorted = True i = 0 while i + gap < n: if array[i] > array[i + gap]: array[i], array[i + gap] = array[i + gap], array[i] is_sorted = False i += 1 return array ================================================ FILE: algorithms/sorting/counting_sort.py ================================================ """ Counting Sort Counting sort counts the occurrences of each value and uses cumulative counts to place each element in its correct position. It supports negative integers by shifting values internally. Reference: https://en.wikipedia.org/wiki/Counting_sort Complexity: Time: O(n + k) best / O(n + k) average / O(n + k) worst Space: O(n + k) where k is the range of input values """ from __future__ import annotations def counting_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using counting sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> counting_sort([3, 1, 2]) [1, 2, 3] """ min_value = min(array) offset = -min_value if min_value < 0 else 0 shifted = [v + offset for v in array] max_value = max(shifted) counts = [0] * (max_value + 1) for value in shifted: counts[value] += 1 # Build cumulative counts for i in range(1, max_value + 1): counts[i] += counts[i - 1] result = [0] * len(array) for i in range(len(array) - 1, -1, -1): value = shifted[i] counts[value] -= 1 result[counts[value]] = value - offset return result ================================================ FILE: algorithms/sorting/cycle_sort.py ================================================ """ Cycle Sort Cycle sort decomposes the permutation into cycles and rotates each cycle to produce a sorted result. It minimises the number of writes to the array, making it useful when writes are expensive. Reference: https://en.wikipedia.org/wiki/Cycle_sort Complexity: Time: O(n^2) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def cycle_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using cycle sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> cycle_sort([3, 1, 2]) [1, 2, 3] """ length = len(array) for start in range(length - 1): item = array[start] # Count how many elements are smaller to find the correct position position = start for i in range(start + 1, length): if array[i] < item: position += 1 # No cycle needed for this element if position == start: continue # Skip duplicates while item == array[position]: position += 1 array[position], item = item, array[position] # Rotate the rest of the cycle while position != start: position = start for i in range(start + 1, length): if array[i] < item: position += 1 while item == array[position]: position += 1 array[position], item = item, array[position] return array ================================================ FILE: algorithms/sorting/exchange_sort.py ================================================ """ Exchange Sort Exchange sort compares every pair of elements and swaps them if they are out of order. It is conceptually similar to bubble sort. Reference: https://en.wikipedia.org/wiki/Sorting_algorithm#Exchange_sort Complexity: Time: O(n^2) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def exchange_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using exchange sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> exchange_sort([3, 1, 2]) [1, 2, 3] """ n = len(array) for i in range(n - 1): for j in range(i + 1, n): if array[i] > array[j]: array[i], array[j] = array[j], array[i] return array ================================================ FILE: algorithms/sorting/gnome_sort.py ================================================ """ Gnome Sort Gnome sort moves an element toward the front of the list until it finds an element that is smaller or equal, then steps forward again. It is similar to insertion sort but uses swaps instead of shifts. Reference: https://en.wikipedia.org/wiki/Gnome_sort Complexity: Time: O(n) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def gnome_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using gnome sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> gnome_sort([3, 1, 2]) [1, 2, 3] """ n = len(array) index = 0 while index < n: if index == 0 or array[index] >= array[index - 1]: index += 1 else: array[index], array[index - 1] = array[index - 1], array[index] index -= 1 return array ================================================ FILE: algorithms/sorting/heap_sort.py ================================================ """ Heap Sort Heap sort builds a heap from the data and repeatedly extracts the extreme element to produce a sorted array. Two variants are provided: max-heap sort and min-heap sort. Reference: https://en.wikipedia.org/wiki/Heapsort Complexity: Time: O(n log n) best / O(n log n) average / O(n log n) worst Space: O(1) """ from __future__ import annotations def max_heap_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using a max-heap. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> max_heap_sort([3, 1, 2]) [1, 2, 3] """ for i in range(len(array) - 1, 0, -1): _max_heapify(array, i) return array def _max_heapify(array: list[int], end: int) -> None: """Build a max-heap on *array[0..end]* and swap the root to *end*.""" last_parent = (end - 1) // 2 for parent in range(last_parent, -1, -1): current = parent while current <= last_parent: child = 2 * current + 1 if child + 1 <= end and array[child] < array[child + 1]: child += 1 if array[child] > array[current]: array[current], array[child] = array[child], array[current] current = child else: break array[0], array[end] = array[end], array[0] def min_heap_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using a min-heap. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> min_heap_sort([3, 1, 2]) [1, 2, 3] """ for i in range(len(array) - 1): _min_heapify(array, i) return array def _min_heapify(array: list[int], start: int) -> None: """Build a min-heap on *array[start..]* and place the minimum at *start*.""" end = len(array) - 1 last_parent = (end - start - 1) // 2 for parent in range(last_parent, -1, -1): current = parent while current <= last_parent: child = 2 * current + 1 if ( child + 1 <= end - start and array[child + start] > array[child + 1 + start] ): child += 1 if array[child + start] < array[current + start]: array[current + start], array[child + start] = ( array[child + start], array[current + start], ) current = child else: break ================================================ FILE: algorithms/sorting/insertion_sort.py ================================================ """ Insertion Sort Insertion sort builds the sorted list one element at a time by repeatedly picking the next element and inserting it into its correct position. Reference: https://en.wikipedia.org/wiki/Insertion_sort Complexity: Time: O(n) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def insertion_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using insertion sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> insertion_sort([3, 1, 2]) [1, 2, 3] """ for i in range(len(array)): cursor = array[i] pos = i while pos > 0 and array[pos - 1] > cursor: array[pos] = array[pos - 1] pos -= 1 array[pos] = cursor return array ================================================ FILE: algorithms/sorting/meeting_rooms.py ================================================ """ Meeting Rooms Given an array of meeting time intervals consisting of start and end times [[s1, e1], [s2, e2], ...] (si < ei), determine if a person could attend all meetings (i.e. no two meetings overlap). Reference: https://leetcode.com/problems/meeting-rooms/ Complexity: Time: O(n log n) best / O(n log n) average / O(n log n) worst Space: O(1) """ from __future__ import annotations def can_attend_meetings(intervals: list) -> bool: """Determine whether all meetings can be attended without overlap. Args: intervals: List of interval objects with *start* and *end* attributes. Returns: True if a person can attend all meetings, False otherwise. Examples: >>> # With intervals [[0,30],[5,10],[15,20]] the answer is False. >>> # With intervals [[7,10],[2,4]] the answer is True. """ intervals = sorted(intervals, key=lambda x: x.start) for i in range(1, len(intervals)): if intervals[i].start < intervals[i - 1].end: return False return True ================================================ FILE: algorithms/sorting/merge_sort.py ================================================ """ Merge Sort Merge sort divides the array in half, recursively sorts each half, and then merges the two sorted halves back together. Reference: https://en.wikipedia.org/wiki/Merge_sort Complexity: Time: O(n log n) best / O(n log n) average / O(n log n) worst Space: O(n) """ from __future__ import annotations def merge_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using merge sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> merge_sort([3, 1, 2]) [1, 2, 3] """ if len(array) <= 1: return array mid = len(array) // 2 left = merge_sort(array[:mid]) right = merge_sort(array[mid:]) _merge(left, right, array) return array def _merge(left: list[int], right: list[int], merged: list[int]) -> None: """Merge two sorted lists into *merged* in-place. Args: left: Sorted left half. right: Sorted right half. merged: Destination list (length == len(left) + len(right)). """ left_cursor = 0 right_cursor = 0 while left_cursor < len(left) and right_cursor < len(right): if left[left_cursor] <= right[right_cursor]: merged[left_cursor + right_cursor] = left[left_cursor] left_cursor += 1 else: merged[left_cursor + right_cursor] = right[right_cursor] right_cursor += 1 for left_cursor in range(left_cursor, len(left)): # noqa: B020 merged[left_cursor + right_cursor] = left[left_cursor] for right_cursor in range(right_cursor, len(right)): # noqa: B020 merged[left_cursor + right_cursor] = right[right_cursor] ================================================ FILE: algorithms/sorting/pancake_sort.py ================================================ """ Pancake Sort Pancake sort sorts an array by repeatedly finding the maximum element in the unsorted portion, flipping it to the front, and then flipping the entire unsorted portion so the maximum lands at the end. Reference: https://en.wikipedia.org/wiki/Pancake_sorting Complexity: Time: O(n) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def pancake_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using pancake sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> pancake_sort([3, 1, 2]) [1, 2, 3] """ if len(array) <= 1: return array for cur in range(len(array), 1, -1): index_max = array.index(max(array[0:cur])) if index_max + 1 != cur: if index_max != 0: array[: index_max + 1] = reversed(array[: index_max + 1]) array[:cur] = reversed(array[:cur]) return array ================================================ FILE: algorithms/sorting/pigeonhole_sort.py ================================================ """ Pigeonhole Sort Pigeonhole sort is suitable for sorting lists where the number of elements and the range of possible key values are approximately equal. Reference: https://en.wikipedia.org/wiki/Pigeonhole_sort Complexity: Time: O(n + range) best / O(n + range) average / O(n + range) worst Space: O(range) """ from __future__ import annotations def pigeonhole_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using pigeonhole sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> pigeonhole_sort([3, 1, 2]) [1, 2, 3] """ max_value = max(array) min_value = min(array) size = max_value - min_value + 1 holes = [0] * size for value in array: holes[value - min_value] += 1 i = 0 for count in range(size): while holes[count] > 0: holes[count] -= 1 array[i] = count + min_value i += 1 return array ================================================ FILE: algorithms/sorting/quick_sort.py ================================================ """ Quick Sort Quick sort selects a pivot element, partitions the array around the pivot, and recursively sorts the two partitions. Reference: https://en.wikipedia.org/wiki/Quicksort Complexity: Time: O(n log n) best / O(n log n) average / O(n^2) worst Space: O(log n) """ from __future__ import annotations def quick_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using quick sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> quick_sort([3, 1, 2]) [1, 2, 3] """ _quick_sort_recursive(array, 0, len(array) - 1) return array def _quick_sort_recursive(array: list[int], first: int, last: int) -> None: """Recursively sort *array[first..last]* in-place.""" if first < last: pivot = _partition(array, first, last) _quick_sort_recursive(array, first, pivot - 1) _quick_sort_recursive(array, pivot + 1, last) def _partition(array: list[int], first: int, last: int) -> int: """Partition *array[first..last]* using the last element as pivot. Returns: The final index of the pivot element. """ wall = first for pos in range(first, last): if array[pos] < array[last]: array[pos], array[wall] = array[wall], array[pos] wall += 1 array[wall], array[last] = array[last], array[wall] return wall ================================================ FILE: algorithms/sorting/radix_sort.py ================================================ """ Radix Sort Radix sort processes digits from least significant to most significant, distributing elements into buckets for each digit and collecting them back in order. Reference: https://en.wikipedia.org/wiki/Radix_sort Complexity: Time: O(n * k) best / O(n * k) average / O(n * k) worst Space: O(n + k) where k is the number of digits in the largest value """ from __future__ import annotations def radix_sort(array: list[int]) -> list[int]: """Sort an array of non-negative integers using radix sort. Args: array: List of non-negative integers to sort. Returns: A sorted list. Examples: >>> radix_sort([170, 45, 75, 90, 802, 24, 2, 66]) [2, 24, 45, 66, 75, 90, 170, 802] """ position = 1 max_number = max(array) while position <= max_number: buckets: list[list[int]] = [[] for _ in range(10)] for num in array: digit = num // position % 10 buckets[digit].append(num) index = 0 for bucket in buckets: for num in bucket: array[index] = num index += 1 position *= 10 return array ================================================ FILE: algorithms/sorting/selection_sort.py ================================================ """ Selection Sort Selection sort repeatedly selects the smallest element from the unsorted portion and moves it to the end of the sorted portion. Reference: https://en.wikipedia.org/wiki/Selection_sort Complexity: Time: O(n^2) best / O(n^2) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def selection_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using selection sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> selection_sort([3, 1, 2]) [1, 2, 3] """ for i in range(len(array)): minimum = i for j in range(i + 1, len(array)): if array[j] < array[minimum]: minimum = j array[minimum], array[i] = array[i], array[minimum] return array ================================================ FILE: algorithms/sorting/shell_sort.py ================================================ """ Shell Sort Shell sort is a generalisation of insertion sort that allows the exchange of elements that are far apart. The gap between compared elements is gradually reduced until it becomes 1, at which point the algorithm behaves like a standard insertion sort. Reference: https://en.wikipedia.org/wiki/Shellsort Complexity: Time: O(n log n) best / O(n^(4/3)) average / O(n^2) worst Space: O(1) """ from __future__ import annotations def shell_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using shell sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> shell_sort([3, 1, 2]) [1, 2, 3] """ n = len(array) gap = n // 2 while gap > 0: y_index = gap while y_index < n: y = array[y_index] x_index = y_index - gap while x_index >= 0 and y < array[x_index]: array[x_index + gap] = array[x_index] x_index -= gap array[x_index + gap] = y y_index += 1 gap //= 2 return array ================================================ FILE: algorithms/sorting/sort_colors.py ================================================ """ Sort Colors (Dutch National Flag) Given an array with n objects colored red, white, or blue (represented by 0, 1, and 2), sort them in-place so that objects of the same color are adjacent, with the colors in order red, white, blue. Reference: https://leetcode.com/problems/sort-colors/ Complexity: Time: O(n) best / O(n) average / O(n) worst Space: O(1) """ from __future__ import annotations def sort_colors(array: list[int]) -> list[int]: """Sort an array of 0s, 1s, and 2s in-place. Args: array: List of integers (each 0, 1, or 2) to sort. Returns: The sorted list. Examples: >>> sort_colors([2, 0, 1, 2, 1, 0]) [0, 0, 1, 1, 2, 2] """ red = white = 0 for k in range(len(array)): value = array[k] array[k] = 2 if value < 2: array[white] = 1 white += 1 if value == 0: array[red] = 0 red += 1 return array ================================================ FILE: algorithms/sorting/stooge_sort.py ================================================ """ Stooge Sort Stooge sort is a recursive sorting algorithm notable for its unusually bad time complexity. It works by recursively sorting the first 2/3, then the last 2/3, and then the first 2/3 again. Reference: https://en.wikipedia.org/wiki/Stooge_sort Complexity: Time: O(n^2.709) best / O(n^2.709) average / O(n^2.709) worst Space: O(n) """ from __future__ import annotations def stooge_sort(array: list[int]) -> list[int]: """Sort an array in ascending order using stooge sort. Args: array: List of integers to sort. Returns: A sorted list. Examples: >>> stooge_sort([3, 1, 2]) [1, 2, 3] """ _stooge_sort(array, 0, len(array) - 1) return array def _stooge_sort(array: list[int], low: int, high: int) -> None: """Recursively sort *array[low..high]* using stooge sort.""" if low >= high: return if array[low] > array[high]: array[low], array[high] = array[high], array[low] if high - low + 1 > 2: third = (high - low + 1) // 3 _stooge_sort(array, low, high - third) _stooge_sort(array, low + third, high) _stooge_sort(array, low, high - third) ================================================ FILE: algorithms/sorting/wiggle_sort.py ================================================ """ Wiggle Sort Given an unsorted array, reorder it in-place such that nums[0] < nums[1] > nums[2] < nums[3] ... Reference: https://leetcode.com/problems/wiggle-sort/ Complexity: Time: O(n) best / O(n) average / O(n) worst Space: O(1) """ from __future__ import annotations def wiggle_sort(array: list[int]) -> list[int]: """Reorder *array* in-place into wiggle-sorted order. Args: array: List of integers to reorder. Returns: The wiggle-sorted list. Examples: >>> wiggle_sort([3, 5, 2, 1, 6, 4]) [3, 5, 1, 6, 2, 4] """ for i in range(len(array)): if (i % 2 == 1) == (array[i - 1] > array[i]): array[i - 1], array[i] = array[i], array[i - 1] return array ================================================ FILE: algorithms/stack/__init__.py ================================================ """Stack-based algorithm implementations.""" from __future__ import annotations from algorithms.data_structures.stack import ( AbstractStack, ArrayStack, LinkedListStack, StackNode, ) from .is_consecutive import first_is_consecutive, second_is_consecutive from .is_sorted import is_sorted from .longest_abs_path import length_longest_path from .ordered_stack import OrderedStack from .remove_min import remove_min from .simplify_path import simplify_path from .stutter import first_stutter, second_stutter from .switch_pairs import first_switch_pairs, second_switch_pairs from .valid_parenthesis import is_valid __all__ = [ "AbstractStack", "ArrayStack", "LinkedListStack", "OrderedStack", "StackNode", "first_is_consecutive", "first_stutter", "first_switch_pairs", "is_sorted", "is_valid", "length_longest_path", "remove_min", "second_is_consecutive", "second_stutter", "second_switch_pairs", "simplify_path", ] ================================================ FILE: algorithms/stack/is_consecutive.py ================================================ """ Is Consecutive Check whether a stack contains a sequence of consecutive integers starting from the bottom. Two approaches are provided: one using an auxiliary stack, and one using an auxiliary queue. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations import collections def first_is_consecutive(stack: list[int]) -> bool: """Check if a stack has consecutive integers using an auxiliary stack. Args: stack: A list representing a stack (bottom to top). Returns: True if the values are consecutive from bottom to top. Examples: >>> first_is_consecutive([3, 4, 5, 6, 7]) True >>> first_is_consecutive([3, 4, 6, 7]) False """ storage_stack: list[int] = [] for _ in range(len(stack)): first_value = stack.pop() if len(stack) == 0: return True second_value = stack.pop() if first_value - second_value != 1: return False stack.append(second_value) storage_stack.append(first_value) for _ in range(len(storage_stack)): stack.append(storage_stack.pop()) return True def second_is_consecutive(stack: list[int]) -> bool: """Check if a stack has consecutive integers using an auxiliary queue. Args: stack: A list representing a stack (bottom to top). Returns: True if the values are consecutive from bottom to top. Examples: >>> second_is_consecutive([3, 4, 5, 6, 7]) True >>> second_is_consecutive([3, 4, 6, 7]) False """ queue: collections.deque[int] = collections.deque() for _ in range(len(stack)): first_value = stack.pop() if len(stack) == 0: return True second_value = stack.pop() if first_value - second_value != 1: return False stack.append(second_value) queue.append(first_value) for _ in range(len(queue)): stack.append(queue.pop()) for _ in range(len(stack)): queue.append(stack.pop()) for _ in range(len(queue)): stack.append(queue.pop()) return True ================================================ FILE: algorithms/stack/is_sorted.py ================================================ """ Is Sorted Check whether a stack is sorted in ascending order from bottom to top using a single auxiliary stack. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def is_sorted(stack: list[int]) -> bool: """Check if a stack is sorted in ascending order (bottom to top). Args: stack: A list representing a stack (bottom to top). Returns: True if sorted in ascending order, False otherwise. Examples: >>> is_sorted([1, 2, 3, 4, 5, 6]) True >>> is_sorted([6, 3, 5, 1, 2, 4]) False """ storage_stack: list[int] = [] for _ in range(len(stack)): if len(stack) == 0: break first_val = stack.pop() if len(stack) == 0: break second_val = stack.pop() if first_val < second_val: return False storage_stack.append(first_val) stack.append(second_val) for _ in range(len(storage_stack)): stack.append(storage_stack.pop()) return True ================================================ FILE: algorithms/stack/longest_abs_path.py ================================================ """ Longest Absolute File Path Given a string representing a file system in a special format, find the length of the longest absolute path to a file. Directories and files are separated by newlines; depth is indicated by tab characters. Reference: https://leetcode.com/problems/longest-absolute-file-path/ Complexity: Time: O(n) Space: O(d) where d is the maximum depth """ from __future__ import annotations def length_longest_path(input_str: str) -> int: """Find the length of the longest absolute path to a file. Args: input_str: A string encoding the file system structure using newlines and tabs. Returns: Length of the longest absolute path to a file, or 0 if no file exists. Examples: >>> length_longest_path("dir\\n\\tfile.txt") 12 """ current_length = 0 max_length = 0 stack: list[int] = [] for segment in input_str.split("\n"): depth = segment.count("\t") while len(stack) > depth: current_length -= stack.pop() name_length = len(segment.strip("\t")) + 1 stack.append(name_length) current_length += stack[-1] if "." in segment: max_length = max(max_length, current_length - 1) return max_length ================================================ FILE: algorithms/stack/ordered_stack.py ================================================ """ Ordered Stack A stack that maintains elements in sorted order, with the highest value at the top and the lowest at the bottom. Push operations preserve the ordering invariant. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(n) for push, O(1) for pop/peek Space: O(n) """ from __future__ import annotations class OrderedStack: """A stack that keeps elements in ascending order (bottom to top). Examples: >>> s = OrderedStack() >>> s.push(3) >>> s.push(1) >>> s.push(2) >>> s.pop() 3 """ def __init__(self) -> None: """Initialize an empty ordered stack.""" self.items: list[int] = [] def is_empty(self) -> bool: """Check if the stack is empty. Returns: True if the stack has no elements. """ return self.items == [] def _push_direct(self, item: int) -> None: """Append an item without enforcing order. Args: item: The value to append. """ self.items.append(item) def push(self, item: int) -> None: """Push an item while maintaining sorted order. Args: item: The value to push. """ temp_stack = OrderedStack() if self.is_empty() or item > self.peek(): self._push_direct(item) else: while item < self.peek() and not self.is_empty(): temp_stack._push_direct(self.pop()) self._push_direct(item) while not temp_stack.is_empty(): self._push_direct(temp_stack.pop()) def pop(self) -> int: """Remove and return the top element. Returns: The top (largest) element. Raises: IndexError: If the stack is empty. """ if self.is_empty(): raise IndexError("Stack is empty") return self.items.pop() def peek(self) -> int: """Return the top element without removing it. Returns: The top (largest) element. """ return self.items[len(self.items) - 1] def size(self) -> int: """Return the number of elements in the stack. Returns: The stack size. """ return len(self.items) ================================================ FILE: algorithms/stack/remove_min.py ================================================ """ Remove Min from Stack Remove the smallest value from a stack, preserving the relative order of the remaining elements. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def remove_min(stack: list[int]) -> list[int]: """Remove the minimum value from the stack. Args: stack: A list representing a stack (bottom to top). Returns: The stack with the minimum value removed. Examples: >>> remove_min([2, 8, 3, -6, 7, 3]) [2, 8, 3, 7, 3] """ storage_stack: list[int] = [] if len(stack) == 0: return stack minimum = stack.pop() stack.append(minimum) for _ in range(len(stack)): val = stack.pop() if val <= minimum: minimum = val storage_stack.append(val) for _ in range(len(storage_stack)): val = storage_stack.pop() if val != minimum: stack.append(val) return stack ================================================ FILE: algorithms/stack/simplify_path.py ================================================ """ Simplify Path Given an absolute Unix-style file path, simplify it by resolving '.' (current directory), '..' (parent directory), and multiple slashes. Reference: https://leetcode.com/problems/simplify-path/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def simplify_path(path: str) -> str: """Simplify a Unix-style absolute path. Args: path: An absolute file path string. Returns: The simplified canonical path. Examples: >>> simplify_path("/home/") '/home' >>> simplify_path("/a/./b/../../c/") '/c' """ skip = {"..", ".", ""} stack: list[str] = [] tokens = path.split("/") for token in tokens: if token == "..": if stack: stack.pop() elif token not in skip: stack.append(token) return "/" + "/".join(stack) ================================================ FILE: algorithms/stack/stutter.py ================================================ """ Stutter Replace every value in a stack with two occurrences of that value. Two approaches: one using an auxiliary stack, one using an auxiliary queue. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations import collections def first_stutter(stack: list[int]) -> list[int]: """Stutter a stack using an auxiliary stack. Args: stack: A list representing a stack (bottom to top). Returns: The stack with each value duplicated. Examples: >>> first_stutter([3, 7, 1, 14, 9]) [3, 3, 7, 7, 1, 1, 14, 14, 9, 9] """ storage_stack: list[int] = [] for _ in range(len(stack)): storage_stack.append(stack.pop()) for _ in range(len(storage_stack)): val = storage_stack.pop() stack.append(val) stack.append(val) return stack def second_stutter(stack: list[int]) -> list[int]: """Stutter a stack using an auxiliary queue. Args: stack: A list representing a stack (bottom to top). Returns: The stack with each value duplicated. Examples: >>> second_stutter([3, 7, 1, 14, 9]) [3, 3, 7, 7, 1, 1, 14, 14, 9, 9] """ queue: collections.deque[int] = collections.deque() for _ in range(len(stack)): queue.append(stack.pop()) for _ in range(len(queue)): stack.append(queue.pop()) for _ in range(len(stack)): queue.append(stack.pop()) for _ in range(len(queue)): val = queue.pop() stack.append(val) stack.append(val) return stack ================================================ FILE: algorithms/stack/switch_pairs.py ================================================ """ Switch Pairs Switch successive pairs of values in a stack starting from the bottom. If there is an odd number of values, the top element is not moved. Two approaches: one using an auxiliary stack, one using an auxiliary queue. Reference: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations import collections def first_switch_pairs(stack: list[int]) -> list[int]: """Switch successive pairs using an auxiliary stack. Args: stack: A list representing a stack (bottom to top). Returns: The stack with successive pairs swapped. Examples: >>> first_switch_pairs([3, 8, 17, 9, 1, 10]) [8, 3, 9, 17, 10, 1] """ storage_stack: list[int] = [] for _ in range(len(stack)): storage_stack.append(stack.pop()) for _ in range(len(storage_stack)): if len(storage_stack) == 0: break first = storage_stack.pop() if len(storage_stack) == 0: stack.append(first) break second = storage_stack.pop() stack.append(second) stack.append(first) return stack def second_switch_pairs(stack: list[int]) -> list[int]: """Switch successive pairs using an auxiliary queue. Args: stack: A list representing a stack (bottom to top). Returns: The stack with successive pairs swapped. Examples: >>> second_switch_pairs([3, 8, 17, 9, 1, 10]) [8, 3, 9, 17, 10, 1] """ queue: collections.deque[int] = collections.deque() for _ in range(len(stack)): queue.append(stack.pop()) for _ in range(len(queue)): stack.append(queue.pop()) for _ in range(len(stack)): queue.append(stack.pop()) for _ in range(len(queue)): if len(queue) == 0: break first = queue.pop() if len(queue) == 0: stack.append(first) break second = queue.pop() stack.append(second) stack.append(first) return stack ================================================ FILE: algorithms/stack/valid_parenthesis.py ================================================ """ Valid Parentheses Determine if a string containing only '(', ')', '{', '}', '[' and ']' has valid (properly closed and nested) brackets. Reference: https://leetcode.com/problems/valid-parentheses/ Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def is_valid(s: str) -> bool: """Check if the bracket string is valid. Args: s: A string containing only bracket characters. Returns: True if the brackets are valid, False otherwise. Examples: >>> is_valid("()[]{}") True >>> is_valid("(]") False """ stack: list[str] = [] matching = {")": "(", "}": "{", "]": "["} for char in s: if char in matching.values(): stack.append(char) elif char in matching and (not stack or matching[char] != stack.pop()): return False return not stack ================================================ FILE: algorithms/streaming/__init__.py ================================================ from .misra_gries import misras_gries from .one_sparse_recovery import one_sparse __all__ = [ "misras_gries", "one_sparse", ] ================================================ FILE: algorithms/streaming/misra_gries.py ================================================ """ Misra-Gries Frequency Estimation Given a list of items and a value k, returns every item that appears at least n/k times, where n is the length of the list. Defaults to k=2 (majority problem). Reference: https://en.wikipedia.org/wiki/Misra%E2%80%93Gries_summary Complexity: Time: O(n * k) Space: O(k) """ from __future__ import annotations def misras_gries(array: list[int], k: int = 2) -> dict[str, int] | None: """Find all elements appearing at least n/k times. Args: array: A list of integers. k: The frequency threshold divisor (default 2). Returns: A dict mapping element (as string) to its frequency, or None if no element meets the threshold. Examples: >>> misras_gries([1, 4, 4, 4, 5, 4, 4]) {'4': 5} >>> misras_gries([0, 0, 0, 1, 1, 1, 1]) {'1': 4} >>> misras_gries([0, 0, 0, 0, 1, 1, 1, 2, 2], 3) {'0': 4, '1': 3} """ keys: dict[str, int] = {} for item in array: val = str(item) if val in keys: keys[val] += 1 elif len(keys) < k - 1: keys[val] = 1 else: for key in list(keys): keys[key] -= 1 if keys[key] == 0: del keys[key] suspects = keys.keys() frequencies: dict[str, int] = {} for suspect in suspects: freq = _count_frequency(array, int(suspect)) if freq >= len(array) / k: frequencies[suspect] = freq return frequencies if frequencies else None def _count_frequency(array: list[int], element: int) -> int: """Count occurrences of element in array. Args: array: The list to search. element: The value to count. Returns: The number of occurrences. """ return array.count(element) ================================================ FILE: algorithms/streaming/one_sparse_recovery.py ================================================ """ Non-negative 1-Sparse Recovery Determines whether a dynamic stream of (value, sign) tuples is 1-sparse, meaning all values cancel out except for a single unique number. If so, returns that number; otherwise returns None. Reference: https://en.wikipedia.org/wiki/Sparse_recovery Complexity: Time: O(n * b) where n is stream length and b is bit width (32) Space: O(b) """ from __future__ import annotations def one_sparse(array: list[tuple[int, str]]) -> int | None: """Recover the unique element from a 1-sparse stream. Args: array: A list of (value, sign) tuples where sign is '+' or '-'. Returns: The unique element if the stream is 1-sparse, otherwise None. Examples: >>> one_sparse([(4, '+'), (2, '+'), (2, '-'), (4, '+'), (3, '+'), (3, '-')]) 4 >>> one_sparse([(2, '+'), (2, '+'), (2, '+'), ... (2, '+'), (2, '+'), (2, '+'), (1, '+')]) """ sum_signs = 0 bitsum: list[int] = [0] * 32 sum_values = 0 for val, sign in array: if sign == "+": sum_signs += 1 sum_values += val else: sum_signs -= 1 sum_values -= val _update_bit_sum(bitsum, val, sign) if sum_signs > 0 and _check_bit_sum_consistency(bitsum, sum_signs): return int(sum_values / sum_signs) return None def _check_bit_sum_consistency(bitsum: list[int], sum_signs: int) -> bool: """Check that every entry is either 0 or equal to sum_signs. Args: bitsum: The accumulated bit sums. sum_signs: The expected non-zero value. Returns: True if the bitsum is consistent with a 1-sparse stream. """ return all(val == 0 or val == sum_signs for val in bitsum) def _update_bit_sum(bitsum: list[int], val: int, sign: str) -> None: """Add or subtract the bit representation of val to the bitsum array. Args: bitsum: The accumulated bit sums to update in place. val: The integer value whose bits to process. sign: '+' to add or '-' to subtract. """ idx = 0 if sign == "+": while val: bitsum[idx] += val & 1 idx += 1 val >>= 1 else: while val: bitsum[idx] -= val & 1 idx += 1 val >>= 1 ================================================ FILE: algorithms/string/__init__.py ================================================ """String algorithms package.""" from __future__ import annotations from algorithms.string import fizzbuzz from algorithms.string.add_binary import add_binary from algorithms.string.alphabet_board_path import alphabet_board_path from algorithms.string.atbash_cipher import atbash from algorithms.string.breaking_bad import bracket, match_symbol, match_symbol_1 from algorithms.string.caesar_cipher import caesar_cipher from algorithms.string.check_pangram import check_pangram from algorithms.string.contain_string import contain_string from algorithms.string.count_binary_substring import count_binary_substring from algorithms.string.decode_string import decode_string from algorithms.string.delete_reoccurring import delete_reoccurring_characters from algorithms.string.domain_extractor import domain_name_1, domain_name_2 from algorithms.string.encode_decode import decode, encode from algorithms.string.first_unique_char import first_unique_char from algorithms.string.fizzbuzz import fizzbuzz_with_helper_func from algorithms.string.group_anagrams import group_anagrams from algorithms.string.int_to_roman import int_to_roman from algorithms.string.is_palindrome import ( is_palindrome, is_palindrome_deque, is_palindrome_reverse, is_palindrome_stack, is_palindrome_two_pointer, ) from algorithms.string.is_rotated import is_rotated, is_rotated_v1 from algorithms.string.judge_circle import judge_circle from algorithms.string.knuth_morris_pratt import knuth_morris_pratt from algorithms.string.license_number import license_number from algorithms.string.longest_common_prefix import ( longest_common_prefix_v1, longest_common_prefix_v2, longest_common_prefix_v3, ) from algorithms.string.longest_palindromic_substring import longest_palindrome from algorithms.string.make_sentence import make_sentence from algorithms.string.manacher import manacher from algorithms.string.merge_string_checker import ( is_merge_iterative, is_merge_recursive, ) from algorithms.string.min_distance import min_distance, min_distance_dp from algorithms.string.multiply_strings import multiply from algorithms.string.one_edit_distance import is_one_edit, is_one_edit2 from algorithms.string.panagram import panagram from algorithms.string.rabin_karp import RollingHash, rabin_karp from algorithms.string.repeat_string import repeat_string from algorithms.string.repeat_substring import repeat_substring from algorithms.string.reverse_string import ( iterative, pythonic, recursive, ultra_pythonic, ) from algorithms.string.reverse_vowel import reverse_vowel from algorithms.string.reverse_words import reverse_words from algorithms.string.roman_to_int import roman_to_int from algorithms.string.rotate import rotate, rotate_alt from algorithms.string.strip_url_params import ( strip_url_params1, strip_url_params2, strip_url_params3, ) from algorithms.string.strong_password import strong_password from algorithms.string.swap_characters import can_swap_to_equal from algorithms.string.text_justification import text_justification from algorithms.string.unique_morse import convert_morse_word, unique_morse from algorithms.string.validate_coordinates import ( is_valid_coordinates_0, is_valid_coordinates_1, is_valid_coordinates_regular_expression, ) from algorithms.string.word_squares import word_squares from algorithms.string.z_algorithm import compute_z_array, z_search __all__ = [ "add_binary", "atbash", "bracket", "caesar_cipher", "check_pangram", "contain_string", "convert_morse_word", "count_binary_substring", "decode", "decode_string", "delete_reoccurring_characters", "domain_name_1", "domain_name_2", "encode", "first_unique_char", "fizzbuzz", "fizzbuzz_with_helper_func", "group_anagrams", "int_to_roman", "is_merge_iterative", "is_merge_recursive", "is_one_edit", "is_one_edit2", "is_palindrome", "is_palindrome_deque", "is_palindrome_reverse", "is_palindrome_stack", "is_palindrome_two_pointer", "is_rotated", "is_rotated_v1", "is_valid_coordinates_0", "is_valid_coordinates_1", "is_valid_coordinates_regular_expression", "iterative", "judge_circle", "knuth_morris_pratt", "license_number", "longest_common_prefix_v1", "longest_common_prefix_v2", "longest_common_prefix_v3", "longest_palindrome", "make_sentence", "match_symbol", "match_symbol_1", "min_distance", "min_distance_dp", "multiply", "panagram", "pythonic", "rabin_karp", "recursive", "repeat_string", "repeat_substring", "reverse_vowel", "reverse_words", "RollingHash", "roman_to_int", "rotate", "rotate_alt", "strong_password", "strip_url_params1", "strip_url_params2", "strip_url_params3", "text_justification", "ultra_pythonic", "unique_morse", "word_squares", "z_search", "compute_z_array", "alphabet_board_path", "manacher", "can_swap_to_equal", ] ================================================ FILE: algorithms/string/add_binary.py ================================================ """ Add Binary Given two binary strings, return their sum as a binary string. Reference: https://leetcode.com/problems/add-binary/ Complexity: Time: O(max(m, n)) where m, n are lengths of the two strings Space: O(max(m, n)) """ from __future__ import annotations def add_binary(first: str, second: str) -> str: """Add two binary strings and return their binary sum. Args: first: A string representing a binary number. second: A string representing a binary number. Returns: A string representing the binary sum of the two inputs. Examples: >>> add_binary("11", "1") '100' """ result = "" carry, index_a, index_b = 0, len(first) - 1, len(second) - 1 zero = ord("0") while index_a >= 0 or index_b >= 0 or carry == 1: if index_a >= 0: carry += ord(first[index_a]) - zero index_a -= 1 if index_b >= 0: carry += ord(second[index_b]) - zero index_b -= 1 result = chr(carry % 2 + zero) + result carry //= 2 return result ================================================ FILE: algorithms/string/alphabet_board_path.py ================================================ """Alphabet board path — find moves on a 5x5+1 letter board. Given a board where 'a'-'z' are laid out in rows of 5: a b c d e f g h i j k l m n o p q r s t u v w x y z Return the sequence of moves (U/D/L/R/!) to spell a target word starting from 'a'. Inspired by PR #897 (chnttx). """ from __future__ import annotations def alphabet_board_path(target: str) -> str: """Return move string to spell *target* on the alphabet board.""" moves: list[str] = [] row, col = 0, 0 for ch in target: idx = ord(ch) - ord("a") target_row, target_col = divmod(idx, 5) # Move up/left before down/right to avoid going off-board at 'z' if target_row < row: moves.append("U" * (row - target_row)) if target_col < col: moves.append("L" * (col - target_col)) if target_row > row: moves.append("D" * (target_row - row)) if target_col > col: moves.append("R" * (target_col - col)) moves.append("!") row, col = target_row, target_col return "".join(moves) ================================================ FILE: algorithms/string/atbash_cipher.py ================================================ """ Atbash Cipher Atbash cipher maps each letter of the alphabet to its reverse. The first letter 'a' maps to 'z', 'b' maps to 'y', and so on. Reference: https://en.wikipedia.org/wiki/Atbash Complexity: Time: O(n) where n is the length of the input string Space: O(n) """ from __future__ import annotations def atbash(text: str) -> str: """Encrypt or decrypt a string using the Atbash cipher. Args: text: The input string to transform. Returns: The Atbash-transformed string. Examples: >>> atbash("abcdefghijklmno") 'zyxwvutsrqponml' """ translated = "" for char in text: code = ord(char) if char.isalpha(): if char.isupper(): offset = code - ord("A") translated += chr(ord("Z") - offset) elif char.islower(): offset = code - ord("a") translated += chr(ord("z") - offset) else: translated += char return translated ================================================ FILE: algorithms/string/breaking_bad.py ================================================ """ Breaking Bad Symbol Matching Given an array of words and an array of symbols, display each word with its matched symbol surrounded by square brackets. If a word matches more than one symbol, choose the one with the longest length. Reference: https://en.wikipedia.org/wiki/Trie Complexity: Time: O(n * m) for brute force, O(n * k) for trie-based approach Space: O(n * m) for storing results """ from __future__ import annotations import re from functools import reduce def match_symbol(words: list[str], symbols: list[str]) -> list[str]: """Match symbols in words using regex and surround matches with brackets. Args: words: List of words to search through. symbols: List of symbols to match within the words. Returns: List of words with matched symbols surrounded by square brackets. Examples: >>> match_symbol(['Google'], ['le']) ['Goog[le]'] """ combined = [] for symbol in symbols: for word in words: match = re.search(symbol, word) if match: combined.append(re.sub(symbol, f"[{symbol}]", word)) return combined def match_symbol_1(words: list[str], symbols: list[str]) -> list[str]: """Match the longest symbol in each word using sorted symbol list. Args: words: List of words to search through. symbols: List of symbols to match, sorted by length descending. Returns: List of words with the longest matched symbol bracketed. Examples: >>> match_symbol_1(['Microsoft'], ['i', 'cro']) ['Mi[cro]soft'] """ result = [] symbols = sorted(symbols, key=lambda item: len(item), reverse=True) for word in words: word_replaced = "" for symbol in symbols: if word.find(symbol) != -1: word_replaced = word.replace(symbol, "[" + symbol + "]") result.append(word_replaced) break if word_replaced == "": result.append(word) return result class _TrieNode: """Internal trie node for the bracket function.""" def __init__(self) -> None: self.children: dict[str, _TrieNode] = {} self.symbol: str | None = None def bracket(words: list[str], symbols: list[str]) -> tuple[str, ...]: """Match the longest symbol in each word using a trie-based approach. Args: words: List of words to search through. symbols: List of symbols to build the trie from. Returns: Tuple of words with the longest matched symbol bracketed. Examples: >>> bracket(['Amazon', 'Microsoft', 'Google'], ['Am', 'cro', 'le']) ('[Am]azon', 'Mi[cro]soft', 'Goog[le]') """ root = _TrieNode() for symbol in symbols: node = root for char in symbol: if char not in node.children: node.children[char] = _TrieNode() node = node.children[char] node.symbol = symbol matched = {} for word in words: index = 0 symbol_list = [] while index < len(word): cursor, node = index, root while cursor < len(word) and word[cursor] in node.children: node = node.children[word[cursor]] if node.symbol is not None: symbol_list.append( (cursor + 1 - len(node.symbol), cursor + 1, node.symbol) ) cursor += 1 index += 1 if len(symbol_list) > 0: best = reduce( lambda x, y: x if x[1] - x[0] >= y[1] - y[0] else y, symbol_list, ) matched[word] = f"{word[: best[0]]}[{best[2]}]{word[best[1] :]}" return tuple(matched.get(word, word) for word in words) ================================================ FILE: algorithms/string/caesar_cipher.py ================================================ """ Caesar Cipher Caesar's cipher shifts each letter by a fixed number of positions in the alphabet. Letters wrap around when they pass the end of the alphabet. Reference: https://en.wikipedia.org/wiki/Caesar_cipher Complexity: Time: O(n) where n is the length of the input string Space: O(n) """ from __future__ import annotations def caesar_cipher(text: str, shift: int) -> str: """Encrypt a string using the Caesar cipher with the given shift. Args: text: The plaintext string to encrypt. shift: The number of positions to shift each letter. Returns: The encrypted string with shifted letters. Examples: >>> caesar_cipher("Hello_World!", 4) 'Lipps_Asvph!' """ result = "" for char in text: code = ord(char) if 64 < code < 91: code = ((code - 65 + shift) % 26) + 65 if 96 < code < 123: code = ((code - 97 + shift) % 26) + 97 result = result + chr(code) return result ================================================ FILE: algorithms/string/check_pangram.py ================================================ """ Check Pangram Checks whether a given string is a pangram, meaning it contains every letter of the English alphabet at least once. Reference: https://en.wikipedia.org/wiki/Pangram Complexity: Time: O(n) where n is the length of the input string Space: O(1) """ from __future__ import annotations def check_pangram(input_string: str) -> bool: """Check if the input string is a pangram. Args: input_string: The string to check. Returns: True if the string contains every letter of the alphabet, False otherwise. Examples: >>> check_pangram("The quick brown fox jumps over the lazy dog") True """ alphabet = "abcdefghijklmnopqrstuvwxyz" return all(char in input_string.lower() for char in alphabet) ================================================ FILE: algorithms/string/contain_string.py ================================================ """ Contain String (strStr) Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. Reference: https://leetcode.com/problems/implement-strstr/ Complexity: Time: O(n * m) worst case, where n = len(haystack), m = len(needle) Space: O(1) """ from __future__ import annotations def contain_string(haystack: str, needle: str) -> int: """Find the first occurrence of needle in haystack. Args: haystack: The string to search in. needle: The string to search for. Returns: The index of the first occurrence, or -1 if not found. Examples: >>> contain_string("hello", "ll") 2 """ if len(needle) == 0: return 0 if len(needle) > len(haystack): return -1 for index in range(len(haystack)): if len(haystack) - index < len(needle): return -1 if haystack[index : index + len(needle)] == needle: return index return -1 ================================================ FILE: algorithms/string/count_binary_substring.py ================================================ """ Count Binary Substrings Count the number of non-empty contiguous substrings that have the same number of 0s and 1s, where all 0s and all 1s are grouped consecutively. Reference: https://leetcode.com/problems/count-binary-substrings/ Complexity: Time: O(n) where n is the length of the string Space: O(1) """ from __future__ import annotations def count_binary_substring(text: str) -> int: """Count substrings with equal consecutive 0s and 1s. Args: text: A binary string consisting of '0' and '1' characters. Returns: The number of valid binary substrings. Examples: >>> count_binary_substring("00110011") 6 """ current = 1 previous = 0 count = 0 for index in range(1, len(text)): if text[index] != text[index - 1]: count = count + min(previous, current) previous = current current = 1 else: current = current + 1 count = count + min(previous, current) return count ================================================ FILE: algorithms/string/decode_string.py ================================================ """ Decode String Given an encoded string, return its decoded string. The encoding rule is k[encoded_string], where the encoded_string inside the brackets is repeated exactly k times. Reference: https://leetcode.com/problems/decode-string/ Complexity: Time: O(n * max_k) where n is the length of the string Space: O(n) for the stack """ from __future__ import annotations def decode_string(text: str) -> str: """Decode an encoded string with nested repeat patterns. Args: text: The encoded string in the format k[encoded_string]. Returns: The fully decoded string. Examples: >>> decode_string("3[a]2[bc]") 'aaabcbc' """ stack: list[tuple[str, int]] = [] current_num = 0 current_string = "" for char in text: if char == "[": stack.append((current_string, current_num)) current_string = "" current_num = 0 elif char == "]": prev_string, num = stack.pop() current_string = prev_string + num * current_string elif char.isdigit(): current_num = current_num * 10 + int(char) else: current_string += char return current_string ================================================ FILE: algorithms/string/delete_reoccurring.py ================================================ """ Delete Reoccurring Characters Given a string, delete any reoccurring characters and return the new string containing only the first occurrence of each character. Reference: https://en.wikipedia.org/wiki/Duplicate_removal Complexity: Time: O(n) where n is the length of the string Space: O(n) """ from __future__ import annotations def delete_reoccurring_characters(string: str) -> str: """Remove duplicate characters, keeping only the first occurrence of each. Args: string: The input string to process. Returns: A new string with duplicate characters removed. Examples: >>> delete_reoccurring_characters("aaabcccc") 'abc' """ seen_characters: set[str] = set() output_string = "" for char in string: if char not in seen_characters: seen_characters.add(char) output_string += char return output_string ================================================ FILE: algorithms/string/domain_extractor.py ================================================ """ Domain Name Extractor Given a URL as a string, parse out just the domain name and return it. Uses only the .split() built-in function without regex or urlparse. Reference: https://en.wikipedia.org/wiki/Domain_name Complexity: Time: O(n) where n is the length of the URL Space: O(n) """ from __future__ import annotations def domain_name_1(url: str) -> str: """Extract the domain name from a URL by splitting on protocol and dots. Args: url: The full URL string. Returns: The domain name extracted from the URL. Examples: >>> domain_name_1("https://github.com/SaadBenn") 'github' """ full_domain_name = url.split("//")[-1] actual_domain = full_domain_name.split(".") if len(actual_domain) > 2: return actual_domain[1] return actual_domain[0] def domain_name_2(url: str) -> str: """Extract the domain name from a URL using chained splits. Args: url: The full URL string. Returns: The domain name extracted from the URL. Examples: >>> domain_name_2("http://google.com") 'google' """ return url.split("//")[-1].split("www.")[-1].split(".")[0] ================================================ FILE: algorithms/string/encode_decode.py ================================================ """ Encode and Decode Strings Design an algorithm to encode a list of strings to a single string, and decode it back to the original list of strings. Reference: https://leetcode.com/problems/encode-and-decode-strings/ Complexity: Time: O(n) for both encode and decode Space: O(n) """ from __future__ import annotations def encode(strs: str) -> str: """Encode a space-separated string into a length-prefixed format. Args: strs: A space-separated string of words. Returns: A single encoded string with length-prefixed words. Examples: >>> encode("keon is awesome") '4:keon2:is7:awesome' """ result = "" for word in strs.split(): result += str(len(word)) + ":" + word return result def decode(text: str) -> list[str]: """Decode a length-prefixed string back into a list of strings. Args: text: The encoded string with length-prefixed words. Returns: A list of the original decoded strings. Examples: >>> decode("4:keon2:is7:awesome") ['keon', 'is', 'awesome'] """ words: list[str] = [] index = 0 while index < len(text): colon_index = text.find(":", index) size = int(text[index:colon_index]) words.append(text[colon_index + 1 : colon_index + 1 + size]) index = colon_index + 1 + size return words ================================================ FILE: algorithms/string/first_unique_char.py ================================================ """ First Unique Character in a String Given a string, find the first non-repeating character and return its index. If no unique character exists, return -1. Reference: https://leetcode.com/problems/first-unique-character-in-a-string/ Complexity: Time: O(n^2) worst case due to nested comparisons Space: O(n) for the banned list """ from __future__ import annotations def first_unique_char(text: str) -> int: """Find the index of the first non-repeating character in a string. Args: text: The input string to search. Returns: The index of the first unique character, or -1 if none exists. Examples: >>> first_unique_char("leetcode") 0 """ if len(text) == 1: return 0 banned: list[str] = [] for index in range(len(text)): if ( all(text[index] != text[other] for other in range(index + 1, len(text))) and text[index] not in banned ): return index else: banned.append(text[index]) return -1 ================================================ FILE: algorithms/string/fizzbuzz.py ================================================ """ FizzBuzz Return an array of numbers from 1 to N, replacing multiples of 3 with 'Fizz', multiples of 5 with 'Buzz', and multiples of both with 'FizzBuzz'. Reference: https://en.wikipedia.org/wiki/Fizz_buzz Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations def fizzbuzz(number: int) -> list[int | str]: """Generate FizzBuzz sequence from 1 to number. Args: number: The upper bound of the sequence (inclusive). Returns: A list where multiples of 3 are 'Fizz', multiples of 5 are 'Buzz', multiples of both are 'FizzBuzz', and all others are the integer value. Raises: ValueError: If number is less than 1. TypeError: If number is None. Examples: >>> fizzbuzz(5) [1, 2, 'Fizz', 4, 'Buzz'] """ if number < 1: raise ValueError("n cannot be less than one") if number is None: raise TypeError("n cannot be None") result: list[int | str] = [] for value in range(1, number + 1): if value % 3 == 0 and value % 5 == 0: result.append("FizzBuzz") elif value % 3 == 0: result.append("Fizz") elif value % 5 == 0: result.append("Buzz") else: result.append(value) return result def fizzbuzz_with_helper_func(number: int) -> list[int | str]: """Generate FizzBuzz sequence using a helper function. Args: number: The upper bound of the sequence (inclusive). Returns: A list of FizzBuzz values from 1 to number. Examples: >>> fizzbuzz_with_helper_func(3) [1, 2, 'Fizz'] """ return [_fb(value) for value in range(1, number + 1)] def _fb(value: int) -> int | str: """Return the FizzBuzz value for a single number. Args: value: The number to evaluate. Returns: 'Fizz', 'Buzz', 'FizzBuzz', or the number itself. """ result = (value % 3 == 0) * "Fizz" + (value % 5 == 0) * "Buzz" return result if result != "" else value ================================================ FILE: algorithms/string/group_anagrams.py ================================================ """ Group Anagrams Given an array of strings, group anagrams together. Anagrams are words that contain the same letters in a different order. Reference: https://leetcode.com/problems/group-anagrams/ Complexity: Time: O(n * k log k) where n is the number of strings and k is max length Space: O(n * k) """ from __future__ import annotations def group_anagrams(strings: list[str]) -> list[list[str]]: """Group a list of strings by anagram equivalence. Args: strings: A list of strings to group. Returns: A list of groups, where each group contains strings that are anagrams. Examples: >>> group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]) [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']] """ anagram_map: dict[str, int] = {} groups: list[list[str]] = [] group_index = 0 for word in strings: sorted_word = "".join(sorted(word)) if sorted_word not in anagram_map: anagram_map[sorted_word] = group_index group_index += 1 groups.append([]) groups[-1].append(word) else: groups[anagram_map[sorted_word]].append(word) return groups ================================================ FILE: algorithms/string/int_to_roman.py ================================================ """ Integer to Roman Numeral Given an integer, convert it to a Roman numeral string. Input is guaranteed to be within the range from 1 to 3999. Reference: https://en.wikipedia.org/wiki/Roman_numerals Complexity: Time: O(1) since the input range is bounded Space: O(1) """ from __future__ import annotations def int_to_roman(num: int) -> str: """Convert an integer to its Roman numeral representation. Args: num: An integer between 1 and 3999. Returns: The Roman numeral string for the given integer. Examples: >>> int_to_roman(644) 'DCXLIV' """ thousands = ["", "M", "MM", "MMM"] hundreds = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"] tens = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"] ones = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"] return ( thousands[num // 1000] + hundreds[(num % 1000) // 100] + tens[(num % 100) // 10] + ones[num % 10] ) ================================================ FILE: algorithms/string/is_palindrome.py ================================================ """ Is Palindrome Determine if a string is a palindrome, considering only alphanumeric characters and ignoring cases. Multiple approaches are provided. Reference: https://en.wikipedia.org/wiki/Palindrome Complexity: Time: O(n) for all variations Space: O(n) for variations that create new strings, O(1) for two-pointer """ from __future__ import annotations from collections import deque from string import ascii_letters def is_palindrome(text: str) -> bool: """Check if a string is a palindrome using two pointers on the original. Args: text: The input string to check. Returns: True if the string is a palindrome, False otherwise. Examples: >>> is_palindrome("Otto") True """ left = 0 right = len(text) - 1 while left < right: while not text[left].isalnum(): left += 1 while not text[right].isalnum(): right -= 1 if text[left].lower() != text[right].lower(): return False left, right = left + 1, right - 1 return True def _remove_punctuation(text: str) -> str: """Remove punctuation, case sensitivity and spaces from a string. Args: text: The input string to clean. Returns: A lowercase string with only alphabetic characters. """ return "".join(char.lower() for char in text if char in ascii_letters) def _string_reverse(text: str) -> str: """Reverse a string using slicing. Args: text: The string to reverse. Returns: The reversed string. """ return text[::-1] def is_palindrome_reverse(text: str) -> bool: """Check if a string is a palindrome by comparing with its reverse. Args: text: The input string to check. Returns: True if the string is a palindrome, False otherwise. Examples: >>> is_palindrome_reverse("Otto") True """ text = _remove_punctuation(text) return text == _string_reverse(text) def is_palindrome_two_pointer(text: str) -> bool: """Check if a string is a palindrome using two pointers from both ends. Args: text: The input string to check. Returns: True if the string is a palindrome, False otherwise. Examples: >>> is_palindrome_two_pointer("Otto") True """ text = _remove_punctuation(text) for index in range(0, len(text) // 2): if text[index] != text[len(text) - index - 1]: return False return True def is_palindrome_stack(text: str) -> bool: """Check if a string is a palindrome using a stack. Args: text: The input string to check. Returns: True if the string is a palindrome, False otherwise. Examples: >>> is_palindrome_stack("Otto") True """ stack: list[str] = [] text = _remove_punctuation(text) for index in range(len(text) // 2, len(text)): stack.append(text[index]) return all(text[index] == stack.pop() for index in range(0, len(text) // 2)) def is_palindrome_deque(text: str) -> bool: """Check if a string is a palindrome using a deque. Args: text: The input string to check. Returns: True if the string is a palindrome, False otherwise. Examples: >>> is_palindrome_deque("Otto") True """ text = _remove_punctuation(text) character_deque: deque[str] = deque() for char in text: character_deque.appendleft(char) equal = True while len(character_deque) > 1 and equal: first = character_deque.pop() last = character_deque.popleft() if first != last: equal = False return equal ================================================ FILE: algorithms/string/is_rotated.py ================================================ """ Is Rotated String Given two strings, determine if the second is a rotated version of the first. Two approaches are provided: concatenation check and brute force. Reference: https://leetcode.com/problems/rotate-string/ Complexity: Time: O(n) for concatenation approach, O(n^2) for brute force Space: O(n) """ from __future__ import annotations def is_rotated(first: str, second: str) -> bool: """Check if second is a rotation of first using string concatenation. Args: first: The original string. second: The string to check as a rotation. Returns: True if second is a rotation of first, False otherwise. Examples: >>> is_rotated("hello", "llohe") True """ if len(first) == len(second): return second in first + first else: return False def is_rotated_v1(first: str, second: str) -> bool: """Check if second is a rotation of first using brute force comparison. Args: first: The original string. second: The string to check as a rotation. Returns: True if second is a rotation of first, False otherwise. Examples: >>> is_rotated_v1("hello", "llohe") True """ if len(first) != len(second): return False if len(first) == 0: return True for offset in range(len(first)): if all( first[(offset + index) % len(first)] == second[index] for index in range(len(first)) ): return True return False ================================================ FILE: algorithms/string/judge_circle.py ================================================ """ Judge Route Circle Given a sequence of robot moves (R, L, U, D), determine whether the robot returns to its starting position after completing all moves. Reference: https://leetcode.com/problems/robot-return-to-origin/ Complexity: Time: O(n) where n is the number of moves Space: O(1) """ from __future__ import annotations def judge_circle(moves: str) -> bool: """Determine whether a sequence of moves returns to the origin. Args: moves: A string of move characters ('U', 'D', 'L', 'R'). Returns: True if the robot ends at the starting position, False otherwise. Examples: >>> judge_circle("UD") True """ move_counts = { "U": 0, "D": 0, "R": 0, "L": 0, } for char in moves: move_counts[char] = move_counts[char] + 1 return move_counts["L"] == move_counts["R"] and move_counts["U"] == move_counts["D"] ================================================ FILE: algorithms/string/knuth_morris_pratt.py ================================================ """ Knuth-Morris-Pratt String Search Given two sequences (text and pattern), return the list of start indexes in text that match the pattern using the KMP algorithm. Reference: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Complexity: Time: O(n + m) where n = len(text), m = len(pattern) Space: O(m) for the prefix table """ from __future__ import annotations from collections.abc import Sequence def knuth_morris_pratt(text: Sequence, pattern: Sequence) -> list[int]: """Find all occurrences of pattern in text using the KMP algorithm. Args: text: The sequence to search in. pattern: The pattern sequence to search for. Returns: A list of starting indices where pattern occurs in text. Examples: >>> knuth_morris_pratt('hello there hero!', 'he') [0, 7, 12] """ text_length = len(text) pattern_length = len(pattern) prefix_table = [0 for _ in range(pattern_length)] match_length = 0 for index in range(1, pattern_length): while match_length and pattern[index] != pattern[match_length]: match_length = prefix_table[match_length - 1] if pattern[index] == pattern[match_length]: match_length += 1 prefix_table[index] = match_length match_length = 0 matches: list[int] = [] for index in range(text_length): while match_length and text[index] != pattern[match_length]: match_length = prefix_table[match_length - 1] if text[index] == pattern[match_length]: match_length += 1 if match_length == pattern_length: matches.append(index - pattern_length + 1) match_length = prefix_table[match_length - 1] return matches ================================================ FILE: algorithms/string/license_number.py ================================================ """ License Key Formatting Given a license key string and a group size k, reformat the key so that each group contains exactly k characters, separated by dashes. Reference: https://leetcode.com/problems/license-key-formatting/ Complexity: Time: O(n) where n is the length of the key Space: O(n) """ from __future__ import annotations def license_number(key: str, group_size: int) -> str: """Reformat a license key string into groups of a given size. Args: key: The license key string with dashes. group_size: The desired size of each group. Returns: The reformatted license key with groups separated by dashes. Examples: >>> license_number("a-bc-dfd-df", 2) 'ab-cd-fd-df' """ result: list[str] = [] alphanumeric: list[str] = [] for char in key: if char != "-": alphanumeric.append(char) for index, char in enumerate(reversed(alphanumeric)): result.append(char) if (index + 1) % group_size == 0 and index != len(alphanumeric) - 1: result.append("-") return "".join(result[::-1]) ================================================ FILE: algorithms/string/longest_common_prefix.py ================================================ """ Longest Common Prefix Find the longest common prefix string amongst an array of strings. Three approaches: horizontal scanning, vertical scanning, and divide and conquer. Reference: https://leetcode.com/problems/longest-common-prefix/ Complexity: Time: O(S) where S is the sum of all characters in all strings Space: O(1) for iterative, O(m * log n) for divide and conquer """ from __future__ import annotations def _common_prefix(first: str, second: str) -> str: """Return the common prefix of two strings. Args: first: The first string. second: The second string. Returns: The common prefix shared by both strings. """ if not first or not second: return "" index = 0 while first[index] == second[index]: index = index + 1 if index >= len(first) or index >= len(second): return first[0:index] return first[0:index] def longest_common_prefix_v1(strings: list[str]) -> str: """Find longest common prefix using horizontal scanning. Args: strings: A list of strings to compare. Returns: The longest common prefix, or empty string if none exists. Examples: >>> longest_common_prefix_v1(["flower", "flow", "flight"]) 'fl' """ if not strings: return "" result = strings[0] for index in range(len(strings)): result = _common_prefix(result, strings[index]) return result def longest_common_prefix_v2(strings: list[str]) -> str: """Find longest common prefix using vertical scanning. Args: strings: A list of strings to compare. Returns: The longest common prefix, or empty string if none exists. Examples: >>> longest_common_prefix_v2(["flower", "flow", "flight"]) 'fl' """ if not strings: return "" for index in range(len(strings[0])): for string in strings[1:]: if index == len(string) or string[index] != strings[0][index]: return strings[0][0:index] return strings[0] def longest_common_prefix_v3(strings: list[str]) -> str: """Find longest common prefix using divide and conquer. Args: strings: A list of strings to compare. Returns: The longest common prefix, or empty string if none exists. Examples: >>> longest_common_prefix_v3(["flower", "flow", "flight"]) 'fl' """ if not strings: return "" return _longest_common_recursive(strings, 0, len(strings) - 1) def _longest_common_recursive(strings: list[str], left: int, right: int) -> str: """Recursively find the longest common prefix using divide and conquer. Args: strings: The list of strings. left: The left index of the current partition. right: The right index of the current partition. Returns: The longest common prefix for the partition. """ if left == right: return strings[left] mid = (left + right) // 2 lcp_left = _longest_common_recursive(strings, left, mid) lcp_right = _longest_common_recursive(strings, mid + 1, right) return _common_prefix(lcp_left, lcp_right) ================================================ FILE: algorithms/string/longest_palindromic_substring.py ================================================ """ Longest Palindromic Substring Given a string, find the longest palindromic substring using Manacher's algorithm, which runs in linear time. Reference: https://en.wikipedia.org/wiki/Longest_palindromic_substring Complexity: Time: O(n) using Manacher's algorithm Space: O(n) """ from __future__ import annotations def longest_palindrome(text: str) -> str: """Find the longest palindromic substring using Manacher's algorithm. Args: text: The input string to search. Returns: The longest palindromic substring. Examples: >>> longest_palindrome("cbbd") 'bb' """ if len(text) < 2: return text expanded = "#" + "#".join(text) + "#" palindrome_radii = [0] * len(expanded) center, right_boundary = 0, 0 best_index, best_length = 0, 0 for index in range(len(expanded)): if index < right_boundary and 2 * center - index < len(expanded): palindrome_radii[index] = min( right_boundary - index, palindrome_radii[2 * center - index], ) else: palindrome_radii[index] = 1 while ( palindrome_radii[index] + index < len(expanded) and index - palindrome_radii[index] >= 0 and expanded[index - palindrome_radii[index]] == expanded[index + palindrome_radii[index]] ): palindrome_radii[index] += 1 if index + palindrome_radii[index] > right_boundary: right_boundary = index + palindrome_radii[index] center = index if palindrome_radii[index] > best_length: best_index = index best_length = palindrome_radii[index] substring = expanded[ best_index - palindrome_radii[best_index] + 1 : best_index + palindrome_radii[best_index] ] return substring.replace("#", "") ================================================ FILE: algorithms/string/make_sentence.py ================================================ """ Make Sentence For a given string and dictionary, count how many sentences can be formed from the string such that all words are contained in the dictionary. Reference: https://en.wikipedia.org/wiki/Word_break_problem Complexity: Time: O(2^n) worst case due to recursive exploration Space: O(n) recursion depth """ from __future__ import annotations count = 0 def make_sentence(text_piece: str, dictionaries: list[str]) -> bool: """Check if a string can be segmented into dictionary words and count ways. Updates the global ``count`` variable with the number of valid segmentations. Args: text_piece: The string to segment. dictionaries: A list of valid dictionary words. Returns: True if any segmentation is possible (always returns True). Examples: >>> make_sentence("applet", ["", "app", "let", "t", "apple", "applet"]) True """ global count if len(text_piece) == 0: return True for index in range(0, len(text_piece)): prefix, suffix = text_piece[0:index], text_piece[index:] if (prefix in dictionaries and (suffix in dictionaries or make_sentence(suffix, dictionaries))): count += 1 return True ================================================ FILE: algorithms/string/manacher.py ================================================ """Manacher's algorithm — find the longest palindromic substring in O(n). Manacher's algorithm uses the symmetry of palindromes to avoid redundant comparisons, achieving linear time. It transforms the input to handle both odd- and even-length palindromes uniformly. Inspired by PR #931 (Simranstha045). """ def manacher(s: str) -> str: """Return the longest palindromic substring of *s* in O(n) time.""" # Transform "abc" -> "^#a#b#c#$" so every palindrome has odd length t = "^#" + "#".join(s) + "#$" n = len(t) p = [0] * n # p[i] = radius of palindrome centred at i centre = right = 0 for i in range(1, n - 1): mirror = 2 * centre - i if i < right: p[i] = min(right - i, p[mirror]) while t[i + p[i] + 1] == t[i - p[i] - 1]: p[i] += 1 if i + p[i] > right: centre, right = i, i + p[i] max_len, centre_index = max((v, i) for i, v in enumerate(p)) start = (centre_index - max_len) // 2 return s[start : start + max_len] ================================================ FILE: algorithms/string/merge_string_checker.py ================================================ """ Merge String Checker Determine if a given string can be formed by interleaving two other strings, preserving the character order from each part. Reference: https://leetcode.com/problems/interleaving-string/ Complexity: Time: O(2^n) worst case for recursive, similar for iterative Space: O(n) for recursion depth / stack """ from __future__ import annotations def is_merge_recursive(text: str, part1: str, part2: str) -> bool: """Check if text is an interleaving of part1 and part2 recursively. Args: text: The merged string to verify. part1: The first source string. part2: The second source string. Returns: True if text is a valid interleaving of part1 and part2. Examples: >>> is_merge_recursive("codewars", "cdw", "oears") True """ if not part1: return text == part2 if not part2: return text == part1 if not text: return part1 + part2 == "" if text[0] == part1[0] and is_merge_recursive(text[1:], part1[1:], part2): return True return (text[0] == part2[0] and is_merge_recursive(text[1:], part1, part2[1:])) def is_merge_iterative(text: str, part1: str, part2: str) -> bool: """Check if text is an interleaving of part1 and part2 iteratively. Args: text: The merged string to verify. part1: The first source string. part2: The second source string. Returns: True if text is a valid interleaving of part1 and part2. Examples: >>> is_merge_iterative("codewars", "cdw", "oears") True """ tuple_list = [(text, part1, part2)] while tuple_list: string, first_part, second_part = tuple_list.pop() if string: if first_part and string[0] == first_part[0]: tuple_list.append((string[1:], first_part[1:], second_part)) if second_part and string[0] == second_part[0]: tuple_list.append((string[1:], first_part, second_part[1:])) else: if not first_part and not second_part: return True return False ================================================ FILE: algorithms/string/min_distance.py ================================================ """ Minimum Edit Distance (Delete Operation) Given two words, find the minimum number of steps required to make them the same, where each step deletes one character from either string. Reference: https://leetcode.com/problems/delete-operation-for-two-strings/ Complexity: Time: O(2^n) for recursive LCS, O(m * n) for DP approach Space: O(m * n) for DP table """ from __future__ import annotations def min_distance(word1: str, word2: str) -> int: """Find minimum deletions to make two words equal via recursive LCS. Args: word1: The first word. word2: The second word. Returns: The minimum number of deletion steps. Examples: >>> min_distance("sea", "eat") 2 """ return len(word1) + len(word2) - 2 * _lcs(word1, word2, len(word1), len(word2)) def _lcs(word1: str, word2: str, length1: int, length2: int) -> int: """Compute the length of the longest common subsequence recursively. Args: word1: The first word. word2: The second word. length1: Current length to consider in word1. length2: Current length to consider in word2. Returns: The length of the longest common subsequence. """ if length1 == 0 or length2 == 0: return 0 if word1[length1 - 1] == word2[length2 - 1]: return 1 + _lcs(word1, word2, length1 - 1, length2 - 1) return max( _lcs(word1, word2, length1 - 1, length2), _lcs(word1, word2, length1, length2 - 1), ) def min_distance_dp(word1: str, word2: str) -> int: """Find minimum deletions to make two words equal using dynamic programming. Args: word1: The first word. word2: The second word. Returns: The minimum number of deletion steps. Examples: >>> min_distance_dp("sea", "eat") 2 """ rows, cols = len(word1) + 1, len(word2) + 1 table = [[0 for _ in range(cols)] for _ in range(rows)] if rows == cols: for index in range(1, rows): table[index][0], table[0][index] = index, index else: for index in range(rows): table[index][0] = index for index in range(cols): table[0][index] = index for row in range(1, rows): for col in range(1, cols): if word1[row - 1] == word2[col - 1]: table[row][col] = table[row - 1][col - 1] else: table[row][col] = min(table[row - 1][col], table[row][col - 1]) + 1 return table[len(word1)][len(word2)] ================================================ FILE: algorithms/string/multiply_strings.py ================================================ """ Multiply Strings Given two non-negative integers represented as strings, return their product as a string without using built-in BigInteger or direct integer conversion. Reference: https://leetcode.com/problems/multiply-strings/ Complexity: Time: O(m * n) where m, n are the lengths of the two numbers Space: O(m + n) """ from __future__ import annotations def multiply(num1: str, num2: str) -> str: """Multiply two numbers represented as strings. Args: num1: The first number as a string. num2: The second number as a string. Returns: The product of the two numbers as a string. Examples: >>> multiply("23", "23") '529' """ intermediate: list[int] = [] zero = ord("0") position_i = 1 for digit_i in reversed(num1): position_j = 1 accumulator = 0 for digit_j in reversed(num2): product = ( (ord(digit_i) - zero) * (ord(digit_j) - zero) * position_j * position_i ) position_j *= 10 accumulator += product position_i *= 10 intermediate.append(accumulator) return str(sum(intermediate)) ================================================ FILE: algorithms/string/one_edit_distance.py ================================================ """ One Edit Distance Given two strings, determine if they are exactly one edit distance apart. An edit is an insertion, deletion, or replacement of a single character. Reference: https://leetcode.com/problems/one-edit-distance/ Complexity: Time: O(n) where n is the length of the shorter string Space: O(n) for string slicing """ from __future__ import annotations def is_one_edit(source: str, target: str) -> bool: """Check if two strings are exactly one edit apart using slicing. Args: source: The first string. target: The second string. Returns: True if the strings are exactly one edit apart, False otherwise. Examples: >>> is_one_edit("abc", "abd") True """ if len(source) > len(target): return is_one_edit(target, source) if len(target) - len(source) > 1 or target == source: return False for index in range(len(source)): if source[index] != target[index]: return ( source[index + 1 :] == target[index + 1 :] or source[index:] == target[index + 1 :] ) return True def is_one_edit2(source: str, target: str) -> bool: """Check if two strings are exactly one edit apart using modification. Args: source: The first string. target: The second string. Returns: True if the strings are exactly one edit apart, False otherwise. Examples: >>> is_one_edit2("abc", "abd") True """ source_length, target_length = len(source), len(target) if source_length > target_length: return is_one_edit2(target, source) if len(target) - len(source) > 1 or target == source: return False for index in range(len(source)): if source[index] != target[index]: if source_length == target_length: source = source[:index] + target[index] + source[index + 1 :] else: source = source[:index] + target[index] + source[index:] break return source == target or source == target[:-1] ================================================ FILE: algorithms/string/panagram.py ================================================ """ Panagram Checker Check whether a given string is a panagram (a sentence using every letter of the English alphabet at least once). Reference: https://en.wikipedia.org/wiki/Pangram Complexity: Time: O(n) where n is the length of the string Space: O(1) since the letter set is bounded at 26 """ from __future__ import annotations from string import ascii_lowercase def panagram(string: str) -> bool: """Check if the input string is an English panagram. Args: string: A sentence to check. Returns: True if the string contains every English letter, False otherwise. Examples: >>> panagram("the quick brown fox jumps over the lazy dog") True """ letters = set(ascii_lowercase) for char in string: letters.discard(char.lower()) return len(letters) == 0 ================================================ FILE: algorithms/string/rabin_karp.py ================================================ """ Rabin-Karp String Search A string searching algorithm that uses hashing to find a pattern in text. Uses a rolling hash to efficiently compare the pattern hash with substrings. Reference: https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm Complexity: Time: O(n + m) average, O(n * m) worst case Space: O(1) """ from __future__ import annotations class RollingHash: """A rolling hash implementation for the Rabin-Karp algorithm. Args: text: The text to compute the hash over. window_size: The size of the hash window. """ def __init__(self, text: str, window_size: int) -> None: self.text = text self.hash = 0 self.window_size = window_size for index in range(0, window_size): self.hash += (ord(self.text[index]) - ord("a") + 1) * ( 26 ** (window_size - index - 1) ) self.window_start = 0 self.window_end = window_size def move_window(self) -> None: """Slide the hash window one position to the right.""" if self.window_end <= len(self.text) - 1: self.hash -= (ord(self.text[self.window_start]) - ord("a") + 1) * 26 ** ( self.window_size - 1 ) self.hash *= 26 self.hash += ord(self.text[self.window_end]) - ord("a") + 1 self.window_start += 1 self.window_end += 1 def window_text(self) -> str: """Return the current text within the hash window. Returns: The substring currently covered by the rolling hash window. """ return self.text[self.window_start : self.window_end] def rabin_karp(word: str, text: str) -> int | None: """Find the first occurrence of word in text using the Rabin-Karp algorithm. Args: word: The pattern to search for. text: The text to search in. Returns: The index of the first occurrence, or None if not found. Examples: >>> rabin_karp("abc", "zsnabckfkd") 3 """ if word == "" or text == "": return None if len(word) > len(text): return None rolling_hash = RollingHash(text, len(word)) word_hash = RollingHash(word, len(word)) for _ in range(len(text) - len(word) + 1): if (rolling_hash.hash == word_hash.hash and rolling_hash.window_text() == word): return rolling_hash.window_start rolling_hash.move_window() return None ================================================ FILE: algorithms/string/repeat_string.py ================================================ """ Repeated String Match Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of the result. Return -1 if impossible. Reference: https://leetcode.com/problems/repeated-string-match/ Complexity: Time: O(n * m) where n = len(A), m = len(B) Space: O(n * (m/n + 2)) """ from __future__ import annotations def repeat_string(base: str, target: str) -> int: """Find minimum repetitions of base needed to contain target as a substring. Args: base: The string to repeat. target: The string that should appear as a substring. Returns: The minimum number of repetitions, or -1 if not possible. Examples: >>> repeat_string("abcd", "cdabcdab") 3 """ repetition_count = 1 repeated = base max_count = (len(target) / len(base)) + 1 while target not in repeated: repeated = repeated + base if repetition_count > max_count: repetition_count = -1 break repetition_count = repetition_count + 1 return repetition_count ================================================ FILE: algorithms/string/repeat_substring.py ================================================ """ Repeated Substring Pattern Given a non-empty string, check if it can be constructed by taking a substring of it and appending multiple copies of the substring together. Reference: https://leetcode.com/problems/repeated-substring-pattern/ Complexity: Time: O(n) for the string containment check Space: O(n) """ from __future__ import annotations def repeat_substring(text: str) -> bool: """Check if a string is composed of a repeated substring pattern. Args: text: The input string to check. Returns: True if the string is a repeated pattern, False otherwise. Examples: >>> repeat_substring("abab") True """ doubled = (text + text)[1:-1] return text in doubled ================================================ FILE: algorithms/string/reverse_string.py ================================================ """ Reverse String Reverse a string using four different approaches: recursive, iterative, pythonic (using reversed), and ultra-pythonic (using slicing). Reference: https://en.wikipedia.org/wiki/String_(computer_science)#Reversal Complexity: Time: O(n) for all approaches Space: O(n) for all approaches, O(log n) stack for recursive """ from __future__ import annotations def recursive(text: str) -> str: """Reverse a string using a recursive divide-and-conquer approach. Args: text: The string to reverse. Returns: The reversed string. Examples: >>> recursive("hello there") 'ereht olleh' """ length = len(text) if length < 2: return text return recursive(text[length // 2 :]) + recursive(text[: length // 2]) def iterative(text: str) -> str: """Reverse a string using iterative character swapping. Args: text: The string to reverse. Returns: The reversed string. Examples: >>> iterative("hello there") 'ereht olleh' """ characters = list(text) left, right = 0, len(text) - 1 while left < right: characters[left], characters[right] = ( characters[right], characters[left], ) left += 1 right -= 1 return "".join(characters) def pythonic(text: str) -> str: """Reverse a string using the built-in reversed function. Args: text: The string to reverse. Returns: The reversed string. Examples: >>> pythonic("hello there") 'ereht olleh' """ characters = list(reversed(text)) return "".join(characters) def ultra_pythonic(text: str) -> str: """Reverse a string using slice notation. Args: text: The string to reverse. Returns: The reversed string. Examples: >>> ultra_pythonic("hello there") 'ereht olleh' """ return text[::-1] ================================================ FILE: algorithms/string/reverse_vowel.py ================================================ """ Reverse Vowels of a String Given a string, reverse only the vowels while keeping all other characters in their original positions. Reference: https://leetcode.com/problems/reverse-vowels-of-a-string/ Complexity: Time: O(n) where n is the length of the string Space: O(n) for the character list """ from __future__ import annotations def reverse_vowel(text: str) -> str: """Reverse only the vowels in a string. Args: text: The input string. Returns: A new string with vowels reversed. Examples: >>> reverse_vowel("hello") 'holle' """ vowels = "AEIOUaeiou" left, right = 0, len(text) - 1 characters = list(text) while left < right: while left < right and characters[left] not in vowels: left += 1 while left < right and characters[right] not in vowels: right -= 1 characters[left], characters[right] = ( characters[right], characters[left], ) left, right = left + 1, right - 1 return "".join(characters) ================================================ FILE: algorithms/string/reverse_words.py ================================================ """ Reverse Words in a String Given a string, reverse the order of words. Leading and trailing spaces are trimmed and words are separated by single spaces. Reference: https://leetcode.com/problems/reverse-words-in-a-string/ Complexity: Time: O(n) where n is the length of the string Space: O(n) """ from __future__ import annotations def _reverse_list(array: list[str], left: int, right: int) -> None: """Reverse a portion of a list in place. Args: array: The list to modify. left: The starting index. right: The ending index. """ while left < right: array[left], array[right] = array[right], array[left] left += 1 right -= 1 def reverse_words(string: str) -> str: """Reverse the order of words in a string. Args: string: The input string of words. Returns: A string with the word order reversed. Examples: >>> reverse_words("I am keon kim and I like pizza") 'pizza like I and kim keon am I' """ words = string.strip().split() word_count = len(words) _reverse_list(words, 0, word_count - 1) return " ".join(words) ================================================ FILE: algorithms/string/roman_to_int.py ================================================ """ Roman Numeral to Integer Given a Roman numeral string, convert it to an integer. Input is guaranteed to be within the range from 1 to 3999. Reference: https://en.wikipedia.org/wiki/Roman_numerals Complexity: Time: O(n) where n is the length of the Roman numeral string Space: O(1) """ from __future__ import annotations def roman_to_int(text: str) -> int: """Convert a Roman numeral string to an integer. Args: text: A valid Roman numeral string. Returns: The integer value of the Roman numeral. Examples: >>> roman_to_int("DCXXI") 621 """ number = 0 roman_values = { "M": 1000, "D": 500, "C": 100, "L": 50, "X": 10, "V": 5, "I": 1, } for index in range(len(text) - 1): if roman_values[text[index]] < roman_values[text[index + 1]]: number -= roman_values[text[index]] else: number += roman_values[text[index]] return number + roman_values[text[-1]] ================================================ FILE: algorithms/string/rotate.py ================================================ """ Rotate String Given a string and an integer k, return the string rotated by k positions to the left. Two approaches are provided. Reference: https://en.wikipedia.org/wiki/Circular_shift Complexity: Time: O(n) where n is the length of the string Space: O(n) """ from __future__ import annotations def rotate(text: str, positions: int) -> str: """Rotate a string left by k positions using repeated string approach. Args: text: The string to rotate. positions: The number of positions to rotate left. Returns: The rotated string. Examples: >>> rotate("hello", 2) 'llohe' """ long_string = text * (positions // len(text) + 2) if positions <= len(text): return long_string[positions : positions + len(text)] else: return long_string[positions - len(text) : positions] def rotate_alt(string: str, positions: int) -> str: """Rotate a string left by k positions using modular arithmetic. Args: string: The string to rotate. positions: The number of positions to rotate left. Returns: The rotated string. Examples: >>> rotate_alt("hello", 2) 'llohe' """ positions = positions % len(string) return string[positions:] + string[:positions] ================================================ FILE: algorithms/string/strip_url_params.py ================================================ """ Strip URL Parameters Remove duplicate query string parameters from a URL and optionally remove specified parameters. Three approaches of increasing Pythonic style. Reference: https://en.wikipedia.org/wiki/Query_string Complexity: Time: O(n) where n is the length of the URL Space: O(n) """ from __future__ import annotations import urllib import urllib.parse from collections import defaultdict def strip_url_params1(url: str, params_to_strip: list[str] | None = None) -> str: """Remove duplicate and specified URL parameters using manual parsing. Args: url: The URL string to process. params_to_strip: Optional list of parameter names to remove. Returns: The URL with duplicate and specified parameters removed. Examples: >>> strip_url_params1("www.saadbenn.com?a=1&b=2&a=2") 'www.saadbenn.com?a=1&b=2' """ if not params_to_strip: params_to_strip = [] if url: result = "" tokens = url.split("?") domain = tokens[0] query_string = tokens[-1] result += domain if len(tokens) > 1: result += "?" if not query_string: return url else: key_value_pairs: list[str] = [] fragment = "" for char in query_string: if char.isdigit(): key_value_pairs.append(fragment + char) fragment = "" else: fragment += char seen: dict[str, int] = defaultdict(int) for pair in key_value_pairs: token_parts = pair.split("=") if token_parts[0]: length = len(token_parts[0]) if length == 1: if token_parts and (token_parts[0] not in seen): if params_to_strip: if token_parts[0] != params_to_strip[0]: seen[token_parts[0]] = token_parts[1] result = ( result + token_parts[0] + "=" + token_parts[1] ) else: if token_parts[0] not in seen: seen[token_parts[0]] = token_parts[1] result = ( result + token_parts[0] + "=" + token_parts[1] ) else: check = token_parts[0] letter = check[1] if token_parts and (letter not in seen): if params_to_strip: if letter != params_to_strip[0]: seen[letter] = token_parts[1] result = ( result + token_parts[0] + "=" + token_parts[1] ) else: if letter not in seen: seen[letter] = token_parts[1] result = ( result + token_parts[0] + "=" + token_parts[1] ) return result def strip_url_params2(url: str, param_to_strip: list[str] | None = None) -> str: """Remove duplicate and specified URL parameters using list operations. Args: url: The URL string to process. param_to_strip: Optional list of parameter names to remove. Returns: The URL with duplicate and specified parameters removed. Examples: >>> strip_url_params2("www.saadbenn.com?a=1&b=2&a=2") 'www.saadbenn.com?a=1&b=2' """ if param_to_strip is None: param_to_strip = [] if "?" not in url: return url queries = (url.split("?")[1]).split("&") query_keys = [query[0] for query in queries] for index in range(len(query_keys) - 1, 0, -1): if ( query_keys[index] in param_to_strip or query_keys[index] in query_keys[0:index] ): queries.pop(index) return url.split("?")[0] + "?" + "&".join(queries) def strip_url_params3(url: str, strip: list[str] | None = None) -> str: """Remove duplicate and specified URL parameters using urllib. Args: url: The URL string to process. strip: Optional list of parameter names to remove. Returns: The URL with duplicate and specified parameters removed. Examples: >>> strip_url_params3("www.saadbenn.com?a=1&b=2&a=2") 'www.saadbenn.com?a=1&b=2' """ if not strip: strip = [] parsed = urllib.parse.urlparse(url) query = urllib.parse.parse_qs(parsed.query) query = {key: values[0] for key, values in query.items() if key not in strip} query_string = urllib.parse.urlencode(query) new_parsed = parsed._replace(query=query_string) return new_parsed.geturl() ================================================ FILE: algorithms/string/strong_password.py ================================================ """ Strong Password Checker Given a password string, determine the minimum number of characters that must be added to make it strong. A strong password has at least 6 characters, a digit, a lowercase letter, an uppercase letter, and a special character. Reference: https://www.hackerrank.com/challenges/strong-password/problem Complexity: Time: O(n) where n is the length of the password Space: O(1) """ from __future__ import annotations def strong_password(length: int, password: str) -> int: """Calculate minimum characters to add for a strong password. Args: length: The current length of the password. password: The password string to evaluate. Returns: The minimum number of characters to add. Examples: >>> strong_password(3, "Ab1") 3 """ missing_types = 0 if not any(char.isdigit() for char in password): missing_types += 1 if not any(char.islower() for char in password): missing_types += 1 if not any(char.isupper() for char in password): missing_types += 1 if not any(char in "!@#$%^&*()-+" for char in password): missing_types += 1 return max(missing_types, 6 - length) ================================================ FILE: algorithms/string/swap_characters.py ================================================ """Swap characters — check if two strings can be made equal by one swap. Given two strings of equal length, determine whether exactly one swap of two characters in the first string can make it equal to the second. Inspired by PR #890 (Thejas-1). """ from __future__ import annotations def can_swap_to_equal(s: str, t: str) -> bool: """Return True if swapping exactly one pair in *s* yields *t*.""" if len(s) != len(t): return False diffs = [(a, b) for a, b in zip(s, t, strict=False) if a != b] return len(diffs) == 2 and diffs[0] == diffs[1][::-1] ================================================ FILE: algorithms/string/text_justification.py ================================================ """ Text Justification Given an array of words and a max width, format the text such that each line has exactly max_width characters and is fully justified. Extra spaces are distributed as evenly as possible with left slots getting more. Reference: https://leetcode.com/problems/text-justification/ Complexity: Time: O(n) where n is the total number of characters Space: O(n) """ from __future__ import annotations def text_justification(words: list[str], max_width: int) -> list[str]: """Justify text to a fixed width with evenly distributed spaces. Args: words: A list of words to justify. max_width: The maximum width of each line. Returns: A list of fully justified strings. Raises: ValueError: If any word is longer than max_width. Examples: >>> text_justification(["What", "must", "be"], 16) ['What must be'] """ result: list[str] = [] row_length = 0 row_words: list[str] = [] index = 0 is_first_word = True while index < len(words): while row_length <= max_width and index < len(words): if len(words[index]) > max_width: raise ValueError( "there exists word whose length is larger than max_width" ) tentative_length = row_length row_words.append(words[index]) tentative_length += len(words[index]) if not is_first_word: tentative_length += 1 if tentative_length > max_width: row_words.pop() break row_length = tentative_length index += 1 is_first_word = False row = "" if index == len(words): for word in row_words: row += word + " " row = row[:-1] row += " " * (max_width - len(row)) elif len(row_words) != 1: extra_spaces = max_width - row_length spaces_per_gap = extra_spaces // (len(row_words) - 1) remaining_spaces = extra_spaces - spaces_per_gap * (len(row_words) - 1) for word_index in range(len(row_words)): row += row_words[word_index] if word_index != len(row_words) - 1: row += " " * (1 + spaces_per_gap) if remaining_spaces > 0: row += " " remaining_spaces -= 1 else: row += row_words[0] row += " " * (max_width - len(row)) result.append(row) row_length = 0 row_words = [] is_first_word = True return result ================================================ FILE: algorithms/string/unique_morse.py ================================================ """ Unique Morse Code Representations Given a list of words, determine the number of unique Morse code transformations among all the words. Reference: https://leetcode.com/problems/unique-morse-code-words/ Complexity: Time: O(n * k) where n is the number of words, k is average word length Space: O(n) """ from __future__ import annotations _MORSE_CODE = { "a": ".-", "b": "-...", "c": "-.-.", "d": "-..", "e": ".", "f": "..-.", "g": "--.", "h": "....", "i": "..", "j": ".---", "k": "-.-", "l": ".-..", "m": "--", "n": "-.", "o": "---", "p": ".--.", "q": "--.-", "r": ".-.", "s": "...", "t": "-", "u": "..-", "v": "...-", "w": ".--", "x": "-..-", "y": "-.--", "z": "--..", } def convert_morse_word(word: str) -> str: """Convert a word to its Morse code representation. Args: word: The word to convert (case-insensitive). Returns: The Morse code string for the word. Examples: >>> convert_morse_word("gin") '--...-.' """ morse_word = "" word = word.lower() for char in word: morse_word = morse_word + _MORSE_CODE[char] return morse_word def unique_morse(words: list[str]) -> int: """Count the number of unique Morse code transformations. Args: words: A list of words to transform and count. Returns: The number of distinct Morse code representations. Examples: >>> unique_morse(["gin", "zen", "gig", "msg"]) 2 """ unique_transformations: list[str] = [] for word in words: morse_word = convert_morse_word(word) if morse_word not in unique_transformations: unique_transformations.append(morse_word) return len(unique_transformations) ================================================ FILE: algorithms/string/validate_coordinates.py ================================================ """ Validate Coordinates Validate if given parameters are valid geographical coordinates. Latitude must be between -90 and 90, longitude between -180 and 180. Reference: https://en.wikipedia.org/wiki/Geographic_coordinate_system Complexity: Time: O(n) where n is the length of the coordinate string Space: O(1) """ from __future__ import annotations import re def is_valid_coordinates_0(coordinates: str) -> bool: """Validate coordinates by character checking and parsing. Args: coordinates: A string of the form "lat, lng". Returns: True if the coordinates are valid, False otherwise. Examples: >>> is_valid_coordinates_0("-23, 25") True """ for char in coordinates: if not (char.isdigit() or char in ["-", ".", ",", " "]): return False parts = coordinates.split(", ") if len(parts) != 2: return False try: latitude = float(parts[0]) longitude = float(parts[1]) except ValueError: return False return -90 <= latitude <= 90 and -180 <= longitude <= 180 def is_valid_coordinates_1(coordinates: str) -> bool: """Validate coordinates by splitting and converting to floats. Args: coordinates: A string of the form "lat, lng". Returns: True if the coordinates are valid, False otherwise. Examples: >>> is_valid_coordinates_1("43.91343345, 143") True """ try: latitude, longitude = [ abs(float(part)) for part in coordinates.split(",") if "e" not in part ] except ValueError: return False return latitude <= 90 and longitude <= 180 def is_valid_coordinates_regular_expression(coordinates: str) -> bool: """Validate coordinates using a regular expression. Args: coordinates: A string of the form "lat, lng". Returns: True if the coordinates are valid, False otherwise. Examples: >>> is_valid_coordinates_regular_expression("4, -3") True """ return bool( re.match( r"-?(\d|[1-8]\d|90)\.?\d*, -?(\d|[1-9]\d|1[0-7]\d|180)\.?\d*$", coordinates, ) ) ================================================ FILE: algorithms/string/word_squares.py ================================================ """ Word Squares Given a set of words (without duplicates), find all word squares that can be built from them. A word square reads the same horizontally and vertically. Reference: https://leetcode.com/problems/word-squares/ Complexity: Time: O(n * 26^L) where n is the number of words, L is word length Space: O(n * L) for the prefix map """ from __future__ import annotations import collections def word_squares(words: list[str]) -> list[list[str]]: """Find all valid word squares from a list of same-length words. Args: words: A list of words, all having the same length. Returns: A list of word squares, where each square is a list of words. Examples: >>> word_squares(["area", "lead", "wall", "lady", "ball"]) [['wall', 'area', 'lead', 'lady'], ['ball', 'area', 'lead', 'lady']] """ word_length = len(words[0]) prefix_map: dict[str, list[str]] = collections.defaultdict(list) for word in words: for index in range(word_length): prefix_map[word[:index]].append(word) def _build(square: list[str]) -> None: if len(square) == word_length: squares.append(square) return prefix = "" for row in range(len(square)): prefix += square[row][len(square)] for word in prefix_map[prefix]: _build(square + [word]) squares: list[list[str]] = [] for word in words: _build([word]) return squares ================================================ FILE: algorithms/string/z_algorithm.py ================================================ """Z-algorithm — linear-time pattern matching via the Z-array. The Z-array for a string S stores at Z[i] the length of the longest substring starting at S[i] that is also a prefix of S. By concatenating pattern + '$' + text, occurrences of the pattern correspond to positions where Z[i] == len(pattern). Inspired by PR #930 (Simranstha045). """ from __future__ import annotations def compute_z_array(s: str) -> list[int]: """Compute the Z-array for string *s* in O(n) time.""" n = len(s) if n == 0: return [] z = [0] * n z[0] = n left = right = 0 for i in range(1, n): if i < right: z[i] = min(right - i, z[i - left]) while i + z[i] < n and s[z[i]] == s[i + z[i]]: z[i] += 1 if i + z[i] > right: left, right = i, i + z[i] return z def z_search(text: str, pattern: str) -> list[int]: """Return all starting indices where *pattern* occurs in *text*.""" if not pattern or not text: return [] concat = pattern + "$" + text z = compute_z_array(concat) m = len(pattern) return [i - m - 1 for i in range(m + 1, len(concat)) if z[i] == m] ================================================ FILE: algorithms/tree/__init__.py ================================================ """ Tree Algorithms A collection of binary tree and general tree algorithms including traversal, search, construction, and property-checking operations. """ from algorithms.data_structures.b_tree import BTree from algorithms.data_structures.b_tree import Node as BTreeNode from algorithms.tree.bin_tree_to_list import bin_tree_to_list from algorithms.tree.binary_tree_paths import binary_tree_paths from algorithms.tree.binary_tree_views import ( bottom_view, left_view, right_view, top_view, ) from algorithms.tree.construct_tree_postorder_preorder import ( construct_tree, construct_tree_util, ) from algorithms.tree.deepest_left import DeepestLeft, find_deepest_left from algorithms.tree.invert_tree import reverse from algorithms.tree.is_balanced import is_balanced from algorithms.tree.is_subtree import is_subtree from algorithms.tree.is_symmetric import is_symmetric, is_symmetric_iterative from algorithms.tree.longest_consecutive import longest_consecutive from algorithms.tree.lowest_common_ancestor import lca from algorithms.tree.max_height import max_height from algorithms.tree.max_path_sum import max_path_sum from algorithms.tree.min_height import min_depth, min_height from algorithms.tree.path_sum import has_path_sum, has_path_sum2, has_path_sum3 from algorithms.tree.path_sum2 import path_sum, path_sum2, path_sum3 from algorithms.tree.pretty_print import tree_print from algorithms.tree.same_tree import is_same_tree from algorithms.tree.tree import TreeNode __all__ = [ "BTree", "BTreeNode", "DeepestLeft", "TreeNode", "bin_tree_to_list", "binary_tree_paths", "construct_tree", "construct_tree_util", "find_deepest_left", "has_path_sum", "has_path_sum2", "has_path_sum3", "is_balanced", "is_same_tree", "is_subtree", "is_symmetric", "is_symmetric_iterative", "lca", "longest_consecutive", "max_height", "max_path_sum", "min_depth", "min_height", "path_sum", "path_sum2", "path_sum3", "reverse", "tree_print", "left_view", "right_view", "top_view", "bottom_view", ] ================================================ FILE: algorithms/tree/bin_tree_to_list.py ================================================ """ Binary Tree to Doubly Linked List Converts a binary tree to a sorted doubly linked list in-place by rearranging the left and right pointers of each node. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode def bin_tree_to_list(root: TreeNode | None) -> TreeNode | None: """Convert a binary tree to a sorted doubly linked list. Args: root: The root of the binary tree. Returns: The head (leftmost node) of the resulting doubly linked list, or None if the tree is empty. Examples: >>> bin_tree_to_list(None) is None True """ if not root: return root root = _bin_tree_to_list_util(root) while root.left: root = root.left return root def _bin_tree_to_list_util(root: TreeNode | None) -> TreeNode | None: """Recursively convert subtree nodes into a doubly linked list. Args: root: The root of the subtree to convert. Returns: The root of the partially converted subtree. """ if not root: return root if root.left: left = _bin_tree_to_list_util(root.left) while left.right: left = left.right left.right = root root.left = left if root.right: right = _bin_tree_to_list_util(root.right) while right.left: right = right.left right.left = root root.right = right return root ================================================ FILE: algorithms/tree/binary_tree_paths.py ================================================ """ Binary Tree Paths Given a binary tree, return all root-to-leaf paths as a list of strings in the format "root->...->leaf". Reference: https://en.wikipedia.org/wiki/Binary_tree#Combinatorics Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from algorithms.tree.tree import TreeNode def binary_tree_paths(root: TreeNode | None) -> list[str]: """Return all root-to-leaf paths in the binary tree. Args: root: The root of the binary tree. Returns: A list of strings representing each root-to-leaf path. Examples: >>> binary_tree_paths(None) [] """ result: list[str] = [] if root is None: return result _dfs(result, root, str(root.val)) return result def _dfs(result: list[str], root: TreeNode, current: str) -> None: """Perform depth-first search to collect all root-to-leaf paths. Args: result: The list accumulating path strings. root: The current node being visited. current: The path string built so far. """ if root.left is None and root.right is None: result.append(current) if root.left: _dfs(result, root.left, current + "->" + str(root.left.val)) if root.right: _dfs(result, root.right, current + "->" + str(root.right.val)) ================================================ FILE: algorithms/tree/binary_tree_views.py ================================================ """ Binary Tree Views Compute different "views" of a binary tree — the nodes visible when looking at the tree from a particular direction. - **Left view**: first node at each level (leftmost). - **Right view**: last node at each level (rightmost). - **Top view**: nodes visible when looking from above. - **Bottom view**: nodes visible when looking from below (last node at each horizontal distance). Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity (all views): Time: O(n) — each node is visited once Space: O(n) — queue / dict storage """ from __future__ import annotations from collections import deque from algorithms.common.tree_node import TreeNode def left_view(root: TreeNode | None) -> list[int]: """Return the values visible from the left side of the tree. Args: root: Root of the binary tree. Returns: List of node values, one per level, from the left. Examples: >>> from algorithms.common.tree_node import TreeNode >>> root = TreeNode(1, TreeNode(2, TreeNode(4)), TreeNode(3)) >>> left_view(root) [1, 2, 4] """ if root is None: return [] result: list[int] = [] queue: deque[TreeNode] = deque([root]) while queue: level_size = len(queue) for i in range(level_size): node = queue.popleft() if i == 0: result.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) return result def right_view(root: TreeNode | None) -> list[int]: """Return the values visible from the right side of the tree. Args: root: Root of the binary tree. Returns: List of node values, one per level, from the right. Examples: >>> from algorithms.common.tree_node import TreeNode >>> root = TreeNode(1, TreeNode(2, TreeNode(4)), TreeNode(3)) >>> right_view(root) [1, 3, 4] """ if root is None: return [] result: list[int] = [] queue: deque[TreeNode] = deque([root]) while queue: level_size = len(queue) for i in range(level_size): node = queue.popleft() if i == level_size - 1: result.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) return result def top_view(root: TreeNode | None) -> list[int]: """Return the values visible when looking at the tree from above. Nodes are ordered by horizontal distance from root (left to right). Args: root: Root of the binary tree. Returns: List of node values visible from the top. Examples: >>> from algorithms.common.tree_node import TreeNode >>> root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), ... TreeNode(3, None, TreeNode(6))) >>> top_view(root) [4, 2, 1, 3, 6] """ if root is None: return [] # Map: horizontal distance -> first node value seen (BFS ensures top-most) hd_map: dict[int, int] = {} queue: deque[tuple[TreeNode, int]] = deque([(root, 0)]) while queue: node, hd = queue.popleft() if hd not in hd_map: hd_map[hd] = node.val if node.left: queue.append((node.left, hd - 1)) if node.right: queue.append((node.right, hd + 1)) return [hd_map[k] for k in sorted(hd_map)] def bottom_view(root: TreeNode | None) -> list[int]: """Return the values visible when looking at the tree from below. Nodes are ordered by horizontal distance from root (left to right). When multiple nodes share the same horizontal distance, the last one encountered in level-order (bottommost) wins. Args: root: Root of the binary tree. Returns: List of node values visible from the bottom. Examples: >>> from algorithms.common.tree_node import TreeNode >>> root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), ... TreeNode(3, None, TreeNode(6))) >>> bottom_view(root) [4, 2, 5, 3, 6] """ if root is None: return [] hd_map: dict[int, int] = {} queue: deque[tuple[TreeNode, int]] = deque([(root, 0)]) while queue: node, hd = queue.popleft() hd_map[hd] = node.val # overwrite → keeps bottommost if node.left: queue.append((node.left, hd - 1)) if node.right: queue.append((node.right, hd + 1)) return [hd_map[k] for k in sorted(hd_map)] ================================================ FILE: algorithms/tree/bst_array_to_bst.py ================================================ """ Given an array where elements are sorted in ascending order, convert it to a height balanced BST. """ from algorithms.common.tree_node import TreeNode def array_to_bst(nums): if not nums: return None mid = len(nums) // 2 node = TreeNode(nums[mid]) node.left = array_to_bst(nums[:mid]) node.right = array_to_bst(nums[mid + 1 :]) return node ================================================ FILE: algorithms/tree/bst_closest_value.py ================================================ # Given a non-empty binary search tree and a target value, # find the value in the BST that is closest to the target. # Note: # Given target value is a floating point. # You are guaranteed to have only one unique value in the BST # that is closest to the target. # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val = x # self.left = None # self.right = None def closest_value(root, target): """ :type root: TreeNode :type target: float :rtype: int """ a = root.val kid = root.left if target < a else root.right if not kid: return a b = closest_value(kid, target) return min((a, b), key=lambda x: abs(target - x)) ================================================ FILE: algorithms/tree/bst_count_left_node.py ================================================ """ Write a function count_left_node returns the number of left children in the tree. For example: the following tree has four left children (the nodes storing the values 6, 3, 7, and 10): 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 count_left_node = 4 """ import unittest from bst import bst def count_left_node(root): if root is None: return 0 elif root.left is None: return count_left_node(root.right) else: return 1 + count_left_node(root.left) + count_left_node(root.right) """ The tree is created for testing: 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 count_left_node = 4 """ class TestSuite(unittest.TestCase): def setUp(self): self.tree = bst() self.tree.insert(9) self.tree.insert(6) self.tree.insert(12) self.tree.insert(3) self.tree.insert(8) self.tree.insert(10) self.tree.insert(15) self.tree.insert(7) self.tree.insert(18) def test_count_left_node(self): self.assertEqual(4, count_left_node(self.tree.root)) if __name__ == "__main__": unittest.main() ================================================ FILE: algorithms/tree/bst_delete_node.py ================================================ """ Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST. Basically, the deletion can be divided into two stages: Search for a node to remove. If the node is found, delete the node. Note: Time complexity should be O(height of tree). Example: root = [5,3,6,2,4,null,7] key = 3 5 / \ 3 6 / \\ \ 2 4 7 Given key to delete is 3. So we find the node with value 3 and delete it. One valid answer is [5,4,6,2,null,null,7], shown in the following BST. 5 / \ 4 6 / \ 2 7 Another valid answer is [5,2,6,null,4,null,7]. 5 / \ 2 6 \\ \ 4 7 """ class Solution: def delete_node(self, root, key): """ :type root: TreeNode :type key: int :rtype: TreeNode """ if not root: return None if root.val == key: if root.left: # Find the right most leaf of the left sub-tree left_right_most = root.left while left_right_most.right: left_right_most = left_right_most.right # Attach right child to the right of that leaf left_right_most.right = root.right # Return left child instead of root, a.k.a delete root return root.left else: return root.right # If left or right child got deleted, the returned root is # the child of the deleted node. elif root.val > key: root.left = self.deleteNode(root.left, key) else: root.right = self.deleteNode(root.right, key) return root ================================================ FILE: algorithms/tree/bst_depth_sum.py ================================================ """ Write a function depthSum returns the sum of the values stored in a binary search tree of integers weighted by the depth of each value. For example: 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18) """ import unittest from bst import bst def depth_sum(root, n): if root: return recur_depth_sum(root, 1) def recur_depth_sum(root, n): if root is None: return 0 elif root.left is None and root.right is None: return root.data * n else: return ( n * root.data + recur_depth_sum(root.left, n + 1) + recur_depth_sum(root.right, n + 1) ) """ The tree is created for testing: 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18) """ class TestSuite(unittest.TestCase): def setUp(self): self.tree = bst() self.tree.insert(9) self.tree.insert(6) self.tree.insert(12) self.tree.insert(3) self.tree.insert(8) self.tree.insert(10) self.tree.insert(15) self.tree.insert(7) self.tree.insert(18) def test_depth_sum(self): self.assertEqual(253, depth_sum(self.tree.root, 4)) if __name__ == "__main__": unittest.main() ================================================ FILE: algorithms/tree/bst_height.py ================================================ """ Write a function height returns the height of a tree. The height is defined to be the number of levels. The empty tree has height 0, a tree of one node has height 1, a root node with one or two leaves as children has height 2, and so on For example: height of tree is 4 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 height = 4 """ import unittest from bst import bst def height(root): if root is None: return 0 else: return 1 + max(height(root.left), height(root.right)) """ The tree is created for testing: 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 count_left_node = 4 """ class TestSuite(unittest.TestCase): def setUp(self): self.tree = bst() self.tree.insert(9) self.tree.insert(6) self.tree.insert(12) self.tree.insert(3) self.tree.insert(8) self.tree.insert(10) self.tree.insert(15) self.tree.insert(7) self.tree.insert(18) def test_height(self): self.assertEqual(4, height(self.tree.root)) if __name__ == "__main__": unittest.main() ================================================ FILE: algorithms/tree/bst_is_bst.py ================================================ """ Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is defined as follows: The left subtree of a node contains only nodes with keys less than the node's key. The right subtree of a node contains only nodes with keys greater than the node's key. Both the left and right subtrees must also be binary search trees. Example 1: 2 / \ 1 3 Binary tree [2,1,3], return true. Example 2: 1 / \ 2 3 Binary tree [1,2,3], return false. """ def is_bst(root): """ :type root: TreeNode :rtype: bool """ stack = [] pre = None while root or stack: while root: stack.append(root) root = root.left root = stack.pop() if pre and root.val <= pre.val: return False pre = root root = root.right return True ================================================ FILE: algorithms/tree/bst_iterator.py ================================================ class BSTIterator: def __init__(self, root): self.stack = [] while root: self.stack.append(root) root = root.left def has_next(self): return bool(self.stack) def next(self): node = self.stack.pop() tmp = node if tmp.right: tmp = tmp.right while tmp: self.stack.append(tmp) tmp = tmp.left return node.val ================================================ FILE: algorithms/tree/bst_kth_smallest.py ================================================ class Node: def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right def kth_smallest(root, k): stack = [] while root or stack: while root: stack.append(root) root = root.left root = stack.pop() k -= 1 if k == 0: break root = root.right return root.val class Solution: def kth_smallest(self, root, k): """ :type root: TreeNode :type k: int :rtype: int """ count = [] self.helper(root, count) return count[k - 1] def helper(self, node, count): if not node: return self.helper(node.left, count) count.append(node.val) self.helper(node.right, count) if __name__ == "__main__": n1 = Node(100) n2 = Node(50) n3 = Node(150) n4 = Node(25) n5 = Node(75) n6 = Node(125) n7 = Node(175) n1.left, n1.right = n2, n3 n2.left, n2.right = n4, n5 n3.left, n3.right = n6, n7 print(kth_smallest(n1, 2)) print(Solution().kth_smallest(n1, 2)) ================================================ FILE: algorithms/tree/bst_lowest_common_ancestor.py ================================================ """ Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).” _______6______ / \ ___2__ ___8__ / \\ / \ 0 _4 7 9 / \ 3 5 For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. """ def lowest_common_ancestor(root, p, q): """ :type root: Node :type p: Node :type q: Node :rtype: Node """ while root: if p.val > root.val < q.val: root = root.right elif p.val < root.val > q.val: root = root.left else: return root ================================================ FILE: algorithms/tree/bst_num_empty.py ================================================ """ Write a function num_empty returns returns the number of empty branches in a tree. Function should count the total number of empty branches among the nodes of the tree. A leaf node has two empty branches. In the case, if root is None, it considered as a 1 empty branch For example: the following tree has 10 empty branch (* is empty branch) 9 __ / \\___ 6 12 / \\ / \ 3 8 10 15 / \\ / \\ / \\ / \ * * 7 * * * * 18 / \\ / \ * * * * empty_branch = 10 """ import unittest from bst import bst def num_empty(root): if root is None: return 1 elif root.left is None and root.right: return 1 + num_empty(root.right) elif root.right is None and root.left: return 1 + num_empty(root.left) else: return num_empty(root.left) + num_empty(root.right) """ The tree is created for testing: 9 / \ 6 12 / \\ / \ 3 8 10 15 / \ 7 18 num_empty = 10 """ class TestSuite(unittest.TestCase): def setUp(self): self.tree = bst() self.tree.insert(9) self.tree.insert(6) self.tree.insert(12) self.tree.insert(3) self.tree.insert(8) self.tree.insert(10) self.tree.insert(15) self.tree.insert(7) self.tree.insert(18) def test_num_empty(self): self.assertEqual(10, num_empty(self.tree.root)) if __name__ == "__main__": unittest.main() ================================================ FILE: algorithms/tree/bst_predecessor.py ================================================ def predecessor(root, node): pred = None while root: if node.val > root.val: pred = root root = root.right else: root = root.left return pred ================================================ FILE: algorithms/tree/bst_serialize_deserialize.py ================================================ from algorithms.common.tree_node import TreeNode def serialize(root): def build_string(node): if node: vals.append(str(node.val)) build_string(node.left) build_string(node.right) else: vals.append("#") vals = [] build_string(root) return " ".join(vals) def deserialize(data): def build_tree(): val = next(vals) if val == "#": return None node = TreeNode(int(val)) node.left = build_tree() node.right = build_tree() return node vals = iter(data.split()) return build_tree() ================================================ FILE: algorithms/tree/bst_successor.py ================================================ def successor(root, node): succ = None while root: if node.val < root.val: succ = root root = root.left else: root = root.right return succ ================================================ FILE: algorithms/tree/bst_unique_bst.py ================================================ """ Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For example, Given n = 3, there are a total of 5 unique BST's. 1 3 3 2 1 \\ / / / \\ \ 3 2 1 1 3 2 / / \\ \ 2 1 2 3 """ """ Taking 1~n as root respectively: 1 as root: # of trees = F(0) * F(n-1) // F(0) == 1 2 as root: # of trees = F(1) * F(n-2) 3 as root: # of trees = F(2) * F(n-3) ... n-1 as root: # of trees = F(n-2) * F(1) n as root: # of trees = F(n-1) * F(0) So, the formulation is: F(n) = F(0) * F(n-1) + F(1) * F(n-2) + F(2) * F(n-3) + ... + F(n-2) * F(1) + F(n-1) * F(0) """ def num_trees(n): """ :type n: int :rtype: int """ dp = [0] * (n + 1) dp[0] = 1 dp[1] = 1 for i in range(2, n + 1): for j in range(i + 1): dp[i] += dp[i - j] * dp[j - 1] return dp[-1] ================================================ FILE: algorithms/tree/bst_validate_bst.py ================================================ # =============================================================================== # Validate Binary Search Tree """ To check if the given binary tree is a valid binary search tree (BST), we need to ensure that: 1. The left subtree of a node contains only nodes with keys less than the node's key. 2. The right subtree of a node contains only nodes with keys greater than the node's key. 3. Both the left and right subtrees must also be binary search trees. """ # =============================================================================== from algorithms.common.tree_node import TreeNode # Function to validate if a binary tree is a BST def validate_bst(node): """ Validate if a binary tree is a binary search tree (BST). Input params : Tree Node to be validated Returns : Tuple ( is_bst: bool, min_value: int | None, max_value: int | None ) """ # Base case: An empty tree is a valid BST if not node: return (True, None, None) # Validate the left and right subtrees valid_left, minn_left, maxx_left = validate_bst(node.left) valid_right, minn_right, maxx_right = validate_bst(node.right) # If either subtree is not valid, the whole tree is not a valid BST if not valid_left or not valid_right: return ( False, minn_left if minn_left is not None else node.val, maxx_right if maxx_right is not None else node.val, ) # Check the current node's value against the max of the left subtree if maxx_left is not None and maxx_left > node.val: return ( False, minn_left if minn_left is not None else node.val, maxx_right if maxx_right is not None else node.val, ) # Check the current node's value against the min of the right subtree if minn_right is not None and minn_right < node.val: return ( False, minn_left if minn_left is not None else node.val, maxx_right if maxx_right is not None else node.val, ) # If all checks pass, the tree/subtree is a valid BST return ( True, minn_left if minn_left is not None else node.val, maxx_right if maxx_right is not None else node.val, ) # Example usage if __name__ == "__main__": # Constructing a simple binary tree root = TreeNode(10) root.left = TreeNode(5) root.right = TreeNode(15) root.right.left = TreeNode(12) root.right.right = TreeNode(20) """ 10 / \ 5 15 / \ 12 20 """ # Validate if the constructed tree is a BST is_bst, _, _ = validate_bst(root) print(f"The tree is a valid BST: {is_bst}") ================================================ FILE: algorithms/tree/construct_tree_postorder_preorder.py ================================================ """ Construct Tree from Preorder and Postorder Traversal Given preorder and postorder traversals of a full binary tree, construct the tree and return its inorder traversal. A full binary tree has either zero or two children per node. Reference: https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees Complexity: Time: O(n^2) due to linear search in postorder array Space: O(n) for the constructed tree """ from __future__ import annotations from algorithms.common.tree_node import TreeNode pre_index = 0 def construct_tree_util( pre: list[int], post: list[int], low: int, high: int, size: int ) -> TreeNode | None: """Recursively construct a binary tree from preorder and postorder arrays. Uses a global pre_index to track the current position in the preorder array during recursive construction. Args: pre: The preorder traversal array. post: The postorder traversal array. low: The lower bound index in the postorder array. high: The upper bound index in the postorder array. size: The total number of elements. Returns: The root of the constructed subtree, or None if bounds are invalid. Examples: >>> construct_tree_util([1, 2, 3], [2, 3, 1], 0, 2, 3) is not None True """ global pre_index if pre_index == -1: pre_index = 0 if pre_index >= size or low > high: return None root = TreeNode(pre[pre_index]) pre_index += 1 if low == high or pre_index >= size: return root i = low while i <= high: if pre[pre_index] == post[i]: break i += 1 if i <= high: root.left = construct_tree_util(pre, post, low, i, size) root.right = construct_tree_util(pre, post, i + 1, high, size) return root def construct_tree(pre: list[int], post: list[int], size: int) -> list[int]: """Construct a full binary tree and return its inorder traversal. Args: pre: The preorder traversal array. post: The postorder traversal array. size: The number of elements. Returns: A list of values representing the inorder traversal of the constructed tree. Examples: >>> construct_tree([1, 2, 4, 5, 3, 6, 7], [4, 5, 2, 6, 7, 3, 1], 7) [4, 2, 5, 1, 6, 3, 7] """ root = construct_tree_util(pre, post, 0, size - 1, size) return _inorder(root) def _inorder(root: TreeNode | None, result: list[int] | None = None) -> list[int]: """Return the inorder traversal of a binary tree. Args: root: The root of the tree to traverse. result: Accumulator list for the traversal values. Returns: A list of node values in inorder sequence. """ if root is None: return [] if result is None: result = [] _inorder(root.left, result) result.append(root.val) _inorder(root.right, result) return result ================================================ FILE: algorithms/tree/deepest_left.py ================================================ """ Deepest Left Leaf Given a binary tree, find the deepest node that is the left child of its parent node. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode class DeepestLeft: """Container to track the deepest left node found during traversal. Examples: >>> dl = DeepestLeft() >>> dl.depth 0 """ def __init__(self) -> None: self.depth: int = 0 self.Node: TreeNode | None = None def find_deepest_left( root: TreeNode | None, is_left: bool, depth: int, res: DeepestLeft ) -> None: """Recursively find the deepest left child in a binary tree. Args: root: The current node being examined. is_left: Whether the current node is a left child. depth: The current depth in the tree. res: A DeepestLeft instance tracking the best result so far. Examples: >>> res = DeepestLeft() >>> find_deepest_left(None, True, 1, res) """ if not root: return if is_left and depth > res.depth: res.depth = depth res.Node = root find_deepest_left(root.left, True, depth + 1, res) find_deepest_left(root.right, False, depth + 1, res) ================================================ FILE: algorithms/tree/invert_tree.py ================================================ """ Invert Binary Tree Inverts a binary tree by swapping the left and right children of every node. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode def reverse(root: TreeNode | None) -> None: """Invert a binary tree in-place by swapping left and right children. Args: root: The root of the binary tree to invert. Examples: >>> reverse(None) """ if root is None: return root.left, root.right = root.right, root.left if root.left: reverse(root.left) if root.right: reverse(root.right) ================================================ FILE: algorithms/tree/is_balanced.py ================================================ """ Balanced Binary Tree Determines whether a binary tree is height-balanced, meaning the depth of the left and right subtrees of every node differ by at most one. Reference: https://en.wikipedia.org/wiki/AVL_tree Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode def is_balanced(root: TreeNode | None) -> bool: """Check whether a binary tree is height-balanced. Args: root: The root of the binary tree. Returns: True if the tree is balanced, False otherwise. Examples: >>> is_balanced(None) True """ return _get_depth(root) != -1 def _get_depth(root: TreeNode | None) -> int: """Compute the depth of a tree, returning -1 if unbalanced. Args: root: The root of the subtree. Returns: The depth of the subtree, or -1 if it is unbalanced. """ if root is None: return 0 left = _get_depth(root.left) right = _get_depth(root.right) if abs(left - right) > 1 or -1 in [left, right]: return -1 return 1 + max(left, right) ================================================ FILE: algorithms/tree/is_subtree.py ================================================ """ Subtree Check Given two binary trees s and t, check whether t is a subtree of s. A subtree of s is a tree consisting of a node in s and all of its descendants. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(m * n) where m and n are sizes of the two trees Space: O(m) due to BFS queue """ from __future__ import annotations import collections from algorithms.tree.tree import TreeNode def is_subtree(big: TreeNode, small: TreeNode) -> bool: """Check whether the small tree is a subtree of the big tree. Args: big: The root of the larger tree. small: The root of the potential subtree. Returns: True if small is a subtree of big, False otherwise. Examples: >>> node = TreeNode(1) >>> is_subtree(node, node) True """ flag = False queue: collections.deque[TreeNode] = collections.deque() queue.append(big) while queue: node = queue.popleft() if node.val == small.val: flag = _comp(node, small) break else: queue.append(node.left) queue.append(node.right) return flag def _comp(p: TreeNode | None, q: TreeNode | None) -> bool: """Recursively compare two trees for structural and value equality. Args: p: A node from the first tree. q: A node from the second tree. Returns: True if both subtrees are identical, False otherwise. """ if p is None and q is None: return True if p is not None and q is not None: return p.val == q.val and _comp(p.left, q.left) and _comp(p.right, q.right) return False ================================================ FILE: algorithms/tree/is_symmetric.py ================================================ """ Symmetric Tree Given a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center). Provides both recursive and iterative solutions. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from algorithms.tree.tree import TreeNode def is_symmetric(root: TreeNode | None) -> bool: """Check whether a binary tree is symmetric using recursion. Args: root: The root of the binary tree. Returns: True if the tree is symmetric, False otherwise. Examples: >>> is_symmetric(None) True """ if root is None: return True return _helper(root.left, root.right) def _helper(p: TreeNode | None, q: TreeNode | None) -> bool: """Recursively check whether two subtrees are mirrors of each other. Args: p: The root of the left subtree. q: The root of the right subtree. Returns: True if the subtrees are mirror images, False otherwise. """ if p is None and q is None: return True if p is not None or q is not None or q.val != p.val: return False return _helper(p.left, q.right) and _helper(p.right, q.left) def is_symmetric_iterative(root: TreeNode | None) -> bool: """Check whether a binary tree is symmetric using iteration. Args: root: The root of the binary tree. Returns: True if the tree is symmetric, False otherwise. Examples: >>> is_symmetric_iterative(None) True """ if root is None: return True stack: list[list[TreeNode | None]] = [[root.left, root.right]] while stack: left, right = stack.pop() if left is None and right is None: continue if left is None or right is None: return False if left.val == right.val: stack.append([left.left, right.right]) stack.append([left.right, right.left]) else: return False return True ================================================ FILE: algorithms/tree/longest_consecutive.py ================================================ """ Longest Consecutive Sequence in Binary Tree Given a binary tree, find the length of the longest consecutive sequence path from parent to child (values increasing by one). Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode def longest_consecutive(root: TreeNode | None) -> int: """Find the length of the longest parent-to-child consecutive sequence. Args: root: The root of the binary tree. Returns: The length of the longest consecutive sequence path. Examples: >>> longest_consecutive(None) 0 """ if root is None: return 0 max_len = 0 _dfs(root, 0, root.val, max_len) return max_len def _dfs(root: TreeNode | None, current: int, target: int, max_len: int) -> None: """Recursively search for the longest consecutive sequence. Args: root: The current node being visited. current: The current consecutive sequence length. target: The expected value for the current node to continue the sequence. max_len: The maximum sequence length found so far. """ if root is None: return if root.val == target: current += 1 else: current = 1 max_len = max(current, max_len) _dfs(root.left, current, root.val + 1, max_len) _dfs(root.right, current, root.val + 1, max_len) ================================================ FILE: algorithms/tree/lowest_common_ancestor.py ================================================ """ Lowest Common Ancestor Given a binary tree, find the lowest common ancestor (LCA) of two given nodes. The LCA is the lowest node that has both nodes as descendants (a node can be a descendant of itself). Reference: https://en.wikipedia.org/wiki/Lowest_common_ancestor Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode def lca(root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None: """Find the lowest common ancestor of two nodes in a binary tree. Args: root: The root of the binary tree. p: The first target node. q: The second target node. Returns: The lowest common ancestor node, or None if not found. Examples: >>> node = TreeNode(1) >>> lca(node, node, node).val 1 """ if root is None or root is p or root is q: return root left = lca(root.left, p, q) right = lca(root.right, p, q) if left is not None and right is not None: return root return left if left else right ================================================ FILE: algorithms/tree/max_height.py ================================================ """ Maximum Depth of Binary Tree Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the longest path from the root down to the farthest leaf. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from collections import deque from algorithms.tree.tree import TreeNode def max_height(root: TreeNode | None) -> int: """Compute the maximum depth of a binary tree using iterative BFS. Args: root: The root of the binary tree. Returns: The maximum depth (number of levels) of the tree. Examples: >>> max_height(None) 0 """ if root is None: return 0 height = 0 queue: deque[TreeNode] = deque([root]) while queue: height += 1 level: deque[TreeNode] = deque() while queue: node = queue.popleft() if node.left is not None: level.append(node.left) if node.right is not None: level.append(node.right) queue = level return height ================================================ FILE: algorithms/tree/max_path_sum.py ================================================ """ Binary Tree Maximum Path Sum Given a binary tree, find the maximum path sum. A path is any sequence of nodes from some starting node to any node in the tree along parent-child connections. The path must contain at least one node. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) due to recursion stack """ from __future__ import annotations from algorithms.tree.tree import TreeNode def max_path_sum(root: TreeNode | None) -> float: """Find the maximum path sum in a binary tree. Args: root: The root of the binary tree. Returns: The maximum sum of any path through the tree. Examples: >>> max_path_sum(TreeNode(1)) 1 """ maximum = float("-inf") _helper(root, maximum) return maximum def _helper(root: TreeNode | None, maximum: float) -> float: """Recursively compute the maximum single-branch sum from each node. Args: root: The current node. maximum: The running maximum path sum. Returns: The maximum sum of a path extending from this node to a descendant. """ if root is None: return 0 left = _helper(root.left, maximum) right = _helper(root.right, maximum) maximum = max(maximum, left + right + root.val) return root.val + maximum ================================================ FILE: algorithms/tree/min_height.py ================================================ """ Minimum Depth of Binary Tree Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root down to the nearest leaf. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from algorithms.tree.tree import TreeNode def min_depth(self: object, root: TreeNode | None) -> int: """Compute the minimum depth of a binary tree using recursion. Args: self: Unused parameter (kept for backward compatibility). root: The root of the binary tree. Returns: The minimum depth of the tree. """ if root is None: return 0 if root.left is not None or root.right is not None: return max(self.minDepth(root.left), self.minDepth(root.right)) + 1 return min(self.minDepth(root.left), self.minDepth(root.right)) + 1 def min_height(root: TreeNode | None) -> int: """Compute the minimum depth of a binary tree using iterative BFS. Args: root: The root of the binary tree. Returns: The minimum depth (number of levels to nearest leaf) of the tree. Examples: >>> min_height(None) 0 """ if root is None: return 0 height = 0 level: list[TreeNode] = [root] while level: height += 1 new_level: list[TreeNode] = [] for node in level: if node.left is None and node.right is None: return height if node.left is not None: new_level.append(node.left) if node.right is not None: new_level.append(node.right) level = new_level return height ================================================ FILE: algorithms/tree/path_sum.py ================================================ """ Path Sum Given a binary tree and a target sum, determine if the tree has a root-to-leaf path such that adding up all values along the path equals the given sum. Provides recursive, DFS, and BFS solutions. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from collections import deque from algorithms.tree.tree import TreeNode def has_path_sum(root: TreeNode | None, sum: int) -> bool: """Check if a root-to-leaf path with the given sum exists (recursive). Args: root: The root of the binary tree. sum: The target sum. Returns: True if such a path exists, False otherwise. Examples: >>> has_path_sum(None, 0) False """ if root is None: return False if root.left is None and root.right is None and root.val == sum: return True sum -= root.val return has_path_sum(root.left, sum) or has_path_sum(root.right, sum) def has_path_sum2(root: TreeNode | None, sum: int) -> bool: """Check if a root-to-leaf path with the given sum exists (DFS with stack). Args: root: The root of the binary tree. sum: The target sum. Returns: True if such a path exists, False otherwise. Examples: >>> has_path_sum2(None, 0) False """ if root is None: return False stack: list[tuple[TreeNode, int]] = [(root, root.val)] while stack: node, val = stack.pop() if node.left is None and node.right is None and val == sum: return True if node.left is not None: stack.append((node.left, val + node.left.val)) if node.right is not None: stack.append((node.right, val + node.right.val)) return False def has_path_sum3(root: TreeNode | None, sum: int) -> bool: """Check if a root-to-leaf path with the given sum exists (BFS with queue). Args: root: The root of the binary tree. sum: The target sum. Returns: True if such a path exists, False otherwise. Examples: >>> has_path_sum3(None, 0) False """ if root is None: return False queue: deque[tuple[TreeNode, int]] = deque([(root, sum - root.val)]) while queue: node, val = queue.popleft() if node.left is None and node.right is None and val == 0: return True if node.left is not None: queue.append((node.left, val - node.left.val)) if node.right is not None: queue.append((node.right, val - node.right.val)) return False ================================================ FILE: algorithms/tree/path_sum2.py ================================================ """ Path Sum II Given a binary tree and a target sum, find all root-to-leaf paths where each path's sum equals the given sum. Provides recursive, DFS, and BFS solutions. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(n) Space: O(n) """ from __future__ import annotations from collections import deque from algorithms.tree.tree import TreeNode def path_sum(root: TreeNode | None, sum: int) -> list[list[int]]: """Find all root-to-leaf paths with the given sum (recursive DFS). Args: root: The root of the binary tree. sum: The target sum. Returns: A list of paths, where each path is a list of node values. Examples: >>> path_sum(None, 0) [] """ if root is None: return [] result: list[list[int]] = [] _dfs(root, sum, [], result) return result def _dfs(root: TreeNode, sum: int, path: list[int], result: list[list[int]]) -> None: """Recursively collect paths that sum to the target value. Args: root: The current node. sum: The remaining target sum. path: The path accumulated so far. result: The list accumulating valid paths. """ if root.left is None and root.right is None and root.val == sum: path.append(root.val) result.append(path) if root.left is not None: _dfs(root.left, sum - root.val, path + [root.val], result) if root.right is not None: _dfs(root.right, sum - root.val, path + [root.val], result) def path_sum2(root: TreeNode | None, target: int) -> list[list[int]]: """Find all root-to-leaf paths with the given sum (DFS with stack). Args: root: The root of the binary tree. target: The target sum. Returns: A list of paths, where each path is a list of node values. Examples: >>> path_sum2(None, 0) [] """ if root is None: return [] result: list[list[int]] = [] stack: list[tuple[TreeNode, list[int]]] = [(root, [root.val])] while stack: node, path = stack.pop() if node.left is None and node.right is None and sum(path) == target: result.append(path) if node.left is not None: stack.append((node.left, path + [node.left.val])) if node.right is not None: stack.append((node.right, path + [node.right.val])) return result def path_sum3(root: TreeNode | None, sum: int) -> list[list[int]]: """Find all root-to-leaf paths with the given sum (BFS with queue). Args: root: The root of the binary tree. sum: The target sum. Returns: A list of paths, where each path is a list of node values. Examples: >>> path_sum3(None, 0) [] """ if root is None: return [] result: list[list[int]] = [] initial = (root, root.val, [root.val]) queue: deque[tuple[TreeNode, int, list[int]]] = deque([initial]) while queue: node, val, path = queue.popleft() if node.left is None and node.right is None and val == sum: result.append(path) if node.left is not None: queue.append((node.left, val + node.left.val, path + [node.left.val])) if node.right is not None: queue.append((node.right, val + node.right.val, path + [node.right.val])) return result ================================================ FILE: algorithms/tree/pretty_print.py ================================================ """ Pretty Print Tree Prints a dictionary-based tree structure in a human-readable format showing keys and their nested elements. Reference: https://en.wikipedia.org/wiki/Tree_(data_structure) Complexity: Time: O(n) where n is total number of elements Space: O(1) additional space """ from __future__ import annotations def tree_print(tree: dict) -> list[str]: """Format a dictionary tree as a list of readable strings. Each top-level key maps to a list of sub-elements. The output represents each key followed by its sub-elements joined with arrows. Args: tree: A dictionary mapping keys to lists of sub-elements. Returns: A list of formatted strings representing each tree branch. Examples: >>> tree_print({"a": ["Adam", "Book", 4]}) ['a -> Adam -> Book -> 4'] """ lines: list[str] = [] for key in tree: parts = [str(key)] tree_element = tree[key] for sub_elem in tree_element: parts.append(str(sub_elem)) lines.append(" -> ".join(parts)) return lines ================================================ FILE: algorithms/tree/same_tree.py ================================================ """ Same Tree Given two binary trees, check if they are structurally identical and the nodes have the same values. Reference: https://en.wikipedia.org/wiki/Binary_tree Complexity: Time: O(min(n, m)) where n and m are sizes of the trees Space: O(min(h1, h2)) where h1 and h2 are the heights """ from __future__ import annotations from algorithms.tree.tree import TreeNode def is_same_tree(tree_p: TreeNode | None, tree_q: TreeNode | None) -> bool: """Check whether two binary trees are identical. Args: tree_p: The root of the first binary tree. tree_q: The root of the second binary tree. Returns: True if both trees are structurally identical with equal node values, False otherwise. Examples: >>> is_same_tree(None, None) True """ if tree_p is None and tree_q is None: return True if tree_p is not None and tree_q is not None and tree_p.val == tree_q.val: return is_same_tree(tree_p.left, tree_q.left) and is_same_tree( tree_p.right, tree_q.right ) return False ================================================ FILE: algorithms/tree/traversal_inorder.py ================================================ """ Time complexity : O(n) """ class Node: def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right def inorder(root): """In order function""" res = [] if not root: return res stack = [] while root or stack: while root: stack.append(root) root = root.left root = stack.pop() res.append(root.val) root = root.right return res def inorder_rec(root, res=None): """Recursive Implementation""" if root is None: return [] if res is None: res = [] inorder_rec(root.left, res) res.append(root.val) inorder_rec(root.right, res) return res if __name__ == "__main__": n1 = Node(100) n2 = Node(50) n3 = Node(150) n4 = Node(25) n5 = Node(75) n6 = Node(125) n7 = Node(175) n1.left, n1.right = n2, n3 n2.left, n2.right = n4, n5 n3.left, n3.right = n6, n7 assert inorder(n1) == [25, 50, 75, 100, 125, 150, 175] assert inorder_rec(n1) == [25, 50, 75, 100, 125, 150, 175] ================================================ FILE: algorithms/tree/traversal_level_order.py ================================================ """ Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level). For example: Given binary tree [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 return its level order traversal as: [ [3], [9,20], [15,7] ] """ def level_order(root): ans = [] if not root: return ans level = [root] while level: current = [] new_level = [] for node in level: current.append(node.val) if node.left: new_level.append(node.left) if node.right: new_level.append(node.right) level = new_level ans.append(current) return ans ================================================ FILE: algorithms/tree/traversal_postorder.py ================================================ """ Time complexity : O(n) """ class Node: def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right def postorder(root): res_temp = [] res = [] if not root: return res stack = [] stack.append(root) while stack: root = stack.pop() res_temp.append(root.val) if root.left: stack.append(root.left) if root.right: stack.append(root.right) while res_temp: res.append(res_temp.pop()) return res # Recursive Implementation def postorder_rec(root, res=None): if root is None: return [] if res is None: res = [] postorder_rec(root.left, res) postorder_rec(root.right, res) res.append(root.val) return res ================================================ FILE: algorithms/tree/traversal_preorder.py ================================================ """ Time complexity : O(n) """ class Node: """This is a class of Node""" def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right def preorder(root): """Function to Preorder""" res = [] if not root: return res stack = [] stack.append(root) while stack: root = stack.pop() res.append(root.val) if root.right: stack.append(root.right) if root.left: stack.append(root.left) return res def preorder_rec(root, res=None): """Recursive Implementation""" if root is None: return [] if res is None: res = [] res.append(root.val) preorder_rec(root.left, res) preorder_rec(root.right, res) return res ================================================ FILE: algorithms/tree/traversal_zigzag.py ================================================ """ Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between). For example: Given binary tree [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 return its zigzag level order traversal as: [ [3], [20,9], [15,7] ] """ def zigzag_level(root): res = [] if not root: return res level = [root] flag = 1 while level: current = [] new_level = [] for node in level: current.append(node.val) if node.left: new_level.append(node.left) if node.right: new_level.append(node.right) level = new_level res.append(current[::flag]) flag *= -1 return res ================================================ FILE: algorithms/tree/tree.py ================================================ """Binary Tree Node (re-export). This module re-exports TreeNode from the canonical location in ``algorithms.common.tree_node`` for backward compatibility. """ from algorithms.common.tree_node import TreeNode __all__ = ["TreeNode"] ================================================ FILE: algorithms/tree/trie_add_and_search.py ================================================ """ We are asked to design an efficient data structure that allows us to add and search for words. The search can be a literal word or regular expression containing “.”, where “.” can be any letter. Example: addWord(“bad”) addWord(“dad”) addWord(“mad”) search(“pad”) -> false search(“bad”) -> true search(“.ad”) -> true search(“b..”) -> true """ import collections class TrieNode: def __init__(self, letter, is_terminal=False): self.children = dict() self.letter = letter self.is_terminal = is_terminal class WordDictionary: def __init__(self): self.root = TrieNode("") def add_word(self, word): cur = self.root for letter in word: if letter not in cur.children: cur.children[letter] = TrieNode(letter) cur = cur.children[letter] cur.is_terminal = True def search(self, word, node=None): cur = node if not cur: cur = self.root for i, letter in enumerate(word): # if dot if letter == ".": if i == len(word) - 1: # if last character return any( child.is_terminal for child in cur.children.itervalues() ) return any( self.search(word[i + 1 :], child) for child in cur.children.itervalues() ) # if letter if letter not in cur.children: return False cur = cur.children[letter] return cur.is_terminal class WordDictionary2: def __init__(self): self.word_dict = collections.defaultdict(list) def add_word(self, word): if word: self.word_dict[len(word)].append(word) def search(self, word): if not word: return False if "." not in word: return word in self.word_dict[len(word)] for v in self.word_dict[len(word)]: # match xx.xx.x with yyyyyyy for i, ch in enumerate(word): if ch != v[i] and ch != ".": break else: return True return False ================================================ FILE: docs/index.html ================================================ Algorithms - Pythonic Data Structures and Algorithms

algorithms v1.0.0

Pythonic Data Structures and Algorithms · GitHub · pip install algorithms
No algorithms found.
================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["setuptools>=68.0"] build-backend = "setuptools.build_meta" [project] name = "algorithms" version = "1.0.1" description = "Pythonic Data Structures and Algorithms" readme = "README.md" license = "MIT" requires-python = ">=3.10" authors = [ { name = "Algorithms Team & Contributors", email = "kwk236@gmail.com" }, ] classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Education", ] [project.urls] Homepage = "https://github.com/keon/algorithms" Repository = "https://github.com/keon/algorithms" [project.optional-dependencies] dev = [ "pytest", "ruff", "mypy", "black", ] [tool.setuptools.packages.find] exclude = ["tests", "tests.*", "tools", "tools.*", "docs", "docs.*"] [tool.pytest.ini_options] testpaths = ["tests"] addopts = ["-v", "--tb=short"] [tool.ruff] line-length = 88 target-version = "py310" [tool.ruff.lint] select = ["E", "W", "F", "I", "N", "UP", "B", "SIM"] [tool.ruff.lint.isort] known-first-party = ["algorithms"] [tool.mypy] python_version = "3.10" warn_return_any = true check_untyped_defs = true disallow_untyped_defs = false [tool.black] line-length = 88 target-version = ["py310"] ================================================ FILE: tests/test_array.py ================================================ import unittest from algorithms.array import ( Interval, delete_nth, delete_nth_naive, flatten, flatten_iter, garage, get_longest_non_repeat_v1, get_longest_non_repeat_v2, josephus, limit, longest_non_repeat_v1, longest_non_repeat_v2, max_ones_index, merge_intervals, missing_ranges, move_zeros, n_sum, plus_one_v1, plus_one_v2, plus_one_v3, remove_duplicates, rotate_v1, rotate_v2, rotate_v3, summarize_ranges, three_sum, top_1, trimmean, two_sum, ) class TestJosephus(unittest.TestCase): def test_josephus(self): a = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] josephus_generator = josephus(a, 3) self.assertEqual(next(josephus_generator), "3") self.assertEqual(next(josephus_generator), "6") self.assertEqual(next(josephus_generator), "9") self.assertEqual(next(josephus_generator), "4") self.assertEqual(next(josephus_generator), "8") self.assertEqual(next(josephus_generator), "5") self.assertEqual(next(josephus_generator), "2") self.assertEqual(next(josephus_generator), "7") self.assertEqual(next(josephus_generator), "1") self.assertRaises(StopIteration, next, josephus_generator) class TestDeleteNth(unittest.TestCase): def test_delete_nth_naive(self): self.assertListEqual( delete_nth_naive([20, 37, 20, 21, 37, 21, 21], n=1), [20, 37, 21] ) self.assertListEqual( delete_nth_naive([1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), [1, 1, 3, 3, 7, 2, 2, 2] ) self.assertListEqual( delete_nth_naive([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=3), [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5], ) self.assertListEqual(delete_nth_naive([], n=5), []) self.assertListEqual( delete_nth_naive([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=0), [] ) def test_delete_nth(self): self.assertListEqual( delete_nth([20, 37, 20, 21, 37, 21, 21], n=1), [20, 37, 21] ) self.assertListEqual( delete_nth([1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), [1, 1, 3, 3, 7, 2, 2, 2] ) self.assertListEqual( delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=3), [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5], ) self.assertListEqual(delete_nth([], n=5), []) self.assertListEqual( delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=0), [] ) class TestFlatten(unittest.TestCase): def test_flatten(self): nested_list = [2, 1, [3, [4, 5], 6], 7, [8]] flattened = flatten(nested_list) self.assertEqual(flattened, [2, 1, 3, 4, 5, 6, 7, 8]) nested_list = [[3, [4, 5], 6], 7, [8]] flattened = flatten(nested_list) self.assertEqual(flattened, [3, 4, 5, 6, 7, 8]) nested_list = [[], [8]] flattened = flatten(nested_list) self.assertEqual(flattened, [8]) def test_flatten_iter(self): nested_list = [2, 1, [3, [4, 5], 6], 7, [8]] flattened = flatten_iter(nested_list) self.assertEqual(next(flattened), 2) self.assertEqual(next(flattened), 1) self.assertEqual(next(flattened), 3) self.assertEqual(next(flattened), 4) self.assertEqual(next(flattened), 5) self.assertEqual(next(flattened), 6) self.assertEqual(next(flattened), 7) self.assertEqual(next(flattened), 8) self.assertRaises(StopIteration, next, flattened) nested_list = [[3, [4, 5], 6], 7, [8]] flattened = flatten_iter(nested_list) self.assertEqual(next(flattened), 3) self.assertEqual(next(flattened), 4) self.assertEqual(next(flattened), 5) self.assertEqual(next(flattened), 6) self.assertEqual(next(flattened), 7) self.assertEqual(next(flattened), 8) self.assertRaises(StopIteration, next, flattened) nested_list = [[], [8]] flattened = flatten_iter(nested_list) self.assertEqual(next(flattened), 8) self.assertRaises(StopIteration, next, flattened) class TestGarage(unittest.TestCase): def test_garage(self): initial = [1, 2, 3, 0, 4] final = [0, 3, 2, 1, 4] steps, seq = garage(initial, final) self.assertEqual(steps, 4) self.assertListEqual( seq, [[0, 2, 3, 1, 4], [2, 0, 3, 1, 4], [2, 3, 0, 1, 4], [0, 3, 2, 1, 4]] ) class TestLongestNonRepeat(unittest.TestCase): def test_longest_non_repeat_v1(self): string = "abcabcbb" self.assertEqual(longest_non_repeat_v1(string), 3) string = "bbbbb" self.assertEqual(longest_non_repeat_v1(string), 1) string = "pwwkew" self.assertEqual(longest_non_repeat_v1(string), 3) string = "dvdf" self.assertEqual(longest_non_repeat_v1(string), 3) string = "asjrgapa" self.assertEqual(longest_non_repeat_v1(string), 6) def test_longest_non_repeat_v2(self): string = "abcabcbb" self.assertEqual(longest_non_repeat_v2(string), 3) string = "bbbbb" self.assertEqual(longest_non_repeat_v2(string), 1) string = "pwwkew" self.assertEqual(longest_non_repeat_v2(string), 3) string = "dvdf" self.assertEqual(longest_non_repeat_v2(string), 3) string = "asjrgapa" self.assertEqual(longest_non_repeat_v2(string), 6) def test_get_longest_non_repeat_v1(self): string = "abcabcbb" self.assertEqual(get_longest_non_repeat_v1(string), (3, "abc")) string = "bbbbb" self.assertEqual(get_longest_non_repeat_v1(string), (1, "b")) string = "pwwkew" self.assertEqual(get_longest_non_repeat_v1(string), (3, "wke")) string = "dvdf" self.assertEqual(get_longest_non_repeat_v1(string), (3, "vdf")) string = "asjrgapa" self.assertEqual(get_longest_non_repeat_v1(string), (6, "sjrgap")) def test_get_longest_non_repeat_v2(self): string = "abcabcbb" self.assertEqual(get_longest_non_repeat_v2(string), (3, "abc")) string = "bbbbb" self.assertEqual(get_longest_non_repeat_v2(string), (1, "b")) string = "pwwkew" self.assertEqual(get_longest_non_repeat_v2(string), (3, "wke")) string = "dvdf" self.assertEqual(get_longest_non_repeat_v2(string), (3, "vdf")) string = "asjrgapa" self.assertEqual(get_longest_non_repeat_v2(string), (6, "sjrgap")) class TestMaxOnesIndex(unittest.TestCase): def test_max_ones_index(self): self.assertEqual(9, max_ones_index([1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1])) self.assertEqual(3, max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1])) self.assertEqual(-1, max_ones_index([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])) class TestMergeInterval(unittest.TestCase): def test_merge(self): interval_list = [[1, 3], [2, 6], [8, 10], [15, 18]] intervals = [Interval(i[0], i[1]) for i in interval_list] merged_intervals = Interval.merge(intervals) self.assertEqual( merged_intervals, [Interval(1, 6), Interval(8, 10), Interval(15, 18)] ) def test_merge_intervals(self): interval_list = [[1, 3], [2, 6], [8, 10], [15, 18]] merged_intervals = merge_intervals(interval_list) self.assertEqual(merged_intervals, [[1, 6], [8, 10], [15, 18]]) class TestMissingRanges(unittest.TestCase): def test_missing_ranges(self): arr = [3, 5, 10, 11, 12, 15, 19] self.assertListEqual( missing_ranges(arr, 0, 20), [(0, 2), (4, 4), (6, 9), (13, 14), (16, 18), (20, 20)], ) self.assertListEqual( missing_ranges(arr, 6, 100), [(6, 9), (13, 14), (16, 18), (20, 100)] ) class TestMoveZeros(unittest.TestCase): def test_move_zeros(self): self.assertListEqual( move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]), [False, 1, 1, 2, 1, 3, "a", 0, 0], ) self.assertListEqual( move_zeros([0, 34, "rahul", [], None, 0, True, 0]), [34, "rahul", [], None, True, 0, 0, 0], ) class TestPlusOne(unittest.TestCase): def test_plus_one_v1(self): self.assertListEqual(plus_one_v1([0]), [1]) self.assertListEqual(plus_one_v1([9]), [1, 0]) self.assertListEqual(plus_one_v1([1, 0, 9]), [1, 1, 0]) self.assertListEqual(plus_one_v1([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) self.assertListEqual(plus_one_v1([9, 9, 9, 9]), [1, 0, 0, 0, 0]) def test_plus_one_v2(self): self.assertListEqual(plus_one_v2([0]), [1]) self.assertListEqual(plus_one_v2([9]), [1, 0]) self.assertListEqual(plus_one_v2([1, 0, 9]), [1, 1, 0]) self.assertListEqual(plus_one_v2([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) self.assertListEqual(plus_one_v2([9, 9, 9, 9]), [1, 0, 0, 0, 0]) def test_plus_one_v3(self): self.assertListEqual(plus_one_v3([0]), [1]) self.assertListEqual(plus_one_v3([9]), [1, 0]) self.assertListEqual(plus_one_v3([1, 0, 9]), [1, 1, 0]) self.assertListEqual(plus_one_v3([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) self.assertListEqual(plus_one_v3([9, 9, 9, 9]), [1, 0, 0, 0, 0]) class TestRemoveDuplicate(unittest.TestCase): def test_remove_duplicates(self): self.assertListEqual( remove_duplicates( [1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 7, 7, 7, 8, 8, 9, 10, 10] ), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ) self.assertListEqual( remove_duplicates(["hey", "hello", "hello", "car", "house", "house"]), ["hey", "hello", "car", "house"], ) self.assertListEqual( remove_duplicates([True, True, False, True, False, None, None]), [True, False, None], ) self.assertListEqual( remove_duplicates([1, 1, "hello", "hello", True, False, False]), [1, "hello", False], ) self.assertListEqual( remove_duplicates([1, "hello", True, False]), [1, "hello", False] ) class TestRotateArray(unittest.TestCase): def test_rotate_v1(self): self.assertListEqual( rotate_v1([1, 2, 3, 4, 5, 6, 7], k=3), [5, 6, 7, 1, 2, 3, 4] ) self.assertListEqual( rotate_v1([1, 2, 3, 4, 5, 6, 7], k=1), [7, 1, 2, 3, 4, 5, 6] ) self.assertListEqual( rotate_v1([1, 2, 3, 4, 5, 6, 7], k=7), [1, 2, 3, 4, 5, 6, 7] ) self.assertListEqual(rotate_v1([1, 2], k=111), [2, 1]) def test_rotate_v2(self): self.assertListEqual( rotate_v2([1, 2, 3, 4, 5, 6, 7], k=3), [5, 6, 7, 1, 2, 3, 4] ) self.assertListEqual( rotate_v2([1, 2, 3, 4, 5, 6, 7], k=1), [7, 1, 2, 3, 4, 5, 6] ) self.assertListEqual( rotate_v2([1, 2, 3, 4, 5, 6, 7], k=7), [1, 2, 3, 4, 5, 6, 7] ) self.assertListEqual(rotate_v2([1, 2], k=111), [2, 1]) def test_rotate_v3(self): self.assertListEqual( rotate_v3([1, 2, 3, 4, 5, 6, 7], k=3), [5, 6, 7, 1, 2, 3, 4] ) self.assertListEqual( rotate_v3([1, 2, 3, 4, 5, 6, 7], k=1), [7, 1, 2, 3, 4, 5, 6] ) self.assertListEqual( rotate_v3([1, 2, 3, 4, 5, 6, 7], k=7), [1, 2, 3, 4, 5, 6, 7] ) self.assertListEqual(rotate_v3([1, 2], k=111), [2, 1]) class TestSummaryRanges(unittest.TestCase): def test_summarize_ranges(self): self.assertListEqual( summarize_ranges([0, 1, 2, 4, 5, 7]), [(0, 2), (4, 5), (7, 7)] ) self.assertListEqual( summarize_ranges([-5, -4, -3, 1, 2, 4, 5, 6]), [(-5, -3), (1, 2), (4, 6)] ) self.assertListEqual(summarize_ranges([-2, -1, 0, 1, 2]), [(-2, 2)]) class TestThreeSum(unittest.TestCase): def test_three_sum(self): self.assertSetEqual(three_sum([-1, 0, 1, 2, -1, -4]), {(-1, 0, 1), (-1, -1, 2)}) self.assertSetEqual( three_sum([-1, 3, 1, 2, -1, -4, -2]), {(-4, 1, 3), (-2, -1, 3), (-1, -1, 2)} ) class TestTwoSum(unittest.TestCase): def test_two_sum(self): self.assertTupleEqual((0, 2), two_sum([2, 11, 7, 9], target=9)) self.assertTupleEqual((0, 3), two_sum([-3, 5, 2, 3, 8, -9], target=0)) self.assertIsNone(two_sum([-3, 5, 2, 3, 8, -9], target=6)) class TestTrimmean(unittest.TestCase): def test_trimmean(self): self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 20), 5.5) self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 20), 6.0) class TestTop1(unittest.TestCase): def test_top_1(self): self.assertListEqual(top_1([1, 1, 2, 2, 3]), [1, 2]) self.assertListEqual(top_1([1, 2, 3, 324, 234, 23, 23, 1, 23, 23]), [23]) class TestLimit(unittest.TestCase): def test_limit(self): self.assertListEqual(limit([1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) self.assertListEqual(limit([1, 2, 3, 4, 5], 2, 4), [2, 3, 4]) self.assertListEqual(limit([1, 2, 3, 4, 5], 2), [2, 3, 4, 5]) self.assertListEqual(limit([1, 2, 3, 4, 5], None, 4), [1, 2, 3, 4]) class TestNSum(unittest.TestCase): def test_n_sum(self): self.assertEqual(n_sum(2, [-3, 5, 2, 3, 8, -9], 6), []) # noqa: E501 self.assertEqual( n_sum(3, [-5, -4, -3, -2, -1, 0, 1, 2, 3], 0), sorted( [ [-5, 2, 3], [-2, 0, 2], [-4, 1, 3], [-3, 1, 2], [-1, 0, 1], [-2, -1, 3], [-3, 0, 3], ] ), ) # noqa: E501 self.assertEqual( n_sum(3, [-1, 0, 1, 2, -1, -4], 0), sorted([[-1, -1, 2], [-1, 0, 1]]) ) # noqa: E501 self.assertEqual( n_sum(4, [1, 0, -1, 0, -2, 2], 0), sorted([[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]), ) # noqa: E501 self.assertEqual( n_sum( 4, [7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, -3, -2], 10 ), sorted( [ [-6, 2, 7, 7], [-6, 3, 6, 7], [-6, 4, 5, 7], [-6, 4, 6, 6], [-5, 1, 7, 7], [-5, 2, 6, 7], [-5, 3, 5, 7], [-5, 3, 6, 6], [-5, 4, 4, 7], [-5, 4, 5, 6], [-4, 0, 7, 7], [-4, 1, 6, 7], [-4, 2, 5, 7], [-4, 2, 6, 6], [-4, 3, 4, 7], [-4, 3, 5, 6], [-4, 4, 4, 6], [-3, -1, 7, 7], [-3, 0, 6, 7], [-3, 1, 5, 7], [-3, 1, 6, 6], [-3, 2, 4, 7], [-3, 2, 5, 6], [-3, 3, 4, 6], [-3, 4, 4, 5], [-2, -2, 7, 7], [-2, -1, 6, 7], [-2, 0, 5, 7], [-2, 0, 6, 6], [-2, 1, 4, 7], [-2, 1, 5, 6], [-2, 2, 3, 7], [-2, 2, 4, 6], [-2, 3, 4, 5], [-1, 0, 4, 7], [-1, 0, 5, 6], [-1, 1, 3, 7], [-1, 1, 4, 6], [-1, 2, 3, 6], [-1, 2, 4, 5], [-1, 3, 4, 4], [0, 1, 2, 7], [0, 1, 3, 6], [0, 1, 4, 5], [0, 2, 3, 5], [0, 2, 4, 4], [1, 2, 3, 4], ] ), ) # noqa: E501 self.assertEqual( n_sum( 2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], 0, # noqa: E501 sum_closure=lambda a, b: a[0] + b[0], ), # noqa: E501 [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]], ) # noqa: E501 self.assertEqual( n_sum( 2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], [0, 3], # noqa: E501 sum_closure=lambda a, b: [a[0] + b[0], a[1] + b[1]], # noqa: E501 same_closure=lambda a, b: a[0] == b[0] and a[1] == b[1], ), # noqa: E501 [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]], ) # noqa: E501 self.assertEqual( n_sum( 2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], -5, # noqa: E501 sum_closure=lambda a, b: [a[0] + b[1], a[1] + b[0]], # noqa: E501 compare_closure=lambda a, b: -1 if a[0] < b else 1 if a[0] > b else 0, ), # noqa: E501 [[[-9, 5], [8, 4]]], ) # noqa: E501 if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_backtracking.py ================================================ import unittest from algorithms.backtracking import ( add_operators, anagram, array_sum_combinations, combination_sum, find_words, generate_abbreviations, generate_parenthesis_v1, generate_parenthesis_v2, get_factors, letter_combinations, palindromic_substrings, pattern_match, permute, permute_iter, permute_recursive, permute_unique, recursive_get_factors, subsets, subsets_unique, subsets_v2, unique_array_sum_combinations, ) class TestAddOperator(unittest.TestCase): def test_add_operators(self): # "123", 6 -> ["1+2+3", "1*2*3"] s = "123" target = 6 self.assertEqual(add_operators(s, target), ["1+2+3", "1*2*3"]) # "232", 8 -> ["2*3+2", "2+3*2"] s = "232" target = 8 self.assertEqual(add_operators(s, target), ["2+3*2", "2*3+2"]) s = "123045" target = 3 answer = [ "1+2+3*0*4*5", "1+2+3*0*45", "1+2-3*0*4*5", "1+2-3*0*45", "1-2+3+0-4+5", "1-2+3-0-4+5", "1*2+3*0-4+5", "1*2-3*0-4+5", "1*23+0-4*5", "1*23-0-4*5", "12+3*0-4-5", "12-3*0-4-5", ] self.assertEqual(add_operators(s, target), answer) class TestPermuteAndAnagram(unittest.TestCase): def test_permute(self): perms = ["abc", "bac", "bca", "acb", "cab", "cba"] self.assertEqual(perms, permute("abc")) def test_permute_iter(self): it = permute_iter("abc") perms = ["abc", "bac", "bca", "acb", "cab", "cba"] for i in range(len(perms)): self.assertEqual(perms[i], next(it)) def test_angram(self): self.assertTrue(anagram("apple", "pleap")) self.assertFalse(anagram("apple", "cherry")) class TestArrayCombinationSum(unittest.TestCase): def test_array_sum_combinations(self): a = [1, 2, 3, 3] b = [2, 3, 3, 4] c = [2, 3, 3, 4] target = 7 answer = [ [1, 2, 4], [1, 3, 3], [1, 3, 3], [1, 3, 3], [1, 3, 3], [1, 4, 2], [2, 2, 3], [2, 2, 3], [2, 3, 2], [2, 3, 2], [3, 2, 2], [3, 2, 2], ] answer.sort() self.assertListEqual(sorted(array_sum_combinations(a, b, c, target)), answer) def test_unique_array_sum_combinations(self): a = [1, 2, 3, 3] b = [2, 3, 3, 4] c = [2, 3, 3, 4] target = 7 answer = [(2, 3, 2), (3, 2, 2), (1, 2, 4), (1, 4, 2), (2, 2, 3), (1, 3, 3)] answer.sort() self.assertListEqual( sorted(unique_array_sum_combinations(a, b, c, target)), answer ) class TestCombinationSum(unittest.TestCase): def check_sum(self, nums, target): if sum(nums) == target: return (True, nums) else: return (False, nums) def test_combination_sum(self): candidates1 = [2, 3, 6, 7] target1 = 7 answer1 = [[2, 2, 3], [7]] self.assertEqual(combination_sum(candidates1, target1), answer1) candidates2 = [2, 3, 5] target2 = 8 answer2 = [[2, 2, 2, 2], [2, 3, 3], [3, 5]] self.assertEqual(combination_sum(candidates2, target2), answer2) class TestFactorCombinations(unittest.TestCase): def test_get_factors(self): target1 = 32 answer1 = [[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], [2, 4, 4], [4, 8]] self.assertEqual(sorted(get_factors(target1)), sorted(answer1)) target2 = 12 answer2 = [[2, 6], [2, 2, 3], [3, 4]] self.assertEqual(sorted(get_factors(target2)), sorted(answer2)) self.assertEqual(sorted(get_factors(1)), []) self.assertEqual(sorted(get_factors(37)), []) def test_recursive_get_factors(self): target1 = 32 answer1 = [[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], [2, 4, 4], [4, 8]] self.assertEqual(sorted(recursive_get_factors(target1)), sorted(answer1)) target2 = 12 answer2 = [[2, 6], [2, 2, 3], [3, 4]] self.assertEqual(sorted(recursive_get_factors(target2)), sorted(answer2)) self.assertEqual(sorted(recursive_get_factors(1)), []) self.assertEqual(sorted(recursive_get_factors(37)), []) class TestFindWords(unittest.TestCase): def test_normal(self): board = [ ["o", "a", "a", "n"], ["e", "t", "a", "e"], ["i", "h", "k", "r"], ["i", "f", "l", "v"], ] words = ["oath", "pea", "eat", "rain"] result = find_words(board, words) test_result = ["oath", "eat"] self.assertEqual(sorted(result), sorted(test_result)) def test_none(self): board = [ ["o", "a", "a", "n"], ["e", "t", "a", "e"], ["i", "h", "k", "r"], ["i", "f", "l", "v"], ] words = ["chicken", "nugget", "hello", "world"] self.assertEqual(find_words(board, words), []) def test_empty(self): board = [] words = [] self.assertEqual(find_words(board, words), []) def test_uneven(self): board = [["o", "a", "a", "n"], ["e", "t", "a", "e"]] words = ["oath", "pea", "eat", "rain"] self.assertEqual(find_words(board, words), ["eat"]) def test_repeat(self): board = [["a", "a", "a"], ["a", "a", "a"], ["a", "a", "a"]] words = ["a", "aa", "aaa", "aaaa", "aaaaa"] self.assertTrue(len(find_words(board, words)) == 5) class TestGenerateAbbreviations(unittest.TestCase): def test_generate_abbreviations(self): word1 = "word" answer1 = [ "word", "wor1", "wo1d", "wo2", "w1rd", "w1r1", "w2d", "w3", "1ord", "1or1", "1o1d", "1o2", "2rd", "2r1", "3d", "4", ] self.assertEqual(sorted(generate_abbreviations(word1)), sorted(answer1)) word2 = "hello" answer2 = [ "hello", "hell1", "hel1o", "hel2", "he1lo", "he1l1", "he2o", "he3", "h1llo", "h1ll1", "h1l1o", "h1l2", "h2lo", "h2l1", "h3o", "h4", "1ello", "1ell1", "1el1o", "1el2", "1e1lo", "1e1l1", "1e2o", "1e3", "2llo", "2ll1", "2l1o", "2l2", "3lo", "3l1", "4o", "5", ] self.assertEqual(sorted(generate_abbreviations(word2)), sorted(answer2)) class TestPatternMatch(unittest.TestCase): def test_pattern_match(self): pattern1 = "abab" string1 = "redblueredblue" pattern2 = "aaaa" string2 = "asdasdasdasd" pattern3 = "aabb" string3 = "xyzabcxzyabc" self.assertTrue(pattern_match(pattern1, string1)) self.assertTrue(pattern_match(pattern2, string2)) self.assertFalse(pattern_match(pattern3, string3)) class TestGenerateParenthesis(unittest.TestCase): def test_generate_parenthesis(self): self.assertEqual(generate_parenthesis_v1(2), ["()()", "(())"]) self.assertEqual( generate_parenthesis_v1(3), ["()()()", "()(())", "(())()", "(()())", "((()))"], ) self.assertEqual(generate_parenthesis_v2(2), ["(())", "()()"]) self.assertEqual( generate_parenthesis_v2(3), ["((()))", "(()())", "(())()", "()(())", "()()()"], ) class TestLetterCombinations(unittest.TestCase): def test_letter_combinations(self): digit1 = "23" answer1 = ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] self.assertEqual(sorted(letter_combinations(digit1)), sorted(answer1)) digit2 = "34" answer2 = ["dg", "dh", "di", "eg", "eh", "ei", "fg", "fh", "fi"] self.assertEqual(sorted(letter_combinations(digit2)), sorted(answer2)) class TestPalindromicSubstrings(unittest.TestCase): def test_palindromic_substrings(self): string1 = "abc" answer1 = [["a", "b", "c"]] self.assertEqual(palindromic_substrings(string1), sorted(answer1)) string2 = "abcba" answer2 = [["abcba"], ["a", "bcb", "a"], ["a", "b", "c", "b", "a"]] self.assertEqual(sorted(palindromic_substrings(string2)), sorted(answer2)) string3 = "abcccba" answer3 = [ ["abcccba"], ["a", "bcccb", "a"], ["a", "b", "ccc", "b", "a"], ["a", "b", "cc", "c", "b", "a"], ["a", "b", "c", "cc", "b", "a"], ["a", "b", "c", "c", "c", "b", "a"], ] self.assertEqual(sorted(palindromic_substrings(string3)), sorted(answer3)) class TestPermuteUnique(unittest.TestCase): def test_permute_unique(self): nums1 = [1, 1, 2] answer1 = [[2, 1, 1], [1, 2, 1], [1, 1, 2]] self.assertEqual(sorted(permute_unique(nums1)), sorted(answer1)) nums2 = [1, 2, 1, 3] answer2 = [ [3, 1, 2, 1], [1, 3, 2, 1], [1, 2, 3, 1], [1, 2, 1, 3], [3, 2, 1, 1], [2, 3, 1, 1], [2, 1, 3, 1], [2, 1, 1, 3], [3, 1, 1, 2], [1, 3, 1, 2], [1, 1, 3, 2], [1, 1, 2, 3], ] self.assertEqual(sorted(permute_unique(nums2)), sorted(answer2)) nums3 = [1, 2, 3] answer3 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]] self.assertEqual(sorted(permute_unique(nums3)), sorted(answer3)) class TestPermute(unittest.TestCase): def test_permute(self): nums1 = [1, 2, 3, 4] answer1 = [ [1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], [1, 3, 2, 4], [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], [1, 3, 4, 2], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3], [2, 4, 3, 1], [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1], [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1], ] self.assertEqual(sorted(permute(nums1)), sorted(answer1)) nums2 = [1, 2, 3] answer2 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]] self.assertEqual(sorted(permute(nums2)), sorted(answer2)) def test_permute_recursive(self): nums1 = [1, 2, 3, 4] answer1 = [ [1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], [1, 3, 2, 4], [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], [1, 3, 4, 2], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3], [2, 4, 3, 1], [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1], [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1], ] self.assertEqual(sorted(permute_recursive(nums1)), sorted(answer1)) nums2 = [1, 2, 3] answer2 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]] self.assertEqual(sorted(permute_recursive(nums2)), sorted(answer2)) class TestSubsetsUnique(unittest.TestCase): def test_subsets_unique(self): nums1 = [1, 2, 2] answer1 = [(1, 2), (1,), (1, 2, 2), (2,), (), (2, 2)] self.assertEqual(sorted(subsets_unique(nums1)), sorted(answer1)) nums2 = [1, 2, 3, 4] answer2 = [ (1, 2), (1, 3), (1, 2, 3, 4), (1,), (2,), (3,), (1, 4), (1, 2, 3), (4,), (), (2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), (3, 4), (2, 4), ] self.assertEqual(sorted(subsets_unique(nums2)), sorted(answer2)) class TestSubsets(unittest.TestCase): def test_subsets(self): nums1 = [1, 2, 3] answer1 = [[1, 2, 3], [1, 2], [1, 3], [1], [2, 3], [2], [3], []] self.assertEqual(sorted(subsets(nums1)), sorted(answer1)) nums2 = [1, 2, 3, 4] answer2 = [ [1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], [3, 4], [3], [4], [], ] self.assertEqual(sorted(subsets(nums2)), sorted(answer2)) def test_subsets_v2(self): nums1 = [1, 2, 3] answer1 = [[1, 2, 3], [1, 2], [1, 3], [1], [2, 3], [2], [3], []] self.assertEqual(sorted(subsets_v2(nums1)), sorted(answer1)) nums2 = [1, 2, 3, 4] answer2 = [ [1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], [3, 4], [3], [4], [], ] self.assertEqual(sorted(subsets_v2(nums2)), sorted(answer2)) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_bit_manipulation.py ================================================ import random import unittest from algorithms.bit_manipulation import ( add_bitwise_operator, binary_gap, bytes_big_endian_to_int, bytes_little_endian_to_int, clear_bit, count_flips_to_convert, count_ones_iter, count_ones_recur, find_difference, find_missing_number, find_missing_number2, flip_bit_longest_seq, get_bit, has_alternative_bit, has_alternative_bit_fast, insert_mult_bits, insert_one_bit, int_to_bytes_big_endian, int_to_bytes_little_endian, is_power_of_two, remove_bit, reverse_bits, set_bit, single_number, single_number2, single_number3, subsets, swap_pair, update_bit, ) class TestSuite(unittest.TestCase): def setUp(self): """Initialize seed.""" random.seed("test") def test_add_bitwise_operator(self): self.assertEqual(5432 + 97823, add_bitwise_operator(5432, 97823)) self.assertEqual(0, add_bitwise_operator(0, 0)) self.assertEqual(10, add_bitwise_operator(10, 0)) self.assertEqual(10, add_bitwise_operator(0, 10)) def test_count_ones_recur(self): # 8 -> 1000 self.assertEqual(1, count_ones_recur(8)) # 109 -> 1101101 self.assertEqual(5, count_ones_recur(109)) # 63 -> 111111 self.assertEqual(6, count_ones_recur(63)) # 0 -> 0 self.assertEqual(0, count_ones_recur(0)) def test_count_ones_iter(self): # 8 -> 1000 self.assertEqual(1, count_ones_iter(8)) # 109 -> 1101101 self.assertEqual(5, count_ones_iter(109)) # 63 -> 111111 self.assertEqual(6, count_ones_iter(63)) # 0 -> 0 self.assertEqual(0, count_ones_iter(0)) def test_count_flips_to_convert(self): # 29: 11101 and 15: 01111 self.assertEqual(2, count_flips_to_convert(29, 15)) # 45: 0000101101 and 987: 1111011011 self.assertEqual(8, count_flips_to_convert(45, 987)) # 34: 100010 self.assertEqual(0, count_flips_to_convert(34, 34)) # 34: 100010 and 53: 110101 self.assertEqual(4, count_flips_to_convert(34, 53)) def test_find_missing_number(self): self.assertEqual(7, find_missing_number([4, 1, 3, 0, 6, 5, 2])) self.assertEqual(0, find_missing_number([1])) self.assertEqual(1, find_missing_number([0])) nums = [i for i in range(100000) if i != 12345] random.shuffle(nums) self.assertEqual(12345, find_missing_number(nums)) def test_find_missing_number2(self): self.assertEqual(7, find_missing_number2([4, 1, 3, 0, 6, 5, 2])) self.assertEqual(0, find_missing_number2([1])) self.assertEqual(1, find_missing_number2([0])) nums = [i for i in range(100000) if i != 12345] random.shuffle(nums) self.assertEqual(12345, find_missing_number2(nums)) def test_flip_bit_longest_seq(self): # 1775: 11011101111 self.assertEqual(8, flip_bit_longest_seq(1775)) # 5: 101 self.assertEqual(3, flip_bit_longest_seq(5)) # 71: 1000111 self.assertEqual(4, flip_bit_longest_seq(71)) # 0: 0 self.assertEqual(1, flip_bit_longest_seq(0)) def test_is_power_of_two(self): self.assertTrue(is_power_of_two(64)) self.assertFalse(is_power_of_two(91)) self.assertTrue(is_power_of_two(2**1001)) self.assertTrue(is_power_of_two(1)) self.assertFalse(is_power_of_two(0)) def test_reverse_bits(self): self.assertEqual(43261596, reverse_bits(964176192)) self.assertEqual(964176192, reverse_bits(43261596)) self.assertEqual(1, reverse_bits(2147483648)) # bin(0) => 00000000000000000000000000000000 self.assertEqual(0, reverse_bits(0)) # bin(2**32 - 1) => 11111111111111111111111111111111 self.assertEqual(2**32 - 1, reverse_bits(2**32 - 1)) def test_single_number(self): random.seed("test") self.assertEqual(0, single_number([1, 0, 2, 1, 2, 3, 3])) self.assertEqual(101, single_number([101])) single = random.randint(1, 100000) nums = [random.randint(1, 100000) for _ in range(1000)] nums *= 2 # nums contains pairs of random integers nums.append(single) random.shuffle(nums) self.assertEqual(single, single_number(nums)) def test_single_number2(self): self.assertEqual(3, single_number2([4, 2, 3, 2, 1, 1, 4, 2, 4, 1])) single = random.randint(1, 100000) nums = [random.randint(1, 100000) for _ in range(1000)] nums *= 3 # nums contains triplets of random integers nums.append(single) random.shuffle(nums) self.assertEqual(single, single_number2(nums)) def test_single_number3(self): self.assertEqual(sorted([2, 5]), sorted(single_number3([2, 1, 5, 6, 6, 1]))) self.assertEqual(sorted([4, 3]), sorted(single_number3([9, 9, 4, 3]))) def test_subsets(self): self.assertSetEqual( subsets([1, 2, 3]), {(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)}, ) self.assertSetEqual( subsets([10, 20, 30, 40]), { (10, 40), (10, 20, 40), (10, 30), (10, 20, 30, 40), (40,), (10, 30, 40), (30,), (20, 30), (30, 40), (10,), (), (10, 20), (20, 40), (20, 30, 40), (10, 20, 30), (20,), }, ) def test_get_bit(self): # 22 = 10110 self.assertEqual(1, get_bit(22, 2)) self.assertEqual(0, get_bit(22, 3)) def test_set_bit(self): # 22 = 10110 --> after set bit at 3th position: 30 = 11110 self.assertEqual(30, set_bit(22, 3)) def test_clear_bit(self): # 22 = 10110 --> after clear bit at 2nd position: 20 = 10010 self.assertEqual(18, clear_bit(22, 2)) def test_update_bit(self): # 22 = 10110 --> after update bit at 3th position with # value 1: 30 = 11110 self.assertEqual(30, update_bit(22, 3, 1)) # 22 = 10110 --> after update bit at 2nd position with # value 0: 20 = 10010 self.assertEqual(18, update_bit(22, 2, 0)) def test_int_to_bytes_big_endian(self): self.assertEqual(b"\x11", int_to_bytes_big_endian(17)) def test_int_to_bytes_little_endian(self): self.assertEqual(b"\x11", int_to_bytes_little_endian(17)) def test_bytes_big_endian_to_int(self): self.assertEqual(17, bytes_big_endian_to_int(b"\x11")) def test_bytes_little_endian_to_int(self): self.assertEqual(17, bytes_little_endian_to_int(b"\x11")) def test_swap_pair(self): # 22: 10110 --> 41: 101001 self.assertEqual(41, swap_pair(22)) # 10: 1010 --> 5 : 0101 self.assertEqual(5, swap_pair(10)) def test_find_difference(self): self.assertEqual("e", find_difference("abcd", "abecd")) def test_has_alternative_bit(self): self.assertTrue(has_alternative_bit(5)) self.assertFalse(has_alternative_bit(7)) self.assertFalse(has_alternative_bit(11)) self.assertTrue(has_alternative_bit(10)) def test_has_alternative_bit_fast(self): self.assertTrue(has_alternative_bit_fast(5)) self.assertFalse(has_alternative_bit_fast(7)) self.assertFalse(has_alternative_bit_fast(11)) self.assertTrue(has_alternative_bit_fast(10)) def test_insert_one_bit(self): """ Input: num = 10101 (21) insert_one_bit(num, 1, 2): 101101 (45) insert_one_bit(num, 0 ,2): 101001 (41) insert_one_bit(num, 1, 5): 110101 (53) insert_one_bit(num, 1, 0): 101010 (42) """ self.assertEqual(45, insert_one_bit(21, 1, 2)) self.assertEqual(41, insert_one_bit(21, 0, 2)) self.assertEqual(53, insert_one_bit(21, 1, 5)) self.assertEqual(43, insert_one_bit(21, 1, 0)) def test_insert_mult_bits(self): """ Input: num = 101 (5) insert_mult_bits(num, 7, 3, 1): 101111 (47) insert_mult_bits(num, 7, 3, 0): 101111 (47) insert_mult_bits(num, 7, 3, 3): 111101 (61) """ self.assertEqual(47, insert_mult_bits(5, 7, 3, 1)) self.assertEqual(47, insert_mult_bits(5, 7, 3, 0)) self.assertEqual(61, insert_mult_bits(5, 7, 3, 3)) def test_remove_bit(self): """ Input: num = 10101 (21) remove_bit(num, 2): output = 1001 (9) remove_bit(num, 4): output = 101 (5) remove_bit(num, 0): output = 1010 (10) """ self.assertEqual(9, remove_bit(21, 2)) self.assertEqual(5, remove_bit(21, 4)) self.assertEqual(10, remove_bit(21, 0)) def test_binary_gap(self): # 22 = 10110 self.assertEqual(2, binary_gap(22)) # 6 = 110 self.assertEqual(1, binary_gap(6)) # 8 = 1000 self.assertEqual(0, binary_gap(8)) # 145 = 10010001 self.assertEqual(4, binary_gap(145)) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_community_algorithms.py ================================================ """Tests for community-contributed algorithms. Covers algorithms adopted from open PRs and implemented to match the repo's code standards. """ from __future__ import annotations import unittest from algorithms.backtracking.minimax import minimax from algorithms.bit_manipulation.gray_code import gray_code, gray_to_binary from algorithms.data_structures.kd_tree import KDTree from algorithms.dynamic_programming.bitmask import tsp from algorithms.dynamic_programming.count_paths_dp import ( count_paths_dp, count_paths_memo, count_paths_recursive, ) from algorithms.graph.blossom import max_matching from algorithms.math.linear_regression import linear_regression, r_squared, rmse from algorithms.math.manhattan_distance import manhattan_distance from algorithms.math.polynomial_division import polynomial_division from algorithms.searching.exponential_search import exponential_search from algorithms.searching.sentinel_search import sentinel_search from algorithms.string.alphabet_board_path import alphabet_board_path from algorithms.string.manacher import manacher from algorithms.string.swap_characters import can_swap_to_equal from algorithms.string.z_algorithm import compute_z_array, z_search class TestManacher(unittest.TestCase): def test_odd_palindrome(self): result = manacher("babad") self.assertIn(result, ("bab", "aba")) def test_even_palindrome(self): self.assertEqual(manacher("cbbd"), "bb") def test_single_char(self): self.assertEqual(manacher("a"), "a") def test_full_palindrome(self): self.assertEqual(manacher("racecar"), "racecar") def test_empty(self): self.assertEqual(manacher(""), "") def test_all_same(self): self.assertEqual(manacher("aaaa"), "aaaa") class TestZAlgorithm(unittest.TestCase): def test_z_array_basic(self): z = compute_z_array("aabxaa") self.assertEqual(z[0], 6) self.assertEqual(z[1], 1) self.assertEqual(z[4], 2) def test_z_search_found(self): self.assertEqual(z_search("abxabcabcaby", "abcaby"), [6]) def test_z_search_multiple(self): self.assertEqual(z_search("aaaa", "aa"), [0, 1, 2]) def test_z_search_not_found(self): self.assertEqual(z_search("hello", "xyz"), []) def test_z_search_empty(self): self.assertEqual(z_search("abc", ""), []) self.assertEqual(z_search("", "abc"), []) def test_z_array_empty(self): self.assertEqual(compute_z_array(""), []) class TestGrayCode(unittest.TestCase): def test_2bit(self): self.assertEqual(gray_code(2), [0, 1, 3, 2]) def test_3bit(self): self.assertEqual(gray_code(3), [0, 1, 3, 2, 6, 7, 5, 4]) def test_1bit(self): self.assertEqual(gray_code(1), [0, 1]) def test_0bit(self): self.assertEqual(gray_code(0), [0]) def test_successive_differ_by_one_bit(self): codes = gray_code(4) for i in range(len(codes) - 1): xor = codes[i] ^ codes[i + 1] self.assertTrue(xor & (xor - 1) == 0) # power of 2 def test_gray_to_binary_roundtrip(self): for n in range(16): gray = n ^ (n >> 1) self.assertEqual(gray_to_binary(gray), n) class TestKdTree(unittest.TestCase): def test_nearest_basic(self): points = [(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)] tree = KDTree(points) self.assertEqual(tree.nearest((9, 2)), (8, 1)) def test_nearest_exact(self): points = [(1, 1), (2, 2), (3, 3)] tree = KDTree(points) self.assertEqual(tree.nearest((2, 2)), (2, 2)) def test_nearest_3d(self): points = [(0, 0, 0), (1, 1, 1), (2, 2, 2)] tree = KDTree(points) self.assertEqual(tree.nearest((1, 1, 0)), (1, 1, 1)) def test_single_point(self): tree = KDTree([(5, 5)]) self.assertEqual(tree.nearest((0, 0)), (5, 5)) class TestExponentialSearch(unittest.TestCase): def test_found(self): arr = [1, 3, 5, 7, 9, 11, 13, 15] self.assertEqual(exponential_search(arr, 7), 3) def test_first_element(self): self.assertEqual(exponential_search([1, 2, 3], 1), 0) def test_last_element(self): self.assertEqual(exponential_search([1, 2, 3], 3), 2) def test_not_found(self): self.assertEqual(exponential_search([1, 2, 3], 4), -1) def test_empty(self): self.assertEqual(exponential_search([], 1), -1) def test_single_element_found(self): self.assertEqual(exponential_search([42], 42), 0) def test_single_element_not_found(self): self.assertEqual(exponential_search([42], 99), -1) class TestSentinelSearch(unittest.TestCase): def test_found(self): arr = [4, 2, 7, 1, 9] self.assertEqual(sentinel_search(arr, 7), 2) def test_last_element(self): arr = [4, 2, 7, 1, 9] self.assertEqual(sentinel_search(arr, 9), 4) def test_not_found(self): arr = [4, 2, 7, 1, 9] self.assertEqual(sentinel_search(arr, 5), -1) def test_empty(self): self.assertEqual(sentinel_search([], 1), -1) def test_restores_array(self): arr = [1, 2, 3] sentinel_search(arr, 99) self.assertEqual(arr, [1, 2, 3]) class TestManhattanDistance(unittest.TestCase): def test_2d(self): self.assertEqual(manhattan_distance((1, 2), (4, 6)), 7) def test_3d(self): self.assertEqual(manhattan_distance((0, 0, 0), (1, 2, 3)), 6) def test_same_point(self): self.assertEqual(manhattan_distance((3, 4), (3, 4)), 0) def test_negative(self): self.assertEqual(manhattan_distance((-1, -1), (1, 1)), 4) class TestLinearRegression(unittest.TestCase): def test_basic_fit(self): x = [1, 2, 3, 4, 5] y = [2, 4, 5, 4, 5] m, b = linear_regression(x, y) self.assertAlmostEqual(m, 0.6) self.assertAlmostEqual(b, 2.2) def test_perfect_line(self): x = [0, 1, 2, 3] y = [0, 2, 4, 6] m, b = linear_regression(x, y) self.assertAlmostEqual(m, 2.0) self.assertAlmostEqual(b, 0.0) def test_r_squared_perfect(self): x = [0, 1, 2, 3] y = [0, 2, 4, 6] self.assertAlmostEqual(r_squared(x, y), 1.0) def test_rmse_perfect(self): x = [0, 1, 2, 3] y = [0, 2, 4, 6] self.assertAlmostEqual(rmse(x, y), 0.0) def test_rmse_nonperfect(self): x = [1, 2, 3, 4, 5] y = [2, 4, 5, 4, 5] self.assertGreater(rmse(x, y), 0) def test_too_few_points(self): with self.assertRaises(ValueError): linear_regression([1], [2]) class TestPolynomialDivision(unittest.TestCase): def test_basic(self): # (x^2 - 3x + 2) / (x - 1) = (x - 2), remainder 0 q, r = polynomial_division([1, -3, 2], [1, -1]) self.assertEqual(q, [1.0, -2.0]) self.assertEqual(r, [0.0]) def test_with_remainder(self): # (x^2 + 1) / (x - 1) = (x + 1), remainder 2 q, r = polynomial_division([1, 0, 1], [1, -1]) self.assertAlmostEqual(q[0], 1.0) self.assertAlmostEqual(q[1], 1.0) self.assertAlmostEqual(r[0], 2.0) def test_divide_by_zero(self): with self.assertRaises(ZeroDivisionError): polynomial_division([1, 2], [0, 0]) class TestAlphabetBoardPath(unittest.TestCase): def test_leet(self): path = alphabet_board_path("leet") self.assertIn("!", path) # Verify length: must spell 4 chars so exactly 4 '!'s self.assertEqual(path.count("!"), 4) def test_code(self): path = alphabet_board_path("code") self.assertEqual(path.count("!"), 4) def test_z(self): # 'z' is at row 5, col 0 — must go down 5 from 'a' path = alphabet_board_path("z") self.assertEqual(path, "DDDDD!") def test_za(self): path = alphabet_board_path("za") self.assertEqual(path, "DDDDD!UUUUU!") class TestSwapCharacters(unittest.TestCase): def test_can_swap(self): self.assertTrue(can_swap_to_equal("ab", "ba")) def test_identical(self): # No differences — need exactly 2, so False self.assertFalse(can_swap_to_equal("ab", "ab")) def test_too_many_diffs(self): self.assertFalse(can_swap_to_equal("abc", "xyz")) def test_different_lengths(self): self.assertFalse(can_swap_to_equal("ab", "abc")) def test_one_diff(self): self.assertFalse(can_swap_to_equal("ab", "ac")) class TestMinimax(unittest.TestCase): def test_depth_2(self): # max(min(3,5), min(2,9)) = max(3,2) = 3 self.assertEqual(minimax(2, True, [3, 5, 2, 9]), 3) def test_depth_3(self): self.assertEqual(minimax(3, True, [3, 5, 2, 9, 12, 5, 23, 23]), 12) def test_single_leaf(self): self.assertEqual(minimax(0, True, [42]), 42) def test_minimizing(self): self.assertEqual(minimax(1, False, [3, 5]), 3) class TestTsp(unittest.TestCase): def test_4_cities(self): dist = [ [0, 10, 15, 20], [10, 0, 35, 25], [15, 35, 0, 30], [20, 25, 30, 0], ] self.assertEqual(tsp(dist), 80) def test_3_cities(self): dist = [ [0, 1, 2], [1, 0, 3], [2, 3, 0], ] self.assertEqual(tsp(dist), 6) class TestCountPathsDp(unittest.TestCase): def test_3x7(self): self.assertEqual(count_paths_dp(3, 7), 28) self.assertEqual(count_paths_memo(3, 7), 28) self.assertEqual(count_paths_recursive(3, 7), 28) def test_3x3(self): self.assertEqual(count_paths_dp(3, 3), 6) def test_1x1(self): self.assertEqual(count_paths_dp(1, 1), 1) def test_2x2(self): self.assertEqual(count_paths_dp(2, 2), 2) class TestBlossom(unittest.TestCase): def test_path_graph(self): # Path: 0-1-2-3 → max matching has 2 edges matching = max_matching(4, [(0, 1), (1, 2), (2, 3)]) self.assertEqual(len(matching), 2) def test_triangle(self): # Triangle: 0-1-2 → max matching has 1 edge matching = max_matching(3, [(0, 1), (1, 2), (0, 2)]) self.assertEqual(len(matching), 1) def test_empty_graph(self): matching = max_matching(3, []) self.assertEqual(len(matching), 0) def test_complete_4(self): # K4 → max matching has 2 edges edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] matching = max_matching(4, edges) self.assertEqual(len(matching), 2) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_compression.py ================================================ import unittest from algorithms.compression.elias import elias_delta, elias_gamma from algorithms.compression.huffman_coding import HuffmanCoding from algorithms.compression.rle_compression import decode_rle, encode_rle class TestHuffmanCoding(unittest.TestCase): @classmethod def setUpClass(cls): cls.file_in_name = "huffman_coding_in.txt" cls.file_out_bin_name = "huffman_coding_out.bin" cls.file_out_name = "huffman_coding_out.txt" def setUp(self): import random random.seed(1951) with open(self.file_in_name, "wb") as file_in: for _ in range(10000): file_in.write(bytes([random.randrange(0, 256)])) def test_huffman_coding(self): HuffmanCoding.encode_file(self.file_in_name, self.file_out_bin_name) HuffmanCoding.decode_file(self.file_out_bin_name, self.file_out_name) with ( open(self.file_in_name, "rb") as file_1, open(self.file_out_name, "rb") as file_2, ): content_1 = file_1.read() content_2 = file_2.read() self.assertEqual(content_1, content_2) def tearDown(self): import os os.remove(self.file_in_name) os.remove(self.file_out_bin_name) os.remove(self.file_out_name) class TestRLECompression(unittest.TestCase): def test_encode_rle(self): self.assertEqual( "12W1B12W3B24W1B14W", encode_rle( "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW" ), ) def test_decode_rle(self): self.assertEqual( "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW", decode_rle("12W1B12W3B24W1B14W"), ) class TestEliasCoding(unittest.TestCase): def test_elias_gamma(self): correct_result = [ "0", "00", "100", "101", "11000", "11001", "11010", "11011", "1110000", "1110001", "1110010", ] result = [] for i in range(11): result.append(elias_gamma(i)) self.assertEqual(correct_result, result) def test_elias_delta(self): correct_result = [ "0", "000", "1000", "1001", "10100", "10101", "10110", "10111", "11000000", "11000001", "11000010", ] result = [] for i in range(11): result.append(elias_delta(i)) self.assertEqual(correct_result, result) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_data_structures.py ================================================ """Tests for data structures in algorithms/data_structures.""" import unittest from algorithms.data_structures.avl_tree import AvlTree from algorithms.data_structures.hash_table import HashTable, ResizableHashTable from algorithms.data_structures.red_black_tree import RBNode, RBTree from algorithms.data_structures.segment_tree import SegmentTree from algorithms.data_structures.separate_chaining_hash_table import ( SeparateChainingHashTable, ) from algorithms.data_structures.trie import Trie from algorithms.data_structures.union_find import Union class TestRBTree(unittest.TestCase): def _make_tree(self, values): tree = RBTree() for v in values: tree.insert(RBNode(v, 1)) return tree def test_insert_single(self): tree = self._make_tree([5]) result = tree.inorder() self.assertEqual(len(result), 1) self.assertEqual(result[0]["val"], 5) def test_insert_multiple_sorted(self): values = [11, 2, 14, 1, 7, 15, 5, 8, 4] tree = self._make_tree(values) result = tree.inorder() vals = [r["val"] for r in result] self.assertEqual(vals, sorted(values)) def test_root_is_black(self): tree = self._make_tree([10, 5, 15]) self.assertEqual(tree.root.color, 0) def test_empty_tree(self): tree = RBTree() self.assertIsNone(tree.root) self.assertEqual(tree.inorder(), []) def test_insert_duplicates_order(self): tree = self._make_tree([3, 1, 2]) result = tree.inorder() vals = [r["val"] for r in result] self.assertEqual(vals, [1, 2, 3]) class TestAvlTree(unittest.TestCase): def test_insert_single(self): tree = AvlTree() tree.insert(10) self.assertIsNotNone(tree.node) self.assertEqual(tree.node.val, 10) def test_insert_multiple_root_exists(self): tree = AvlTree() for v in [5, 3, 7, 1, 4]: tree.insert(v) self.assertIsNotNone(tree.node) def test_balanced_after_insert(self): tree = AvlTree() for v in [1, 2, 3, 4, 5]: tree.insert(v) # Tree should remain balanced; height should be <= log2(5)+1 ~ 3 self.assertLessEqual(tree.height, 3) def test_empty_tree(self): tree = AvlTree() self.assertIsNone(tree.node) self.assertEqual(tree.in_order_traverse(), []) def test_in_order_traverse_populated(self): tree = AvlTree() for v in [5, 3, 7, 1, 4]: tree.insert(v) self.assertEqual(tree.in_order_traverse(), [1, 3, 4, 5, 7]) def test_insert_balance_factor(self): tree = AvlTree() for v in [5, 4, 3, 2, 1]: tree.insert(v) # After balancing, the balance factor should be within [-1, 1] self.assertIn(tree.balance, [-1, 0, 1]) class TestTrie(unittest.TestCase): def test_insert_and_search(self): trie = Trie() trie.insert("apple") self.assertTrue(trie.search("apple")) def test_search_missing(self): trie = Trie() trie.insert("apple") self.assertFalse(trie.search("app")) def test_starts_with(self): trie = Trie() trie.insert("apple") self.assertTrue(trie.starts_with("app")) self.assertFalse(trie.starts_with("apl")) def test_empty_trie(self): trie = Trie() self.assertFalse(trie.search("anything")) self.assertFalse(trie.starts_with("a")) def test_multiple_words(self): trie = Trie() for w in ["cat", "car", "card", "care"]: trie.insert(w) self.assertTrue(trie.search("card")) self.assertFalse(trie.search("ca")) self.assertTrue(trie.starts_with("ca")) def test_insert_single_char(self): trie = Trie() trie.insert("a") self.assertTrue(trie.search("a")) self.assertFalse(trie.search("b")) class TestUnionFind(unittest.TestCase): def test_add_and_root(self): uf = Union() uf.add(1) self.assertEqual(uf.root(1), 1) def test_unite_connects(self): uf = Union() uf.add(1) uf.add(2) uf.unite(1, 2) self.assertEqual(uf.root(1), uf.root(2)) def test_not_connected(self): uf = Union() uf.add(1) uf.add(2) self.assertNotEqual(uf.root(1), uf.root(2)) def test_count_decrements_on_unite(self): uf = Union() uf.add(1) uf.add(2) uf.add(3) self.assertEqual(uf.count, 3) uf.unite(1, 2) self.assertEqual(uf.count, 2) def test_unite_same_element(self): uf = Union() uf.add(1) uf.unite(1, 1) self.assertEqual(uf.count, 1) def test_transitive_connectivity(self): uf = Union() for x in [1, 2, 3]: uf.add(x) uf.unite(1, 2) uf.unite(2, 3) self.assertEqual(uf.root(1), uf.root(3)) class TestSegmentTree(unittest.TestCase): def test_max_query(self): tree = SegmentTree([2, 4, 5, 3, 4], max) self.assertEqual(tree.query(2, 4), 5) def test_sum_query(self): tree = SegmentTree([1, 2, 3, 4, 5], lambda a, b: a + b) self.assertEqual(tree.query(0, 4), 15) def test_single_element_query(self): tree = SegmentTree([7, 2, 9], max) self.assertEqual(tree.query(0, 0), 7) self.assertEqual(tree.query(2, 2), 9) def test_full_range_max(self): arr = [3, 1, 4, 1, 5, 9, 2, 6] tree = SegmentTree(arr, max) self.assertEqual(tree.query(0, len(arr) - 1), 9) class TestHashTable(unittest.TestCase): def test_put_and_get(self): ht = HashTable() ht.put(1, "one") self.assertEqual(ht.get(1), "one") def test_get_missing(self): ht = HashTable() self.assertIsNone(ht.get(99)) def test_delete(self): ht = HashTable() ht.put(1, "one") ht.del_(1) self.assertIsNone(ht.get(1)) def test_update_existing(self): ht = HashTable() ht.put(1, "one") ht.put(1, "ONE") self.assertEqual(ht.get(1), "ONE") def test_len(self): ht = HashTable() ht.put(1, "a") ht.put(2, "b") self.assertEqual(len(ht), 2) def test_bracket_operators(self): ht = HashTable() ht[5] = "five" self.assertEqual(ht[5], "five") del ht[5] self.assertIsNone(ht[5]) class TestResizableHashTable(unittest.TestCase): def test_put_and_get(self): ht = ResizableHashTable() ht.put(1, "a") self.assertEqual(ht.get(1), "a") def test_resizes_automatically(self): ht = ResizableHashTable() for i in range(20): ht.put(i, str(i)) for i in range(20): self.assertEqual(ht.get(i), str(i)) class TestSeparateChainingHashTable(unittest.TestCase): def test_put_and_get(self): table = SeparateChainingHashTable() table.put("hello", "world") self.assertEqual(table.get("hello"), "world") def test_get_missing(self): table = SeparateChainingHashTable() self.assertIsNone(table.get("missing")) def test_delete(self): table = SeparateChainingHashTable() table.put("key", "value") table.del_("key") self.assertIsNone(table.get("key")) def test_len(self): table = SeparateChainingHashTable() table.put("a", 1) table.put("b", 2) self.assertEqual(len(table), 2) def test_collision_handling(self): # Force collision by using small table table = SeparateChainingHashTable(size=1) table.put("x", 10) table.put("y", 20) self.assertEqual(table.get("x"), 10) self.assertEqual(table.get("y"), 20) def test_bracket_operators(self): table = SeparateChainingHashTable() table["k"] = "v" self.assertEqual(table["k"], "v") del table["k"] self.assertIsNone(table["k"]) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_dynamic_programming.py ================================================ import unittest from algorithms.dynamic_programming import ( Item, Job, climb_stairs, climb_stairs_optimized, combination_sum_bottom_up, combination_sum_topdown, count, edit_distance, egg_drop, fib_iter, fib_list, fib_recursive, find_k_factor, get_maximum_value, hosoya_testing, house_robber, int_divide, longest_increasing_subsequence, max_profit_naive, max_profit_optimized, planting_trees, regex_matching, schedule, ) class TestBuySellStock(unittest.TestCase): def test_max_profit_naive(self): self.assertEqual(max_profit_naive([7, 1, 5, 3, 6, 4]), 5) self.assertEqual(max_profit_naive([7, 6, 4, 3, 1]), 0) def test_max_profit_optimized(self): self.assertEqual(max_profit_optimized([7, 1, 5, 3, 6, 4]), 5) self.assertEqual(max_profit_optimized([7, 6, 4, 3, 1]), 0) class TestClimbingStairs(unittest.TestCase): def test_climb_stairs(self): self.assertEqual(climb_stairs(2), 2) self.assertEqual(climb_stairs(10), 89) def test_climb_stairs_optimized(self): self.assertEqual(climb_stairs_optimized(2), 2) self.assertEqual(climb_stairs_optimized(10), 89) class TestCoinChange(unittest.TestCase): def test_count(self): self.assertEqual(count([1, 2, 3], 4), 4) self.assertEqual(count([2, 5, 3, 6], 10), 5) class TestCombinationSum(unittest.TestCase): def test_combination_sum_topdown(self): self.assertEqual(combination_sum_topdown([1, 2, 3], 4), 7) def test_combination_sum_bottom_up(self): self.assertEqual(combination_sum_bottom_up([1, 2, 3], 4), 7) class TestEditDistance(unittest.TestCase): def test_edit_distance(self): self.assertEqual(edit_distance("food", "money"), 4) self.assertEqual(edit_distance("horse", "ros"), 3) class TestEggDrop(unittest.TestCase): def test_egg_drop(self): self.assertEqual(egg_drop(1, 2), 2) self.assertEqual(egg_drop(2, 6), 3) self.assertEqual(egg_drop(3, 14), 4) class TestFib(unittest.TestCase): def test_fib_recursive(self): self.assertEqual(fib_recursive(10), 55) self.assertEqual(fib_recursive(30), 832040) def test_fib_list(self): self.assertEqual(fib_list(10), 55) self.assertEqual(fib_list(30), 832040) def test_fib_iter(self): self.assertEqual(fib_iter(10), 55) self.assertEqual(fib_iter(30), 832040) class TestHosoyaTriangle(unittest.TestCase): """[summary] Test for the file hosoya_triangle Arguments: unittest {[type]} -- [description] """ def test_hosoya(self): self.assertEqual([1], hosoya_testing(1)) self.assertEqual( [1, 1, 1, 2, 1, 2, 3, 2, 2, 3, 5, 3, 4, 3, 5, 8, 5, 6, 6, 5, 8], hosoya_testing(6), ) self.assertEqual( [ 1, 1, 1, 2, 1, 2, 3, 2, 2, 3, 5, 3, 4, 3, 5, 8, 5, 6, 6, 5, 8, 13, 8, 10, 9, 10, 8, 13, 21, 13, 16, 15, 15, 16, 13, 21, 34, 21, 26, 24, 25, 24, 26, 21, 34, 55, 34, 42, 39, 40, 40, 39, 42, 34, 55, ], hosoya_testing(10), ) class TestHouseRobber(unittest.TestCase): def test_house_robber(self): self.assertEqual(44, house_robber([1, 2, 16, 3, 15, 3, 12, 1])) class TestJobScheduling(unittest.TestCase): def test_job_scheduling(self): job1, job2 = Job(1, 3, 2), Job(2, 3, 4) self.assertEqual(4, schedule([job1, job2])) class TestKnapsack(unittest.TestCase): def test_get_maximum_value(self): item1, item2, item3 = Item(60, 10), Item(100, 20), Item(120, 30) self.assertEqual(220, get_maximum_value([item1, item2, item3], 50)) item1, item2, item3, item4 = Item(60, 5), Item(50, 3), Item(70, 4), Item(30, 2) self.assertEqual(80, get_maximum_value([item1, item2, item3, item4], 5)) class TestLongestIncreasingSubsequence(unittest.TestCase): def test_longest_increasing_subsequence(self): sequence = [1, 101, 10, 2, 3, 100, 4, 6, 2] self.assertEqual(5, longest_increasing_subsequence(sequence)) class TestLongestIncreasingSubsequenceOptimized(unittest.TestCase): def test_longest_increasing_subsequence_optimized(self): sequence = [1, 101, 10, 2, 3, 100, 4, 6, 2] self.assertEqual(5, longest_increasing_subsequence(sequence)) class TestLongestIncreasingSubsequenceOptimized2(unittest.TestCase): def test_longest_increasing_subsequence_optimized2(self): sequence = [1, 101, 10, 2, 3, 100, 4, 6, 2] self.assertEqual(5, longest_increasing_subsequence(sequence)) class TestIntDivide(unittest.TestCase): def test_int_divide(self): self.assertEqual(5, int_divide(4)) self.assertEqual(42, int_divide(10)) self.assertEqual(204226, int_divide(50)) class TestDpKFactor(unittest.TestCase): def test_kfactor(self): # Test 1 n1 = 4 k1 = 1 self.assertEqual(find_k_factor(n1, k1), 1) # Test 2 n2 = 7 k2 = 1 self.assertEqual(find_k_factor(n2, k2), 70302) # Test 3 n3 = 10 k3 = 2 self.assertEqual(find_k_factor(n3, k3), 74357) # Test 4 n4 = 8 k4 = 2 self.assertEqual(find_k_factor(n4, k4), 53) # Test 5 n5 = 9 k5 = 1 self.assertEqual(find_k_factor(n5, k5), 71284044) class TestPlantingTrees(unittest.TestCase): def test_simple(self): # arrange trees = [0, 1, 10, 10] length = 10 width = 1 # act res = planting_trees(trees, length, width) # assert self.assertEqual(res, 2.414213562373095) def test_simple2(self): # arrange trees = [0, 3, 5, 5, 6, 9] length = 10 width = 1 # act res = planting_trees(trees, length, width) # assert self.assertEqual(res, 9.28538328578604) class TestRegexMatching(unittest.TestCase): def test_none_0(self): s = "" p = "" self.assertTrue(regex_matching.is_match(s, p)) def test_none_1(self): s = "" p = "a" self.assertFalse(regex_matching.is_match(s, p)) def test_no_symbol_equal(self): s = "abcd" p = "abcd" self.assertTrue(regex_matching.is_match(s, p)) def test_no_symbol_not_equal_0(self): s = "abcd" p = "efgh" self.assertFalse(regex_matching.is_match(s, p)) def test_no_symbol_not_equal_1(self): s = "ab" p = "abb" self.assertFalse(regex_matching.is_match(s, p)) def test_symbol_0(self): s = "" p = "a*" self.assertTrue(regex_matching.is_match(s, p)) def test_symbol_1(self): s = "a" p = "ab*" self.assertTrue(regex_matching.is_match(s, p)) def test_symbol_2(self): # E.g. # s a b b # p 1 0 0 0 # a 0 1 0 0 # b 0 0 1 0 # * 0 1 1 1 s = "abb" p = "ab*" self.assertTrue(regex_matching.is_match(s, p)) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_graph.py ================================================ import unittest from algorithms.graph import ( Sudoku, Tarjan, all_pairs_shortest_path, bellman_ford, check_bipartite, check_digraph_strongly_connected, count_connected_number_of_component, count_islands, cycle_detection, dinic, edmonds_karp, find_path, find_path_dfs, ford_fulkerson, get_factors, get_factors_iterative1, get_factors_iterative2, ladder_length, maximum_flow_bfs, maximum_flow_dfs, maze_search, num_islands_dfs, pacific_atlantic, path_between_two_vertices_in_digraph, prims_minimum_spanning, strongly_connected_components_kosaraju, top_sort, top_sort_recursive, topological_sort, walls_and_gates, ) from algorithms.graph.dijkstra import Dijkstra class TestTarjan(unittest.TestCase): """ Test for the file tarjan.py Arguments: unittest {[type]} -- [description] """ def test_tarjan_example_1(self): # Graph from https://en.wikipedia.org/wiki/File:Scc.png example = { "A": ["B"], "B": ["C", "E", "F"], "C": ["D", "G"], "D": ["C", "H"], "E": ["A", "F"], "F": ["G"], "G": ["F"], "H": ["D", "G"], } g = Tarjan(example) self.assertEqual(g.sccs, [["F", "G"], ["C", "D", "H"], ["A", "B", "E"]]) def test_tarjan_example_2(self): # Graph from https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm#/media/File:Tarjan%27s_Algorithm_Animation.gif example = { "A": ["E"], "B": ["A"], "C": ["B", "D"], "D": ["C"], "E": ["B"], "F": ["B", "E", "G"], "G": ["F", "C"], "H": ["G", "H", "D"], } g = Tarjan(example) self.assertEqual(g.sccs, [["A", "B", "E"], ["C", "D"], ["F", "G"], ["H"]]) class TestCheckBipartite(unittest.TestCase): def test_check_bipartite(self): adj_list_1 = [[0, 0, 1], [0, 0, 1], [1, 1, 0]] self.assertEqual(True, check_bipartite(adj_list_1)) adj_list_2 = [[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]] self.assertEqual(True, check_bipartite(adj_list_2)) adj_list_3 = [[0, 1, 0, 0], [1, 0, 1, 1], [0, 1, 0, 1], [0, 1, 1, 0]] self.assertEqual(False, check_bipartite(adj_list_3)) class TestDijkstra(unittest.TestCase): def test_dijkstra(self): g = Dijkstra(9) g.graph = [ [0, 4, 0, 0, 0, 0, 0, 8, 0], [4, 0, 8, 0, 0, 0, 0, 11, 0], [0, 8, 0, 7, 0, 4, 0, 0, 2], [0, 0, 7, 0, 9, 14, 0, 0, 0], [0, 0, 0, 9, 0, 10, 0, 0, 0], [0, 0, 4, 14, 10, 0, 2, 0, 0], [0, 0, 0, 0, 0, 2, 0, 1, 6], [8, 11, 0, 0, 0, 0, 1, 0, 7], [0, 0, 2, 0, 0, 0, 6, 7, 0], ] self.assertEqual(g.dijkstra(0), [0, 4, 12, 19, 21, 11, 9, 8, 14]) class TestMaximumFlow(unittest.TestCase): """ Test for the file maximum_flow.py Arguments: unittest {[type]} -- [description] """ def test_ford_fulkerson(self): capacity = [ [0, 10, 10, 0, 0, 0, 0], [0, 0, 2, 0, 4, 8, 0], [0, 0, 0, 0, 0, 9, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 10], [0, 0, 0, 0, 6, 0, 10], [0, 0, 0, 0, 0, 0, 0], ] self.assertEqual(19, ford_fulkerson(capacity, 0, 6)) def test_edmonds_karp(self): capacity = [ [0, 10, 10, 0, 0, 0, 0], [0, 0, 2, 0, 4, 8, 0], [0, 0, 0, 0, 0, 9, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 10], [0, 0, 0, 0, 6, 0, 10], [0, 0, 0, 0, 0, 0, 0], ] self.assertEqual(19, edmonds_karp(capacity, 0, 6)) def dinic(self): capacity = [ [0, 10, 10, 0, 0, 0, 0], [0, 0, 2, 0, 4, 8, 0], [0, 0, 0, 0, 0, 9, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 10], [0, 0, 0, 0, 6, 0, 10], [0, 0, 0, 0, 0, 0, 0], ] self.assertEqual(19, dinic(capacity, 0, 6)) class TestMaximumFlowBfs(unittest.TestCase): """ Test for the file def maximum_flow_bfs.py Arguments: unittest {[type]} -- [description] """ def test_maximum_flow_bfs(self): graph = [ [0, 16, 13, 0, 0, 0], [0, 0, 10, 12, 0, 0], [0, 4, 0, 0, 14, 0], [0, 0, 9, 0, 0, 20], [0, 0, 0, 7, 0, 4], [0, 0, 0, 0, 0, 0], ] maximum_flow = maximum_flow_bfs(graph) self.assertEqual(maximum_flow, 23) class TestMaximumFlowDfs(unittest.TestCase): """ Test for the file def maximum_flow_dfs.py Arguments: unittest {[type]} -- [description] """ def test_maximum_flow_dfs(self): graph = [ [0, 16, 13, 0, 0, 0], [0, 0, 10, 12, 0, 0], [0, 4, 0, 0, 14, 0], [0, 0, 9, 0, 0, 20], [0, 0, 0, 7, 0, 4], [0, 0, 0, 0, 0, 0], ] maximum_flow = maximum_flow_dfs(graph) self.assertEqual(maximum_flow, 23) class TestAllPairsShortestPath(unittest.TestCase): def test_all_pairs_shortest_path(self): graph = [ [0, 0.1, 0.101, 0.142, 0.277], [0.465, 0, 0.191, 0.192, 0.587], [0.245, 0.554, 0, 0.333, 0.931], [1.032, 0.668, 0.656, 0, 0.151], [0.867, 0.119, 0.352, 0.398, 0], ] result = all_pairs_shortest_path(graph) self.assertEqual( result, [ [0, 0.1, 0.101, 0.142, 0.277], [0.436, 0, 0.191, 0.192, 0.34299999999999997], [0.245, 0.345, 0, 0.333, 0.484], [0.706, 0.27, 0.46099999999999997, 0, 0.151], [0.5549999999999999, 0.119, 0.31, 0.311, 0], ], ) class TestBellmanFord(unittest.TestCase): def test_bellman_ford(self): graph1 = { "a": {"b": 6, "e": 7}, "b": {"c": 5, "d": -4, "e": 8}, "c": {"b": -2}, "d": {"a": 2, "c": 7}, "e": {"b": -3}, } self.assertEqual(True, bellman_ford(graph1, "a")) graph2 = { "a": {"d": 3, "e": 4}, "b": {"a": 7, "e": 2}, "c": {"a": 12, "d": 9, "e": 11}, "d": {"c": 5, "e": 11}, "e": {"a": 7, "b": 5, "d": 1}, } self.assertEqual(True, bellman_ford(graph2, "a")) class TestConnectedComponentInGraph(unittest.TestCase): """ Class for testing different cases for connected components in graph """ def test_count_connected_components(self): """ Test Function that test the different cases of count connected components 2----------0 1--------5 3 | | 4 output = 3 """ expected_result = 3 # adjacency list representation of graph adj_list = [[2], [5], [0, 4], [], [2], [1]] size = 5 result = count_connected_number_of_component.count_components(adj_list, size) self.assertEqual(result, expected_result) def test_connected_components_with_empty_graph(self): """ input : output : 0 """ adj_list = [[]] expected_result = 0 size = 0 result = count_connected_number_of_component.count_components(adj_list, size) self.assertEqual(result, expected_result) def test_connected_components_without_edges_graph(self): """ input : 0 2 3 4 output : 4 """ adj_list = [[0], [], [2], [3], [4]] size = 4 expected_result = 4 result = count_connected_number_of_component.count_components(adj_list, size) self.assertEqual(result, expected_result) class PrimsMinimumSpanning(unittest.TestCase): def test_prim_spanning(self): graph1 = { 1: [[3, 2], [8, 3]], 2: [[3, 1], [5, 4]], 3: [[8, 1], [2, 4], [4, 5]], 4: [[5, 2], [2, 3], [6, 5]], 5: [[4, 3], [6, 4]], } self.assertEqual(14, prims_minimum_spanning(graph1)) graph2 = { 1: [[7, 2], [6, 4]], 2: [[7, 1], [9, 4], [6, 3]], 3: [[8, 4], [6, 2]], 4: [[6, 1], [9, 2], [8, 3]], } self.assertEqual(19, prims_minimum_spanning(graph2)) class TestDigraphStronglyConnected(unittest.TestCase): def test_digraph_strongly_connected(self): g1 = check_digraph_strongly_connected.Graph(5) g1.add_edge(0, 1) g1.add_edge(1, 2) g1.add_edge(2, 3) g1.add_edge(3, 0) g1.add_edge(2, 4) g1.add_edge(4, 2) self.assertTrue(g1.is_strongly_connected()) g2 = check_digraph_strongly_connected.Graph(4) g2.add_edge(0, 1) g2.add_edge(1, 2) g2.add_edge(2, 3) self.assertFalse(g2.is_strongly_connected()) class TestCycleDetection(unittest.TestCase): def test_cycle_detection_with_cycle(self): graph = { "A": ["B", "C"], "B": ["D"], "C": ["F"], "D": ["E", "F"], "E": ["B"], "F": [], } self.assertTrue(cycle_detection.contains_cycle(graph)) def test_cycle_detection_with_no_cycle(self): graph = { "A": ["B", "C"], "B": ["D", "E"], "C": ["F"], "D": ["E"], "E": [], "F": [], } self.assertFalse(cycle_detection.contains_cycle(graph)) class TestFindPath(unittest.TestCase): def test_find_all_paths(self): graph = { "A": ["B", "C"], "B": ["C", "D"], "C": ["D", "F"], "D": ["C"], "E": ["F"], "F": ["C"], } paths = find_path.find_all_path(graph, "A", "F") print(paths) self.assertEqual( sorted(paths), sorted( [ ["A", "C", "F"], ["A", "B", "C", "F"], ["A", "B", "D", "C", "F"], ] ), ) class TestPathBetweenTwoVertices(unittest.TestCase): def test_node_is_reachable(self): g = path_between_two_vertices_in_digraph.Graph(4) g.add_edge(0, 1) g.add_edge(0, 2) g.add_edge(1, 2) g.add_edge(2, 0) g.add_edge(2, 3) g.add_edge(3, 3) self.assertTrue(g.is_reachable(1, 3)) self.assertFalse(g.is_reachable(3, 1)) class TestStronglyConnectedComponentsKosaraju(unittest.TestCase): def test_kosaraju_algorithm(self): vertices = 6 adj = [[2], [0], [3], [1, 4], [5], [4]] result = ( strongly_connected_components_kosaraju.Kosaraju() .kosaraju(vertices, adj) ) # Expected result: 2 strongly connected components self.assertEqual(result, 2) # --- Tests merged from test_bfs.py --- class TestCountIslandsBfs(unittest.TestCase): def test_count_islands(self): grid_1 = [[1, 1, 1, 1, 0], [1, 1, 0, 1, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0]] self.assertEqual(1, count_islands(grid_1)) grid_2 = [[1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 1]] self.assertEqual(3, count_islands(grid_2)) grid_3 = [ [1, 1, 1, 0, 0, 0], [1, 1, 0, 0, 0, 0], [1, 0, 0, 0, 0, 1], [0, 0, 1, 1, 0, 1], [0, 0, 1, 1, 0, 0], ] self.assertEqual(3, count_islands(grid_3)) grid_4 = [ [1, 1, 0, 0, 1, 1], [0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 0, 0], ] self.assertEqual(5, count_islands(grid_4)) class TestMazeSearchBfs(unittest.TestCase): def test_maze_search(self): grid_1 = [ [1, 0, 1, 1, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 1], [1, 1, 1, 0, 1, 1], ] self.assertEqual(14, maze_search(grid_1)) grid_2 = [[1, 0, 0], [0, 1, 1], [0, 1, 1]] self.assertEqual(-1, maze_search(grid_2)) class TestWordLadder(unittest.TestCase): def test_ladder_length(self): # hit -> hot -> dot -> dog -> cog self.assertEqual( 5, ladder_length("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) ) # pick -> sick -> sink -> sank -> tank == 5 self.assertEqual( 5, ladder_length("pick", "tank", ["tock", "tick", "sank", "sink", "sick"]) ) # live -> life == 1, no matter what is the word_list. self.assertEqual(1, ladder_length("live", "life", ["hoho", "luck"])) # 0 length from ate -> ate self.assertEqual(0, ladder_length("ate", "ate", [])) # not possible to reach ! self.assertEqual(-1, ladder_length("rahul", "coder", ["blahh", "blhah"])) # --- Tests merged from test_dfs.py --- class TestAllFactors(unittest.TestCase): def test_get_factors(self): self.assertEqual( [[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], [2, 4, 4], [4, 8]], get_factors(32), ) def test_get_factors_iterative1(self): self.assertEqual( [[2, 16], [4, 8], [2, 2, 8], [2, 4, 4], [2, 2, 2, 4], [2, 2, 2, 2, 2]], get_factors_iterative1(32), ) def test_get_factors_iterative2(self): self.assertEqual( [[2, 2, 2, 2, 2], [2, 2, 2, 4], [2, 2, 8], [2, 4, 4], [2, 16], [4, 8]], get_factors_iterative2(32), ) class TestCountIslandsDfs(unittest.TestCase): def test_num_islands(self): self.assertEqual( 1, num_islands_dfs( [[1, 1, 1, 1, 0], [1, 1, 0, 1, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0]] ), ) self.assertEqual( 3, num_islands_dfs( [[1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 1]] ), ) class TestPacificAtlantic(unittest.TestCase): def test_pacific_atlantic(self): self.assertEqual( [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]], pacific_atlantic( [ [1, 2, 2, 3, 5], [3, 2, 3, 4, 4], [2, 4, 5, 3, 1], [6, 7, 1, 4, 5], [5, 1, 1, 2, 4], ] ), ) class TestSudoku(unittest.TestCase): def test_sudoku_solver(self): board = [["5", "3", "."], ["6", ".", "."], [".", "9", "8"]] test_obj = Sudoku(board, 3, 3) test_obj.solve() self.assertEqual( [["5", "3", "1"], ["6", "1", "2"], ["1", "9", "8"]], test_obj.board ) class TestWallsAndGates(unittest.TestCase): def test_walls_and_gates(self): rooms = [ [float("inf"), -1, 0, float("inf")], [float("inf"), float("inf"), float("inf"), -1], [float("inf"), -1, float("inf"), -1], [0, -1, float("inf"), float("inf")], ] walls_and_gates(rooms) self.assertEqual( [[3, -1, 0, 1], [2, 2, 1, -1], [1, -1, 2, -1], [0, -1, 3, 4]], rooms ) class TestMazeSearchDfs(unittest.TestCase): def test_maze_search(self): maze_1 = [ [1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1], ] self.assertEqual(37, find_path_dfs(maze_1)) maze_2 = [ [1, 0, 1, 1, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 1], [1, 1, 1, 0, 1, 1], ] self.assertEqual(14, find_path_dfs(maze_2)) maze_3 = [[1, 0, 0], [0, 1, 1], [0, 1, 1]] self.assertEqual(-1, find_path_dfs(maze_3)) # --- Tests merged from test_topological.py --- class TestTopSort(unittest.TestCase): def setUp(self): self.depGraph = { "a": ["b"], "b": ["c"], "c": ["e"], "e": ["g"], "d": [], "f": ["e", "d"], "g": [], } def test_topsort(self): res = top_sort_recursive(self.depGraph) self.assertTrue(res.index("g") < res.index("e")) res = top_sort(self.depGraph) self.assertTrue(res.index("g") < res.index("e")) class TestTopologicalSort(unittest.TestCase): def test_simple_dag(self): vertices = 6 edges = [(5, 2), (5, 0), (4, 0), (4, 1), (2, 3), (3, 1)] order = topological_sort(vertices, edges) # Verify correct length self.assertEqual(len(order), vertices) # Verify constraints position = {node: i for i, node in enumerate(order)} for u, v in edges: self.assertLess(position[u], position[v]) def test_single_vertex(self): vertices = 1 edges = [] order = topological_sort(vertices, edges) self.assertEqual(order, [0]) def test_disconnected_graph(self): vertices = 4 edges = [(0, 1), (2, 3)] order = topological_sort(vertices, edges) self.assertEqual(len(order), vertices) position = {node: i for i, node in enumerate(order)} for u, v in edges: self.assertLess(position[u], position[v]) def test_no_edges(self): vertices = 5 edges = [] order = topological_sort(vertices, edges) self.assertEqual(len(order), vertices) self.assertCountEqual(order, [0, 1, 2, 3, 4]) def test_cycle_detection(self): vertices = 3 edges = [(0, 1), (1, 2), (2, 0)] with self.assertRaises(ValueError): topological_sort(vertices, edges) def test_self_loop_cycle(self): vertices = 2 edges = [(0, 0)] with self.assertRaises(ValueError): topological_sort(vertices, edges) ================================================ FILE: tests/test_greedy.py ================================================ import unittest from algorithms.greedy import ( max_contiguous_subsequence_sum, ) class TestMaxContiguousSubsequenceSum(unittest.TestCase): def test_max_contiguous_subsequence_sum(self): arr1 = [-2, 3, 8, -1, 4] arr2 = [-1, 1, 0] arr3 = [-1, -3, -4] arr4 = [-2, 3, 8, -12, 8, 4] self.assertEqual(max_contiguous_subsequence_sum(arr1), 14) self.assertEqual(max_contiguous_subsequence_sum(arr2), 1) self.assertEqual(max_contiguous_subsequence_sum(arr3), -1) self.assertEqual(max_contiguous_subsequence_sum(arr4), 12) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_heap.py ================================================ import unittest from algorithms.heap import BinaryHeap, get_skyline, k_closest, max_sliding_window class TestBinaryHeap(unittest.TestCase): """ Test suite for the binary_heap data structures """ def setUp(self): self.min_heap = BinaryHeap() self.min_heap.insert(4) self.min_heap.insert(50) self.min_heap.insert(7) self.min_heap.insert(55) self.min_heap.insert(90) self.min_heap.insert(87) def test_insert(self): # Before insert 2: [0, 4, 50, 7, 55, 90, 87] # After insert: [0, 2, 50, 4, 55, 90, 87, 7] self.min_heap.insert(2) self.assertEqual([0, 2, 50, 4, 55, 90, 87, 7], self.min_heap.heap) self.assertEqual(7, self.min_heap.current_size) def test_remove_min(self): ret = self.min_heap.remove_min() # Before remove_min : [0, 4, 50, 7, 55, 90, 87] # After remove_min: [7, 50, 87, 55, 90] # Test return value self.assertEqual(4, ret) self.assertEqual([0, 7, 50, 87, 55, 90], self.min_heap.heap) self.assertEqual(5, self.min_heap.current_size) class TestSuite(unittest.TestCase): def test_get_skyline(self): buildings = [[2, 9, 10], [3, 7, 15], [5, 12, 12], [15, 20, 10], [19, 24, 8]] # Expect output output = [[2, 10], [3, 15], [7, 12], [12, 0], [15, 10], [20, 8], [24, 0]] self.assertEqual(output, get_skyline(buildings)) def test_max_sliding_window(self): nums = [1, 3, -1, -3, 5, 3, 6, 7] self.assertEqual([3, 3, 5, 5, 6, 7], max_sliding_window(nums, 3)) def test_k_closest_points(self): points = [(1, 0), (2, 3), (5, 2), (1, 1), (2, 8), (10, 2), (-1, 0), (-2, -2)] self.assertEqual([(-1, 0), (1, 0)], k_closest(points, 2)) self.assertEqual([(1, 1), (-1, 0), (1, 0)], k_closest(points, 3)) self.assertEqual([(-2, -2), (1, 1), (1, 0), (-1, 0)], k_closest(points, 4)) self.assertEqual( [(10, 2), (2, 8), (5, 2), (-2, -2), (2, 3), (1, 0), (-1, 0), (1, 1)], k_closest(points, 8), ) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_issue_fixes.py ================================================ """Tests for algorithms added to resolve open GitHub issues.""" from __future__ import annotations import unittest from algorithms.common.tree_node import TreeNode from algorithms.data_structures import SqrtDecomposition from algorithms.graph.dijkstra_heapq import dijkstra from algorithms.math.goldbach import goldbach, verify_goldbach from algorithms.tree.binary_tree_views import ( bottom_view, left_view, right_view, top_view, ) # ── Dijkstra with priority queue (#565) ──────────────────────────────── class TestDijkstraHeapq(unittest.TestCase): """Tests for the heap-based Dijkstra implementation.""" def setUp(self): self.graph = { "s": {"a": 2, "b": 1}, "a": {"s": 3, "b": 4, "c": 8}, "b": {"s": 4, "a": 2, "d": 2}, "c": {"a": 2, "d": 7, "t": 4}, "d": {"b": 1, "c": 11, "t": 5}, "t": {"c": 3, "d": 5}, } def test_shortest_path(self): dist, path = dijkstra(self.graph, "s", "t") self.assertEqual(dist, 8) self.assertEqual(path, ["s", "b", "d", "t"]) def test_same_source_and_target(self): dist, path = dijkstra(self.graph, "s", "s") self.assertEqual(dist, 0) self.assertEqual(path, ["s"]) def test_direct_neighbor(self): dist, path = dijkstra(self.graph, "s", "b") self.assertEqual(dist, 1) self.assertEqual(path, ["s", "b"]) def test_unreachable_target(self): graph = {"a": {"b": 1}, "b": {}, "c": {}} dist, path = dijkstra(graph, "a", "c") self.assertEqual(dist, float("inf")) self.assertEqual(path, []) def test_single_node(self): graph = {"a": {}} dist, path = dijkstra(graph, "a", "a") self.assertEqual(dist, 0) self.assertEqual(path, ["a"]) def test_triangle(self): graph = {"a": {"b": 1, "c": 4}, "b": {"c": 2}, "c": {}} dist, path = dijkstra(graph, "a", "c") self.assertEqual(dist, 3) self.assertEqual(path, ["a", "b", "c"]) # ── Goldbach's conjecture (#908) ──────────────────────────────────────── class TestGoldbach(unittest.TestCase): """Tests for Goldbach's conjecture decomposition.""" def test_small_even(self): self.assertEqual(goldbach(4), (2, 2)) def test_goldbach_28(self): p, q = goldbach(28) self.assertEqual(p + q, 28) self.assertTrue(p <= q) def test_goldbach_100(self): p, q = goldbach(100) self.assertEqual(p + q, 100) def test_goldbach_large(self): p, q = goldbach(1000) self.assertEqual(p + q, 1000) def test_odd_raises(self): with self.assertRaises(ValueError): goldbach(7) def test_two_raises(self): with self.assertRaises(ValueError): goldbach(2) def test_negative_raises(self): with self.assertRaises(ValueError): goldbach(-4) def test_verify_range(self): self.assertTrue(verify_goldbach(200)) # ── Binary tree views (#829) ──────────────────────────────────────────── class TestBinaryTreeViews(unittest.TestCase): """Tests for left/right/top/bottom tree views.""" def setUp(self): """Build a tree: 1 / \\ 2 3 / \\ \\ 4 5 6 """ self.root = TreeNode( 1, TreeNode(2, TreeNode(4), TreeNode(5)), TreeNode(3, None, TreeNode(6)), ) def test_left_view(self): self.assertEqual(left_view(self.root), [1, 2, 4]) def test_right_view(self): self.assertEqual(right_view(self.root), [1, 3, 6]) def test_top_view(self): self.assertEqual(top_view(self.root), [4, 2, 1, 3, 6]) def test_bottom_view(self): self.assertEqual(bottom_view(self.root), [4, 2, 5, 3, 6]) def test_empty_tree(self): self.assertEqual(left_view(None), []) self.assertEqual(right_view(None), []) self.assertEqual(top_view(None), []) self.assertEqual(bottom_view(None), []) def test_single_node(self): root = TreeNode(42) self.assertEqual(left_view(root), [42]) self.assertEqual(right_view(root), [42]) self.assertEqual(top_view(root), [42]) self.assertEqual(bottom_view(root), [42]) def test_left_skewed(self): """ 1 / 2 / 3 """ root = TreeNode(1, TreeNode(2, TreeNode(3))) self.assertEqual(left_view(root), [1, 2, 3]) self.assertEqual(right_view(root), [1, 2, 3]) def test_right_skewed(self): """ 1 \\ 2 \\ 3 """ root = TreeNode(1, None, TreeNode(2, None, TreeNode(3))) self.assertEqual(left_view(root), [1, 2, 3]) self.assertEqual(right_view(root), [1, 2, 3]) # ── Square root decomposition (#651) ──────────────────────────────────── class TestSqrtDecomposition(unittest.TestCase): """Tests for square root decomposition range queries.""" def test_full_range_sum(self): sd = SqrtDecomposition([1, 2, 3, 4, 5, 6, 7, 8, 9]) self.assertEqual(sd.query(0, 8), 45) def test_partial_range(self): sd = SqrtDecomposition([1, 2, 3, 4, 5, 6, 7, 8, 9]) self.assertEqual(sd.query(2, 5), 18) # 3+4+5+6 def test_single_element(self): sd = SqrtDecomposition([10, 20, 30]) self.assertEqual(sd.query(1, 1), 20) def test_update(self): sd = SqrtDecomposition([1, 2, 3, 4, 5]) sd.update(2, 10) self.assertEqual(sd.query(0, 4), 22) # 1+2+10+4+5 def test_update_full_range(self): sd = SqrtDecomposition([1, 2, 3, 4, 5, 6, 7, 8, 9]) sd.update(4, 10) self.assertEqual(sd.query(0, 8), 50) def test_multiple_updates(self): sd = SqrtDecomposition([1, 1, 1, 1, 1]) sd.update(0, 5) sd.update(4, 5) self.assertEqual(sd.query(0, 4), 13) # 5+1+1+1+5 def test_out_of_range_update(self): sd = SqrtDecomposition([1, 2, 3]) with self.assertRaises(IndexError): sd.update(5, 10) def test_invalid_query(self): sd = SqrtDecomposition([1, 2, 3]) with self.assertRaises(IndexError): sd.query(0, 5) def test_small_array(self): sd = SqrtDecomposition([42]) self.assertEqual(sd.query(0, 0), 42) sd.update(0, 100) self.assertEqual(sd.query(0, 0), 100) def test_larger_array(self): arr = list(range(1, 101)) # 1..100 sd = SqrtDecomposition(arr) self.assertEqual(sd.query(0, 99), 5050) self.assertEqual(sd.query(0, 9), 55) # 1+2+...+10 if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_iterative_segment_tree.py ================================================ import unittest from functools import reduce from algorithms.data_structures.iterative_segment_tree import SegmentTree def gcd(a, b): if b == 0: return a return gcd(b, a % b) class TestSegmentTree(unittest.TestCase): """ Test for the Iterative Segment Tree data structure """ def test_segment_tree_creation(self): arr = [2, 4, 3, 6, 8, 9, 3] max_segment_tree = SegmentTree(arr, max) min_segment_tree = SegmentTree(arr, min) sum_segment_tree = SegmentTree(arr, lambda a, b: a + b) gcd_segment_tree = SegmentTree(arr, gcd) self.assertEqual( max_segment_tree.tree, [None, 9, 8, 9, 4, 8, 9, 2, 4, 3, 6, 8, 9, 3] ) self.assertEqual( min_segment_tree.tree, [None, 2, 3, 2, 3, 6, 3, 2, 4, 3, 6, 8, 9, 3] ) self.assertEqual( sum_segment_tree.tree, [None, 35, 21, 14, 7, 14, 12, 2, 4, 3, 6, 8, 9, 3] ) self.assertEqual( gcd_segment_tree.tree, [None, 1, 1, 1, 1, 2, 3, 2, 4, 3, 6, 8, 9, 3] ) def test_max_segment_tree(self): arr = [-1, 1, 10, 2, 9, -3, 8, 4, 7, 5, 6, 0] self.__test_all_segments(arr, max) def test_min_segment_tree(self): arr = [1, 10, -2, 9, -3, 8, 4, -7, 5, 6, 11, -12] self.__test_all_segments(arr, min) def test_sum_segment_tree(self): arr = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6, -11, -12] self.__test_all_segments(arr, lambda a, b: a + b) def test_gcd_segment_tree(self): arr = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6, 11, 12, 14] self.__test_all_segments(arr, gcd) def test_max_segment_tree_with_updates(self): arr = [-1, 1, 10, 2, 9, -3, 8, 4, 7, 5, 6, 0] updates = { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10, 10: 11, 11: 12, } self.__test_all_segments_with_updates(arr, max, updates) def test_min_segment_tree_with_updates(self): arr = [1, 10, -2, 9, -3, 8, 4, -7, 5, 6, 11, -12] updates = { 0: 7, 1: 2, 2: 6, 3: -14, 4: 5, 5: 4, 6: 7, 7: -10, 8: 9, 9: 10, 10: 12, 11: 1, } self.__test_all_segments_with_updates(arr, min, updates) def test_sum_segment_tree_with_updates(self): arr = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6, -11, -12] updates = { 0: 12, 1: 11, 2: 10, 3: 9, 4: 8, 5: 7, 6: 6, 7: 5, 8: 4, 9: 3, 10: 2, 11: 1, } self.__test_all_segments_with_updates(arr, lambda a, b: a + b, updates) def test_gcd_segment_tree_with_updates(self): arr = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6, 11, 12, 14] updates = { 0: 4, 1: 2, 2: 3, 3: 9, 4: 21, 5: 7, 6: 4, 7: 4, 8: 2, 9: 5, 10: 17, 11: 12, 12: 3, } self.__test_all_segments_with_updates(arr, gcd, updates) def __test_all_segments(self, arr, fnc): """ Test all possible segments in the tree :param arr: array to test :param fnc: function of the segment tpree """ segment_tree = SegmentTree(arr, fnc) self.__test_segments_helper(segment_tree, fnc, arr) def __test_all_segments_with_updates(self, arr, fnc, upd): """ Test all possible segments in the tree with updates :param arr: array to test :param fnc: function of the segment tree :param upd: updates to test """ segment_tree = SegmentTree(arr, fnc) for index, value in upd.items(): arr[index] = value segment_tree.update(index, value) self.__test_segments_helper(segment_tree, fnc, arr) def __test_segments_helper(self, seg_tree, fnc, arr): for i in range(0, len(arr)): for j in range(i, len(arr)): range_value = reduce(fnc, arr[i : j + 1]) self.assertEqual(seg_tree.query(i, j), range_value) ================================================ FILE: tests/test_linked_list.py ================================================ import unittest from algorithms.linked_list import ( RandomListNode, copy_random_pointer_v1, copy_random_pointer_v2, is_cyclic, is_palindrome, is_palindrome_dict, is_palindrome_stack, is_sorted, merge_two_list, merge_two_list_recur, remove_range, reverse_list, reverse_list_recursive, rotate_right, swap_pairs, ) class Node: def __init__(self, x): self.val = x self.next = None # Convert from linked list Node to list for testing def convert(head): ret = [] if head: current = head while current: ret.append(current.val) current = current.next return ret class TestSuite(unittest.TestCase): def setUp(self): # list test for palindrome self.l = Node("A") self.l.next = Node("B") self.l.next.next = Node("C") self.l.next.next.next = Node("B") self.l.next.next.next.next = Node("A") self.l1 = Node("A") self.l1.next = Node("B") self.l1.next.next = Node("C") self.l1.next.next.next = Node("B") def test_reverse_list(self): head = Node(1) head.next = Node(2) head.next.next = Node(3) head.next.next.next = Node(4) self.assertEqual([4, 3, 2, 1], convert(reverse_list(head))) head = Node(1) head.next = Node(2) head.next.next = Node(3) head.next.next.next = Node(4) self.assertEqual([4, 3, 2, 1], convert(reverse_list_recursive(head))) def test_is_sorted(self): head = Node(-2) head.next = Node(2) head.next.next = Node(2) head.next.next.next = Node(4) head.next.next.next.next = Node(9) # head -> -2 -> 2 -> 2 -> 4 -> 9 self.assertTrue(is_sorted(head)) head = Node(1) head.next = Node(2) head.next.next = Node(8) head.next.next.next = Node(4) head.next.next.next.next = Node(6) # head -> 1 -> 2 -> 8 -> 4 -> 6 self.assertFalse(is_sorted(head)) def test_remove_range(self): # Test case: middle case. head = Node(0) head.next = Node(1) head.next.next = Node(2) head.next.next.next = Node(3) head.next.next.next.next = Node(4) # Expect output: 0 4 self.assertEqual([0, 4], convert(remove_range(head, 1, 3))) # Test case: taking out the front node head = Node(0) head.next = Node(1) head.next.next = Node(2) head.next.next.next = Node(3) head.next.next.next.next = Node(4) # Expect output: 2 3 4 self.assertEqual([2, 3, 4], convert(remove_range(head, 0, 1))) # Test case: removing all the nodes head = Node(0) head.next = Node(1) head.next.next = Node(2) head.next.next.next = Node(3) head.next.next.next.next = Node(4) self.assertEqual([], convert(remove_range(head, 0, 7))) def test_swap_in_pairs(self): head = Node(1) head.next = Node(2) head.next.next = Node(3) head.next.next.next = Node(4) # Expect output : 2 --> 1 --> 4 --> 3 self.assertEqual([2, 1, 4, 3], convert(swap_pairs(head))) def test_rotate_right(self): # Given 1->2->3->4->5->NULL head = Node(1) head.next = Node(2) head.next.next = Node(3) head.next.next.next = Node(4) head.next.next.next.next = Node(5) # K = 2. Expect output: 4->5->1->2->3->NULL. self.assertEqual([4, 5, 1, 2, 3], convert(rotate_right(head, 2))) def test_is_cyclic(self): # create linked list => A -> B -> C -> D -> E -> C head = Node("A") head.next = Node("B") curr = head.next cyclic_node = Node("C") curr.next = cyclic_node curr = curr.next curr.next = Node("D") curr = curr.next curr.next = Node("E") curr = curr.next curr.next = cyclic_node self.assertTrue(is_cyclic(head)) # create linked list 1 -> 2 -> 3 -> 4 head = Node(1) curr = head for i in range(2, 6): curr.next = Node(i) curr = curr.next self.assertFalse(is_cyclic(head)) def test_merge_two_list(self): """ Input: head1:1->2->4, head2: 1->3->4 Output: 1->1->2->3->4->4 """ head1 = Node(1) head1.next = Node(2) head1.next.next = Node(4) head2 = Node(1) head2.next = Node(3) head2.next.next = Node(4) self.assertEqual([1, 1, 2, 3, 4, 4], convert(merge_two_list(head1, head2))) # Test recursive head1 = Node(1) head1.next = Node(2) head1.next.next = Node(4) head2 = Node(1) head2.next = Node(3) head2.next.next = Node(4) self.assertEqual( [1, 1, 2, 3, 4, 4], convert(merge_two_list_recur(head1, head2)) ) def test_is_palindrome(self): self.assertTrue(is_palindrome(self.l)) self.assertFalse(is_palindrome(self.l1)) def test_is_palindrome_stack(self): self.assertTrue(is_palindrome_stack(self.l)) self.assertFalse(is_palindrome_stack(self.l1)) def test_is_palindrome_dict(self): self.assertTrue(is_palindrome_dict(self.l)) self.assertFalse(is_palindrome_dict(self.l1)) def test_solution_0(self): self._init_random_list_nodes() result = copy_random_pointer_v1(self.random_list_node1) self._assert_is_a_copy(result) def test_solution_1(self): self._init_random_list_nodes() result = copy_random_pointer_v2(self.random_list_node1) self._assert_is_a_copy(result) def _assert_is_a_copy(self, result): self.assertEqual(5, result.next.next.next.next.label) self.assertEqual(4, result.next.next.next.label) self.assertEqual(3, result.next.next.label) self.assertEqual(2, result.next.label) self.assertEqual(1, result.label) self.assertEqual(3, result.next.next.next.next.random.label) self.assertIsNone(result.next.next.next.random) self.assertEqual(2, result.next.next.random.label) self.assertEqual(5, result.next.random.label) self.assertEqual(4, result.random.label) def _init_random_list_nodes(self): self.random_list_node1 = RandomListNode(1) random_list_node2 = RandomListNode(2) random_list_node3 = RandomListNode(3) random_list_node4 = RandomListNode(4) random_list_node5 = RandomListNode(5) self.random_list_node1.next, self.random_list_node1.random = ( random_list_node2, random_list_node4, ) random_list_node2.next, random_list_node2.random = ( random_list_node3, random_list_node5, ) random_list_node3.next, random_list_node3.random = ( random_list_node4, random_list_node2, ) random_list_node4.next = random_list_node5 random_list_node5.random = random_list_node3 if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_map.py ================================================ import unittest from algorithms.map import ( HashTable, ResizableHashTable, SeparateChainingHashTable, is_anagram, is_isomorphic, longest_palindromic_subsequence, word_pattern, ) class TestHashTable(unittest.TestCase): def test_one_entry(self): m = HashTable(10) m.put(1, "1") self.assertEqual("1", m.get(1)) def test_add_entry_bigger_than_table_size(self): m = HashTable(10) m.put(11, "1") self.assertEqual("1", m.get(11)) def test_get_none_if_key_missing_and_hash_collision(self): m = HashTable(10) m.put(1, "1") self.assertEqual(None, m.get(11)) def test_two_entries_with_same_hash(self): m = HashTable(10) m.put(1, "1") m.put(11, "11") self.assertEqual("1", m.get(1)) self.assertEqual("11", m.get(11)) def test_get_on_full_table_does_halts(self): # and does not search forever m = HashTable(10) for i in range(10, 20): m.put(i, i) self.assertEqual(None, m.get(1)) def test_delete_key(self): m = HashTable(10) for i in range(5): m.put(i, i**2) m.del_(1) self.assertEqual(None, m.get(1)) self.assertEqual(4, m.get(2)) def test_delete_key_and_reassign(self): m = HashTable(10) m.put(1, 1) del m[1] m.put(1, 2) self.assertEqual(2, m.get(1)) def test_assigning_to_full_table_throws_error(self): m = HashTable(3) m.put(1, 1) m.put(2, 2) m.put(3, 3) with self.assertRaises(ValueError): m.put(4, 4) def test_len_trivial(self): m = HashTable(10) self.assertEqual(0, len(m)) for i in range(10): m.put(i, i) self.assertEqual(i + 1, len(m)) def test_len_after_deletions(self): m = HashTable(10) m.put(1, 1) self.assertEqual(1, len(m)) m.del_(1) self.assertEqual(0, len(m)) m.put(11, 42) self.assertEqual(1, len(m)) def test_resizable_hash_table(self): m = ResizableHashTable() self.assertEqual(ResizableHashTable.MIN_SIZE, m.size) for i in range(ResizableHashTable.MIN_SIZE): m.put(i, "foo") self.assertEqual(ResizableHashTable.MIN_SIZE * 2, m.size) self.assertEqual("foo", m.get(1)) self.assertEqual("foo", m.get(3)) self.assertEqual("foo", m.get(ResizableHashTable.MIN_SIZE - 1)) def test_fill_up_the_limit(self): m = HashTable(10) for i in range(10): m.put(i, i**2) for i in range(10): self.assertEqual(i**2, m.get(i)) class TestSeparateChainingHashTable(unittest.TestCase): def test_one_entry(self): m = SeparateChainingHashTable(10) m.put(1, "1") self.assertEqual("1", m.get(1)) def test_two_entries_with_same_hash(self): m = SeparateChainingHashTable(10) m.put(1, "1") m.put(11, "11") self.assertEqual("1", m.get(1)) self.assertEqual("11", m.get(11)) def test_len_trivial(self): m = SeparateChainingHashTable(10) self.assertEqual(0, len(m)) for i in range(10): m.put(i, i) self.assertEqual(i + 1, len(m)) def test_len_after_deletions(self): m = SeparateChainingHashTable(10) m.put(1, 1) self.assertEqual(1, len(m)) m.del_(1) self.assertEqual(0, len(m)) m.put(11, 42) self.assertEqual(1, len(m)) def test_delete_key(self): m = SeparateChainingHashTable(10) for i in range(5): m.put(i, i**2) m.del_(1) self.assertEqual(None, m.get(1)) self.assertEqual(4, m.get(2)) def test_delete_key_and_reassign(self): m = SeparateChainingHashTable(10) m.put(1, 1) del m[1] m.put(1, 2) self.assertEqual(2, m.get(1)) def test_add_entry_bigger_than_table_size(self): m = SeparateChainingHashTable(10) m.put(11, "1") self.assertEqual("1", m.get(11)) def test_get_none_if_key_missing_and_hash_collision(self): m = SeparateChainingHashTable(10) m.put(1, "1") self.assertEqual(None, m.get(11)) class TestWordPattern(unittest.TestCase): def test_word_pattern(self): self.assertTrue(word_pattern("abba", "dog cat cat dog")) self.assertFalse(word_pattern("abba", "dog cat cat fish")) self.assertFalse(word_pattern("abba", "dog dog dog dog")) self.assertFalse(word_pattern("aaaa", "dog cat cat dog")) class TestIsSomorphic(unittest.TestCase): def test_is_isomorphic(self): self.assertTrue(is_isomorphic("egg", "add")) self.assertFalse(is_isomorphic("foo", "bar")) self.assertTrue(is_isomorphic("paper", "title")) class TestLongestPalindromicSubsequence(unittest.TestCase): def test_longest_palindromic_subsequence_is_correct(self): self.assertEqual(3, longest_palindromic_subsequence("BBABCBCAB")) self.assertEqual(4, longest_palindromic_subsequence("abbaeae")) self.assertEqual(7, longest_palindromic_subsequence("babbbababaa")) self.assertEqual(4, longest_palindromic_subsequence("daccandeeja")) def test_longest_palindromic_subsequence_is_incorrect(self): self.assertNotEqual(4, longest_palindromic_subsequence("BBABCBCAB")) self.assertNotEqual(5, longest_palindromic_subsequence("abbaeae")) self.assertNotEqual(2, longest_palindromic_subsequence("babbbababaa")) self.assertNotEqual(1, longest_palindromic_subsequence("daccandeeja")) class TestIsAnagram(unittest.TestCase): def test_is_anagram(self): self.assertTrue(is_anagram("anagram", "nagaram")) self.assertFalse(is_anagram("rat", "car")) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_math.py ================================================ import unittest import pytest from algorithms.math import ( base_to_int, chinese_remainder_theorem, combination, combination_memo, cosine_similarity, decimal_to_binary_ip, decrypt, diffie_hellman_key_exchange, encrypt, euler_totient, extended_gcd, factorial, factorial_recur, fft, find_next_square, find_next_square2, find_order, find_primitive_root, gcd, gcd_bit, gen_strobogrammatic, get_primes, hailstone, int_to_base, is_prime, is_strobogrammatic, is_strobogrammatic2, krishnamurthy_number, lcm, magic_number, modular_exponential, modular_inverse, num_digits, num_perfect_squares, power, power_recur, prime_check, pythagoras, strobogrammatic_in_range, trailing_zero, ) class TestPower(unittest.TestCase): """ Test for the file power.py Arguments: unittest {[type]} -- [description] """ def test_power(self): self.assertEqual(8, power(2, 3)) self.assertEqual(1, power(5, 0)) self.assertEqual(0, power(10, 3, 5)) self.assertEqual(280380, power(2265, 1664, 465465)) def test_power_recur(self): self.assertEqual(8, power_recur(2, 3)) self.assertEqual(1, power_recur(5, 0)) self.assertEqual(0, power_recur(10, 3, 5)) self.assertEqual(280380, power_recur(2265, 1664, 465465)) class TestBaseConversion(unittest.TestCase): """ Test for the file base_conversion.py Arguments: unittest {[type]} -- [description] """ def test_int_to_base(self): self.assertEqual("101", int_to_base(5, 2)) self.assertEqual("0", int_to_base(0, 2)) self.assertEqual("FF", int_to_base(255, 16)) def test_base_to_int(self): self.assertEqual(5, base_to_int("101", 2)) self.assertEqual(0, base_to_int("0", 2)) self.assertEqual(255, base_to_int("FF", 16)) class TestDecimalToBinaryIP(unittest.TestCase): """ Test for the file decimal_to_binary_ip.py Arguments: unittest {[type]} -- [description] """ def test_decimal_to_binary_ip(self): self.assertEqual( "00000000.00000000.00000000.00000000", decimal_to_binary_ip("0.0.0.0") ) self.assertEqual( "11111111.11111111.11111111.11111111", decimal_to_binary_ip("255.255.255.255"), ) self.assertEqual( "11000000.10101000.00000000.00000001", decimal_to_binary_ip("192.168.0.1") ) class TestEulerTotient(unittest.TestCase): """[summary] Test for the file euler_totient.py Arguments: unittest {[type]} -- [description] """ def test_euler_totient(self): self.assertEqual(4, euler_totient(8)) self.assertEqual(12, euler_totient(21)) self.assertEqual(311040, euler_totient(674614)) self.assertEqual(2354352, euler_totient(3435145)) class TestExtendedGcd(unittest.TestCase): """[summary] Test for the file extended_gcd.py Arguments: unittest {[type]} -- [description] """ def test_extended_gcd(self): self.assertEqual((0, 1, 2), extended_gcd(8, 2)) self.assertEqual((0, 1, 17), extended_gcd(13, 17)) class TestGcd(unittest.TestCase): """[summary] Test for the file gcd.py Arguments: unittest {[type]} -- [description] """ def test_gcd(self): self.assertEqual(4, gcd(8, 12)) self.assertEqual(1, gcd(13, 17)) def test_gcd_non_integer_input(self): with pytest.raises(ValueError, match=r"Input arguments are not integers"): gcd(1.0, 5) gcd(5, 6.7) gcd(33.8649, 6.12312312) def test_gcd_zero_input(self): with pytest.raises( ValueError, match=r"One or more input arguments equals zero" ): gcd(0, 12) gcd(12, 0) gcd(0, 0) def test_gcd_negative_input(self): self.assertEqual(1, gcd(-13, -17)) self.assertEqual(4, gcd(-8, 12)) self.assertEqual(8, gcd(24, -16)) def test_lcm(self): self.assertEqual(24, lcm(8, 12)) self.assertEqual(5767, lcm(73, 79)) def test_lcm_negative_numbers(self): self.assertEqual(24, lcm(-8, -12)) self.assertEqual(5767, lcm(73, -79)) self.assertEqual(1, lcm(-1, 1)) def test_lcm_zero_input(self): with pytest.raises( ValueError, match=r"One or more input arguments equals zero" ): lcm(0, 12) lcm(12, 0) lcm(0, 0) def test_trailing_zero(self): self.assertEqual(1, trailing_zero(34)) self.assertEqual(3, trailing_zero(40)) def test_gcd_bit(self): self.assertEqual(4, gcd_bit(8, 12)) self.assertEqual(1, gcd(13, 17)) class TestGenerateStroboGrammatic(unittest.TestCase): """[summary] Test for the file generate_strobogrammatic.py Arguments: unittest {[type]} -- [description] """ def test_gen_strobomatic(self): self.assertEqual(["88", "11", "96", "69"], gen_strobogrammatic(2)) def test_strobogrammatic_in_range(self): self.assertEqual(4, strobogrammatic_in_range("10", "100")) class TestIsStrobogrammatic(unittest.TestCase): """[summary] Test for the file is_strobogrammatic.py Arguments: unittest {[type]} -- [description] """ def test_is_strobogrammatic(self): self.assertTrue(is_strobogrammatic("69")) self.assertFalse(is_strobogrammatic("14")) def test_is_strobogrammatic2(self): self.assertTrue(is_strobogrammatic2("69")) self.assertFalse(is_strobogrammatic2("14")) class TestModularInverse(unittest.TestCase): """[summary] Test for the file modular_Exponential.py Arguments: unittest {[type]} -- [description] """ def test_modular_inverse(self): # checks if x * x_inv == 1 (mod m) self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 19) % 19) self.assertEqual(1, 53 * modular_inverse.modular_inverse(53, 91) % 91) self.assertEqual( 1, 2 * modular_inverse.modular_inverse(2, 1000000007) % 1000000007 ) self.assertRaises(ValueError, modular_inverse.modular_inverse, 2, 20) class TestModularExponential(unittest.TestCase): """[summary] Test for the file modular_Exponential.py Arguments: unittest {[type]} -- [description] """ def test_modular_exponential(self): self.assertEqual(1, modular_exponential(5, 117, 19)) self.assertEqual( pow(1243, 65321, 10**9 + 7), modular_exponential(1243, 65321, 10**9 + 7) ) self.assertEqual(1, modular_exponential(12, 0, 78)) self.assertRaises(ValueError, modular_exponential, 12, -2, 455) class TestNextPerfectSquare(unittest.TestCase): """[summary] Test for the file next_perfect_square.py Arguments: unittest {[type]} -- [description] """ def test_find_next_square(self): self.assertEqual(36, find_next_square(25)) self.assertEqual(1, find_next_square(0)) def test_find_next_square2(self): self.assertEqual(36, find_next_square2(25)) self.assertEqual(1, find_next_square2(0)) class TestPrimesSieveOfEratosthenes(unittest.TestCase): """[summary] Test for the file primes_sieve_of_eratosthenes.py Arguments: unittest {[type]} -- [description] """ def test_primes(self): self.assertListEqual([2, 3, 5, 7], get_primes(7)) self.assertRaises(ValueError, get_primes, -42) class TestPrimeTest(unittest.TestCase): """[summary] Test for the file prime_test.py Arguments: unittest {[type]} -- [description] """ def test_prime_test(self): """ checks all prime numbers between 2 up to 100. Between 2 up to 100 exists 25 prime numbers! """ counter = 0 for i in range(2, 101): if prime_check(i): counter += 1 self.assertEqual(25, counter) class TestPythagoras(unittest.TestCase): """[summary] Test for the file pythagoras.py Arguments: unittest {[type]} -- [description] """ def test_pythagoras(self): self.assertEqual("Hypotenuse = 3.605551275463989", pythagoras(3, 2, "?")) class TestRabinMiller(unittest.TestCase): """[summary] Test for the file rabin_miller.py Arguments: unittest {[type]} -- [description] """ def test_is_prime(self): self.assertTrue(is_prime(7, 2)) self.assertTrue(is_prime(13, 11)) self.assertFalse(is_prime(6, 2)) class TestRSA(unittest.TestCase): """[summary] Test for the file rsa.py Arguments: unittest {[type]} -- [description] """ def test_encrypt_decrypt(self): self.assertEqual(7, decrypt(encrypt(7, 23, 143), 47, 143)) # def test_key_generator(self): # this test takes a while! # for i in range(100): # print("step {0}".format(i)) # n, e, d = generate_key(26) # data = 2 # en = encrypt(data, e, n) # dec = decrypt(en, d, n) # self.assertEqual(data,dec) class TestCombination(unittest.TestCase): """[summary] Test for the file combination.py Arguments: unittest {[type]} -- [description] """ def test_combination(self): self.assertEqual(10, combination(5, 2)) self.assertEqual(252, combination(10, 5)) def test_combination_memo(self): self.assertEqual(10272278170, combination_memo(50, 10)) self.assertEqual(847660528, combination_memo(40, 10)) class TestFactorial(unittest.TestCase): """[summary] Test for the file factorial.py Arguments: unittest {[type]} -- [description] """ def test_factorial(self): self.assertEqual(1, factorial(0)) self.assertEqual(120, factorial(5)) self.assertEqual(3628800, factorial(10)) self.assertEqual(637816310, factorial(34521, 10**9 + 7)) self.assertRaises(ValueError, factorial, -42) self.assertRaises(ValueError, factorial, 42, -1) def test_factorial_recur(self): self.assertEqual(1, factorial_recur(0)) self.assertEqual(120, factorial_recur(5)) self.assertEqual(3628800, factorial_recur(10)) self.assertEqual(637816310, factorial_recur(34521, 10**9 + 7)) self.assertRaises(ValueError, factorial_recur, -42) self.assertRaises(ValueError, factorial_recur, 42, -1) class TestHailstone(unittest.TestCase): """[summary] Test for the file hailstone.py Arguments: unittest {[type]} -- [description] """ def test_hailstone(self): self.assertEqual([8, 4, 2, 1], hailstone.hailstone(8)) self.assertEqual([10, 5, 16, 8, 4, 2, 1], hailstone.hailstone(10)) class TestCosineSimilarity(unittest.TestCase): """[summary] Test for the file cosine_similarity.py Arguments: unittest {[type]} -- [description] """ def test_cosine_similarity(self): vec_a = [1, 1, 1] vec_b = [-1, -1, -1] vec_c = [1, 2, -1] self.assertAlmostEqual(cosine_similarity(vec_a, vec_a), 1) self.assertAlmostEqual(cosine_similarity(vec_a, vec_b), -1) self.assertAlmostEqual(cosine_similarity(vec_a, vec_c), 0.4714045208) class TestFindPrimitiveRoot(unittest.TestCase): """[summary] Test for the file find_primitive_root_simple.py Arguments: unittest {[type]} -- [description] """ def test_find_primitive_root_simple(self): self.assertListEqual([0], find_primitive_root(1)) self.assertListEqual([2, 3], find_primitive_root(5)) self.assertListEqual([], find_primitive_root(24)) self.assertListEqual( [2, 5, 13, 15, 17, 18, 19, 20, 22, 24, 32, 35], find_primitive_root(37) ) class TestFindOrder(unittest.TestCase): """[summary] Test for the file find_order_simple.py Arguments: unittest {[type]} -- [description] """ def test_find_order_simple(self): self.assertEqual(1, find_order(1, 1)) self.assertEqual(6, find_order(3, 7)) self.assertEqual(-1, find_order(128, 256)) self.assertEqual(352, find_order(3, 353)) class TestKrishnamurthyNumber(unittest.TestCase): """[summary] Test for the file krishnamurthy_number.py Arguments: unittest {[type]} -- [description] """ def test_krishnamurthy_number(self): self.assertFalse(krishnamurthy_number(0)) self.assertTrue(krishnamurthy_number(2)) self.assertTrue(krishnamurthy_number(1)) self.assertTrue(krishnamurthy_number(145)) self.assertTrue(krishnamurthy_number(40585)) class TestMagicNumber(unittest.TestCase): """[summary] Test for the file find_order_simple.py Arguments: unittest {[type]} -- [description] """ def test_magic_number(self): self.assertTrue(magic_number(50113)) self.assertTrue(magic_number(1234)) self.assertTrue(magic_number(100)) self.assertTrue(magic_number(199)) self.assertFalse(magic_number(2000)) self.assertFalse(magic_number(500000)) class TestDiffieHellmanKeyExchange(unittest.TestCase): """[summary] Test for the file diffie_hellman_key_exchange.py Arguments: unittest {[type]} -- [description] """ def test_find_order_simple(self): self.assertFalse(diffie_hellman_key_exchange(3, 6)) self.assertTrue(diffie_hellman_key_exchange(3, 353)) self.assertFalse(diffie_hellman_key_exchange(5, 211)) self.assertTrue(diffie_hellman_key_exchange(11, 971)) class TestNumberOfDigits(unittest.TestCase): """[summary] Test for the file num_digits.py Arguments: unittest {[type]} -- [description] """ def test_num_digits(self): self.assertEqual(2, num_digits(12)) self.assertEqual(5, num_digits(99999)) self.assertEqual(1, num_digits(8)) self.assertEqual(1, num_digits(0)) self.assertEqual(1, num_digits(-5)) self.assertEqual(3, num_digits(-254)) class TestNumberOfPerfectSquares(unittest.TestCase): """[summary] Test for the file num_perfect_squares.py Arguments: unittest {[type]} -- [description] """ def test_num_perfect_squares(self): self.assertEqual(4, num_perfect_squares(31)) self.assertEqual(3, num_perfect_squares(12)) self.assertEqual(2, num_perfect_squares(13)) self.assertEqual(2, num_perfect_squares(10)) self.assertEqual(4, num_perfect_squares(1500)) self.assertEqual(2, num_perfect_squares(1548524521)) self.assertEqual(3, num_perfect_squares(9999999993)) self.assertEqual(1, num_perfect_squares(9)) class TestChineseRemainderSolver(unittest.TestCase): def test_k_three(self): # Example which should give the answer 143 # which is the smallest possible x that # solves the system of equations num = [3, 7, 10] rem = [2, 3, 3] self.assertEqual( chinese_remainder_theorem.solve_chinese_remainder(num, rem), 143 ) def test_k_five(self): # Example which should give the answer 3383 # which is the smallest possible x that # solves the system of equations num = [3, 5, 7, 11, 26] rem = [2, 3, 2, 6, 3] self.assertEqual( chinese_remainder_theorem.solve_chinese_remainder(num, rem), 3383 ) def test_exception_non_coprime(self): # There should be an exception when all # numbers in num are not pairwise coprime num = [3, 7, 10, 14] rem = [2, 3, 3, 1] with self.assertRaises(Exception): # noqa: B017 chinese_remainder_theorem.solve_chinese_remainder(num, rem) def test_empty_lists(self): num = [] rem = [] with self.assertRaises(Exception): # noqa: B017 chinese_remainder_theorem.solve_chinese_remainder(num, rem) class TestFFT(unittest.TestCase): """[summary] Test for the file fft.py Arguments: unittest {[type]} -- [description] """ def test_real_numbers(self): x = [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0] y = [4.000, 2.613, 0.000, 1.082, 0.000, 1.082, 0.000, 2.613] # abs(complex) returns the magnitude result = [float(f"{abs(f):.3f}") for f in fft.fft(x)] self.assertEqual(result, y) def test_all_zero(self): x = [0.0, 0.0, 0.0, 0.0] y = [0.0, 0.0, 0.0, 0.0] result = [float(f"{abs(f):.1f}") for f in fft.fft(x)] self.assertEqual(result, y) def test_all_ones(self): x = [1.0, 1.0, 1.0, 1.0] y = [4.0, 0.0, 0.0, 0.0] result = [float(f"{abs(f):.1f}") for f in fft.fft(x)] self.assertEqual(result, y) def test_complex_numbers(self): x = [2.0 + 2j, 1.0 + 3j, 3.0 + 1j, 2.0 + 2j] real = [8.0, 0.0, 2.0, -2.0] imag = [8.0, 2.0, -2.0, 0.0] real_result = [float(f"{f.real:.1f}") for f in fft.fft(x)] imag_result = [float(f"{f.imag:.1f}") for f in fft.fft(x)] self.assertEqual(real, real_result) self.assertEqual(imag, imag_result) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_matrix.py ================================================ import unittest from algorithms.matrix import ( bomb_enemy, cholesky_matrix_decomposition, copy_transform, crout_matrix_decomposition, matrix_exponentiation, matrix_inversion, multiply, rotate_image, sort_matrix_diagonally, sparse_dot_vector, spiral_traversal, sudoku_validator, sum_sub_squares, ) class TestBombEnemy(unittest.TestCase): def test_3x4(self): grid1 = [["0", "E", "0", "0"], ["E", "0", "W", "E"], ["0", "E", "0", "0"]] self.assertEqual(3, bomb_enemy.max_killed_enemies(grid1)) grid1 = [ ["0", "E", "0", "E"], ["E", "E", "E", "0"], ["E", "0", "W", "E"], ["0", "E", "0", "0"], ] grid2 = [ ["0", "0", "0", "E"], ["E", "0", "0", "0"], ["E", "0", "W", "E"], ["0", "E", "0", "0"], ] self.assertEqual(5, bomb_enemy.max_killed_enemies(grid1)) self.assertEqual(3, bomb_enemy.max_killed_enemies(grid2)) class TestCopyTransform(unittest.TestCase): """[summary] Test for the file copy_transform.py Arguments: unittest {[type]} -- [description] """ def test_copy_transform(self): self.assertEqual( copy_transform.rotate_clockwise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[7, 4, 1], [8, 5, 2], [9, 6, 3]], ) self.assertEqual( copy_transform.rotate_counterclockwise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[3, 6, 9], [2, 5, 8], [1, 4, 7]], ) self.assertEqual( copy_transform.top_left_invert([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[1, 4, 7], [2, 5, 8], [3, 6, 9]], ) self.assertEqual( copy_transform.bottom_left_invert([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[9, 6, 3], [8, 5, 2], [7, 4, 1]], ) class TestCroutMatrixDecomposition(unittest.TestCase): """[summary] Test for the file crout_matrix_decomposition.py Arguments: unittest {[type]} -- [description] """ def test_crout_matrix_decomposition(self): self.assertEqual( ([[9.0, 0.0], [7.0, 0.0]], [[1.0, 1.0], [0.0, 1.0]]), crout_matrix_decomposition.crout_matrix_decomposition([[9, 9], [7, 7]]), ) self.assertEqual( ( [[1.0, 0.0, 0.0], [3.0, -2.0, 0.0], [6.0, -5.0, 0.0]], [[1.0, 2.0, 3.0], [0.0, 1.0, 2.0], [0.0, 0.0, 1.0]], ), crout_matrix_decomposition.crout_matrix_decomposition( [[1, 2, 3], [3, 4, 5], [6, 7, 8]] ), ) self.assertEqual( ( [ [2.0, 0, 0, 0], [4.0, -1.0, 0, 0], [6.0, -2.0, 2.0, 0], [8.0, -3.0, 3.0, 0.0], ], [ [1.0, 0.5, 1.5, 0.5], [0, 1.0, 2.0, 1.0], [0, 0, 1.0, 0.0], [0, 0, 0, 1.0], ], ), crout_matrix_decomposition.crout_matrix_decomposition( [[2, 1, 3, 1], [4, 1, 4, 1], [6, 1, 7, 1], [8, 1, 9, 1]] ), ) class TestCholeskyMatrixDecomposition(unittest.TestCase): """[summary] Test for the file cholesky_matrix_decomposition.py Arguments: unittest {[type]} -- [description] """ def test_cholesky_matrix_decomposition(self): self.assertEqual( [[2.0, 0.0, 0.0], [6.0, 1.0, 0.0], [-8.0, 5.0, 3.0]], cholesky_matrix_decomposition.cholesky_decomposition( [[4, 12, -16], [12, 37, -43], [-16, -43, 98]] ), ) self.assertEqual( None, cholesky_matrix_decomposition.cholesky_decomposition( [[4, 12, -8], [12, 4, -43], [-16, -1, 32]] ), ) self.assertEqual( None, cholesky_matrix_decomposition.cholesky_decomposition( [[4, 12, -16], [12, 37, -43], [-16, -43, 98], [1, 2, 3]] ), ) # example taken from https://ece.uwaterloo.ca/~dwharder/NumericalAnalysis/04LinearAlgebra/cholesky/ self.assertEqual( [ [2.23606797749979, 0.0, 0.0, 0.0], [0.5366563145999494, 2.389979079406345, 0.0, 0.0], [0.13416407864998736, -0.19749126846635062, 2.818332343581848, 0.0], [ -0.2683281572999747, 0.43682390737048743, 0.64657701271919, 3.052723872310221, ], ], cholesky_matrix_decomposition.cholesky_decomposition( [ [5, 1.2, 0.3, -0.6], [1.2, 6, -0.4, 0.9], [0.3, -0.4, 8, 1.7], [-0.6, 0.9, 1.7, 10], ] ), ) class TestInversion(unittest.TestCase): """[summary] Test for the file matrix_inversion.py Arguments: unittest {[type]} -- [description] """ def test_inversion(self): from fractions import Fraction m1 = [[1, 1], [1, 2]] self.assertEqual(matrix_inversion.invert_matrix(m1), [[2, -1], [-1, 1]]) m2 = [[1, 2], [3, 4, 5]] self.assertEqual(matrix_inversion.invert_matrix(m2), [[-1]]) m3 = [[1, 1, 1, 1], [2, 2, 2, 2]] self.assertEqual(matrix_inversion.invert_matrix(m3), [[-2]]) m4 = [[1]] self.assertEqual(matrix_inversion.invert_matrix(m4), [[-3]]) m5 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] self.assertEqual(matrix_inversion.invert_matrix(m5), [[-4]]) m6 = [[3, 5, 1], [2, 5, 0], [1, 9, 8]] self.assertEqual( matrix_inversion.invert_matrix(m6), [ [Fraction(40, 53), Fraction(-31, 53), Fraction(-5, 53)], [Fraction(-16, 53), Fraction(23, 53), Fraction(2, 53)], [Fraction(13, 53), Fraction(-22, 53), Fraction(5, 53)], ], ) class TestMatrixExponentiation(unittest.TestCase): """[summary] Test for the file matrix_exponentiation.py Arguments: unittest {[type]} -- [description] """ def test_matrix_exponentiation(self): mat = [[1, 0, 2], [2, 1, 0], [0, 2, 1]] self.assertEqual( matrix_exponentiation.matrix_exponentiation(mat, 0), [[1, 0, 0], [0, 1, 0], [0, 0, 1]], ) self.assertEqual( matrix_exponentiation.matrix_exponentiation(mat, 1), [[1, 0, 2], [2, 1, 0], [0, 2, 1]], ) self.assertEqual( matrix_exponentiation.matrix_exponentiation(mat, 2), [[1, 4, 4], [4, 1, 4], [4, 4, 1]], ) self.assertEqual( matrix_exponentiation.matrix_exponentiation(mat, 5), [[81, 72, 90], [90, 81, 72], [72, 90, 81]], ) class TestMultiply(unittest.TestCase): """[summary] Test for the file multiply.py Arguments: unittest {[type]} -- [description] """ def test_multiply(self): self.assertEqual( multiply.multiply([[1, 2, 3], [2, 1, 1]], [[1], [2], [3]]), [[14], [7]] ) class TestRotateImage(unittest.TestCase): """[summary] Test for the file rotate_image.py Arguments: unittest {[type]} -- [description] """ def test_rotate_image(self): self.assertEqual( rotate_image.rotate([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[7, 4, 1], [8, 5, 2], [9, 6, 3]], ) class TestSparseDotVector(unittest.TestCase): """[summary] Test for the file sparse_dot_vector.py Arguments: unittest {[type]} -- [description] """ def test_sparse_dot_vector(self): self.assertEqual( sparse_dot_vector.dot_product( sparse_dot_vector.vector_to_index_value_list([1.0, 2.0, 3.0]), sparse_dot_vector.vector_to_index_value_list([0.0, 2.0, 2.0]), ), 10, ) class TestSpiralTraversal(unittest.TestCase): """[summary] Test for the file spiral_traversal.py Arguments: unittest {[type]} -- [description] """ def test_spiral_traversal(self): self.assertEqual( spiral_traversal.spiral_traversal([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [1, 2, 3, 6, 9, 8, 7, 4, 5], ) class TestSudokuValidator(unittest.TestCase): """[summary] Test for the file sudoku_validator.py Arguments: unittest {[type]} -- [description] """ def test_sudoku_validator(self): self.assertTrue( sudoku_validator.valid_solution( [ [5, 3, 4, 6, 7, 8, 9, 1, 2], [6, 7, 2, 1, 9, 5, 3, 4, 8], [1, 9, 8, 3, 4, 2, 5, 6, 7], [8, 5, 9, 7, 6, 1, 4, 2, 3], [4, 2, 6, 8, 5, 3, 7, 9, 1], [7, 1, 3, 9, 2, 4, 8, 5, 6], [9, 6, 1, 5, 3, 7, 2, 8, 4], [2, 8, 7, 4, 1, 9, 6, 3, 5], [3, 4, 5, 2, 8, 6, 1, 7, 9], ] ) ) self.assertTrue( sudoku_validator.valid_solution_hashtable( [ [5, 3, 4, 6, 7, 8, 9, 1, 2], [6, 7, 2, 1, 9, 5, 3, 4, 8], [1, 9, 8, 3, 4, 2, 5, 6, 7], [8, 5, 9, 7, 6, 1, 4, 2, 3], [4, 2, 6, 8, 5, 3, 7, 9, 1], [7, 1, 3, 9, 2, 4, 8, 5, 6], [9, 6, 1, 5, 3, 7, 2, 8, 4], [2, 8, 7, 4, 1, 9, 6, 3, 5], [3, 4, 5, 2, 8, 6, 1, 7, 9], ] ) ) self.assertTrue( sudoku_validator.valid_solution_set( [ [5, 3, 4, 6, 7, 8, 9, 1, 2], [6, 7, 2, 1, 9, 5, 3, 4, 8], [1, 9, 8, 3, 4, 2, 5, 6, 7], [8, 5, 9, 7, 6, 1, 4, 2, 3], [4, 2, 6, 8, 5, 3, 7, 9, 1], [7, 1, 3, 9, 2, 4, 8, 5, 6], [9, 6, 1, 5, 3, 7, 2, 8, 4], [2, 8, 7, 4, 1, 9, 6, 3, 5], [3, 4, 5, 2, 8, 6, 1, 7, 9], ] ) ) self.assertFalse( sudoku_validator.valid_solution( [ [5, 3, 4, 6, 7, 8, 9, 1, 2], [6, 7, 2, 1, 9, 0, 3, 4, 9], [1, 0, 0, 3, 4, 2, 5, 6, 0], [8, 5, 9, 7, 6, 1, 0, 2, 0], [4, 2, 6, 8, 5, 3, 7, 9, 1], [7, 1, 3, 9, 2, 4, 8, 5, 6], [9, 0, 1, 5, 3, 7, 2, 1, 4], [2, 8, 7, 4, 1, 9, 6, 3, 5], [3, 0, 0, 4, 8, 1, 1, 7, 9], ] ) ) self.assertFalse( sudoku_validator.valid_solution_hashtable( [ [5, 3, 4, 6, 7, 8, 9, 1, 2], [6, 7, 2, 1, 9, 0, 3, 4, 9], [1, 0, 0, 3, 4, 2, 5, 6, 0], [8, 5, 9, 7, 6, 1, 0, 2, 0], [4, 2, 6, 8, 5, 3, 7, 9, 1], [7, 1, 3, 9, 2, 4, 8, 5, 6], [9, 0, 1, 5, 3, 7, 2, 1, 4], [2, 8, 7, 4, 1, 9, 6, 3, 5], [3, 0, 0, 4, 8, 1, 1, 7, 9], ] ) ) self.assertFalse( sudoku_validator.valid_solution_set( [ [5, 3, 4, 6, 7, 8, 9, 1, 2], [6, 7, 2, 1, 9, 0, 3, 4, 9], [1, 0, 0, 3, 4, 2, 5, 6, 0], [8, 5, 9, 7, 6, 1, 0, 2, 0], [4, 2, 6, 8, 5, 3, 7, 9, 1], [7, 1, 3, 9, 2, 4, 8, 5, 6], [9, 0, 1, 5, 3, 7, 2, 1, 4], [2, 8, 7, 4, 1, 9, 6, 3, 5], [3, 0, 0, 4, 8, 1, 1, 7, 9], ] ) ) class TestSumSubSquares(unittest.TestCase): """[summary] Test for the file sum_sub_squares.py Arguments: unittest {[type]} -- [description] """ def test_sum_sub_squares(self): mat = [ [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4], [5, 5, 5, 5, 5], ] self.assertEqual( sum_sub_squares.sum_sub_squares(mat, 3), [[18, 18, 18], [27, 27, 27], [36, 36, 36]], ) class TestSortMatrixDiagonally(unittest.TestCase): def test_sort_diagonally(self): mat = [[3, 3, 1, 1], [2, 2, 1, 2], [1, 1, 1, 2]] self.assertEqual( sort_matrix_diagonally.sort_diagonally(mat), [[1, 1, 1, 1], [1, 2, 2, 2], [1, 2, 3, 3]], ) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_monomial.py ================================================ import math import unittest from fractions import Fraction from algorithms.math.polynomial import Monomial class TestSuite(unittest.TestCase): def setUp(self): self.m1 = Monomial({}) self.m2 = Monomial({1: 1}, 2) self.m3 = Monomial({1: 2, 2: -1}, 1.5) self.m4 = Monomial({1: 1, 2: 2, 3: -2}, 3) self.m5 = Monomial({2: 1, 3: 0}, Fraction(2, 3)) self.m6 = Monomial({1: 0, 2: 0, 3: 0}, -2.27) self.m7 = Monomial({1: 2, 7: 2}, -math.pi) self.m8 = Monomial({150: 5, 170: 2, 10000: 3}, 0) self.m9 = 2 self.m10 = math.pi self.m11 = Fraction(3, 8) self.m12 = 0 self.m13 = Monomial({1: 1}, -2) self.m14 = Monomial({1: 2}, 3) self.m15 = Monomial({1: 1}, 3) self.m16 = Monomial({1: 2, 7: 2}, math.pi) self.m17 = Monomial({1: -1}) def test_monomial_addition(self): # Monomials with different underlying variables or # even different power of those variables must not be added! self.assertRaises(ValueError, lambda x, y: x + y, self.m1, self.m2) self.assertRaises(ValueError, lambda x, y: x + y, self.m2, self.m3) self.assertRaises(ValueError, lambda x, y: x + y, self.m2, self.m14) # Additive inverses of each other should produce the zero monomial. self.assertEqual(self.m13 + self.m2, self.m1) # Zero monomial + Zero monomial = Zero monomial self.assertEqual(self.m1 + self.m1, self.m1) # Coefficient float. self.assertEqual(self.m7 + self.m7, Monomial({1: 2, 7: 2}, -2 * math.pi)) # Coefficient 0 so should equal the zero monomial. self.assertEqual(self.m8, self.m1) # The constant term cannot be added to any monomial # that has any variables. self.assertRaises(ValueError, lambda x, y: x + y, self.m2, self.m9) # Any literal cannot be added to a Monomial. However, a monomial # can be added to any int, float, Fraction, or Monomial. # So 2 + Monomial is raises TypeError but Monomial + 2 may work fine! self.assertRaises(TypeError, lambda x, y: x + y, self.m9, self.m2) # Any constant added to a zero monomial produces # a monomial. self.assertEqual(self.m1 + self.m9, Monomial({}, 2)) self.assertEqual(self.m1 + self.m12, Monomial({}, 0)) return def test_monomial_subtraction(self): # Monomials with different underlying variables or # even different power of those variables must not be subtracted! self.assertRaises(ValueError, lambda x, y: x - y, self.m1, self.m2) self.assertRaises(ValueError, lambda x, y: x - y, self.m2, self.m3) self.assertRaises(ValueError, lambda x, y: x - y, self.m2, self.m14) # Additive inverses of each other should produce the zero monomial. self.assertEqual(self.m2 - self.m2, self.m1) self.assertEqual(self.m2 - self.m2, Monomial({}, 0)) # Zero monomial - Zero monomial = Zero monomial self.assertEqual(self.m1 - self.m1, self.m1) # Coefficient int. self.assertEqual(self.m2 - self.m15, Monomial({1: 1}, -1)) # Coefficient float. self.assertEqual(self.m16 - self.m7, Monomial({1: 2, 7: 2}, 2 * math.pi)) # The constant term cannot be added to any monomial # that has any variables. self.assertRaises(ValueError, lambda x, y: x - y, self.m2, self.m9) # Any literal cannot be added to a Monomial. However, a monomial # can be added to any int, float, Fraction, or Monomial. # So 2 + Monomial is raises TypeError but Monomial + 2 may work fine! self.assertRaises(TypeError, lambda x, y: x - y, self.m9, self.m2) # Any constant added to a zero monomial produces # a monomial. self.assertEqual(self.m1 - self.m9, Monomial({}, -2)) self.assertEqual(self.m1 - self.m12, Monomial({}, 0)) return def test_monomial_multiplication(self): # Usual multiplication. # The positive and negative powers of the same variable # should cancel out. self.assertEqual(self.m2 * self.m13, Monomial({1: 2}, -4)) self.assertEqual(self.m2 * self.m17, Monomial({}, 2)) # A coefficient of zero should make the product zero. # Zero monomial * any int, float, Fraction, or Monomial = Zero monomial self.assertEqual(self.m8 * self.m5, self.m1) self.assertEqual(self.m1 * self.m2, self.m1) # Test usual float multiplication. self.assertEqual( self.m7 * self.m3, Monomial({1: 4, 2: -1, 7: 2}, -1.5 * math.pi) ) return def test_monomial_inverse(self): # The Zero monomial is not invertible. self.assertRaises(ValueError, lambda x: x.inverse(), self.m1) self.assertRaises(ValueError, lambda x: x.inverse(), self.m8) self.assertRaises(ValueError, lambda x: x.inverse(), Monomial({}, self.m12)) # Check some inverses. self.assertEqual(self.m7.inverse(), Monomial({1: -2, 7: -2}, -1 / math.pi)) # Doesn't matter if the coefficient is Fraction or float. # Both should be treated as same. self.assertEqual(self.m5.inverse(), Monomial({2: -1}, Fraction(3, 2))) self.assertEqual(self.m5.inverse(), Monomial({2: -1}, 1.5)) # Should work fine without variables too! self.assertTrue(self.m6.inverse(), Monomial({}, Fraction(-100, 227))) self.assertEqual(self.m6.inverse(), Monomial({}, -1 / 2.27)) return def test_monomial_division(self): # Any monomial divided by the Zero Monomial should raise a ValueError. self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m1) self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m8) self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m12) # Test some usual cases. self.assertEqual(self.m7 / self.m3, Monomial({2: 1, 7: 2}, -2 * math.pi / 3)) self.assertEqual(self.m14 / self.m13, Monomial({1: 1}) * Fraction(-3, 2)) return def test_monomial_substitution(self): # Test with int. self.assertAlmostEqual(self.m7.substitute(2), -16 * math.pi, delta=1e-9) # Test with float. self.assertAlmostEqual(self.m7.substitute(1.5), (1.5**4) * -math.pi, delta=1e-9) # Test with Fraction. self.assertAlmostEqual( self.m7.substitute(Fraction(-1, 2)), (Fraction(-1, 2) ** 4) * -math.pi, delta=1e-9, ) # Test with a complete substitution map. self.assertAlmostEqual( self.m7.substitute({1: 3, 7: 0}), (3**2) * (0**2) * -math.pi, delta=1e-9 ) # Test with a more than complete substitution map. self.assertAlmostEqual( self.m7.substitute({1: 3, 7: 0, 2: 2}), (3**2) * (0**2) * -math.pi, delta=1e-9, ) # Should raise a ValueError if not enough variables are supplied! self.assertRaises( ValueError, lambda x, y: x.substitute(y), self.m7, {1: 3, 2: 2} ) self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, {2: 2}) # The zero monomial always gives zero upon substitution. self.assertEqual(self.m8.substitute(2), 0) self.assertEqual(self.m8.substitute({1231: 2, 1: 2}), 0) return def test_monomial_all_variables(self): # Any variable with zero power should not exist in the set # of variables. self.assertEqual(self.m5.all_variables(), {2}) self.assertEqual(self.m6.all_variables(), set()) # The zero monomial should output empty set. self.assertEqual(self.m8.all_variables(), set()) return def test_monomial_clone(self): # A monomial should produce its copy # with same underlying variable dictionary # and same coefficient. self.assertEqual(self.m3, self.m3.clone()) # The zero monomial is identified and # always clones to itself. self.assertEqual(self.m1, self.m8.clone()) self.assertEqual(self.m1, self.m1.clone()) self.assertEqual(self.m8, self.m1.clone()) self.assertEqual(self.m8, self.m8.clone()) return if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_polynomial.py ================================================ import math import unittest from fractions import Fraction from algorithms.math.polynomial import Monomial, Polynomial class TestSuite(unittest.TestCase): def setUp(self): self.p0 = Polynomial([Monomial({})]) self.p1 = Polynomial([Monomial({}), Monomial({})]) self.p2 = Polynomial([Monomial({1: 1}, 2)]) self.p3 = Polynomial([Monomial({1: 1}, 2), Monomial({1: 2, 2: -1}, 1.5)]) self.p4 = Polynomial( [ Monomial({2: 1, 3: 0}, Fraction(2, 3)), Monomial({1: -1, 3: 2}, math.pi), Monomial({1: -1, 3: 2}, 1), ] ) self.p5 = Polynomial( [ Monomial({150: 5, 170: 2, 10000: 3}, 0), Monomial({1: -1, 3: 2}, 1), ] ) self.p6 = Polynomial( [2, -3, Fraction(1, 7), 2**math.pi, Monomial({2: 3, 3: 1}, 1.25)] ) self.p7 = Polynomial([Monomial({1: 1}, -2), Monomial({1: 2, 2: -1}, -1.5)]) self.m1 = Monomial({1: 2, 2: 3}, -1) return def test_polynomial_addition(self): # The zero polynomials should add up to # itselves only. self.assertEqual(self.p0 + self.p1, self.p0) self.assertEqual(self.p0 + self.p1, self.p1) # Additive inverses should add up to the # zero polynomial. self.assertEqual(self.p3 + self.p7, self.p0) self.assertEqual(self.p3 + self.p7, self.p1) # Like terms should combine. # The order of monomials should not matter. self.assertEqual( self.p2 + self.p3, Polynomial([Monomial({1: 1}, 4), Monomial({1: 2, 2: -1}, 1.5)]), ) self.assertEqual( self.p2 + self.p3, Polynomial( [ Monomial({1: 2, 2: -1}, 1.5), Monomial({1: 1}, 4), ] ), ) # Another typical computation. self.assertEqual( self.p5 + self.p6, Polynomial( [ Monomial({}, 7.96783496993343), Monomial({2: 3, 3: 1}, 1.25), Monomial({1: -1, 3: 2}), ] ), ) return def test_polynomial_subtraction(self): self.assertEqual(self.p3 - self.p2, Polynomial([Monomial({1: 2, 2: -1}, 1.5)])) self.assertEqual(self.p3 - self.p3, Polynomial([])) self.assertEqual(self.p2 - self.p3, Polynomial([Monomial({1: 2, 2: -1}, -1.5)])) pass def test_polynomial_multiplication(self): self.assertEqual(self.p0 * self.p2, Polynomial([])) self.assertEqual(self.p1 * self.p2, Polynomial([])) self.assertEqual( self.p2 * self.p3, Polynomial([Monomial({1: 2}, 4), Monomial({1: 3, 2: -1}, Fraction(3, 1))]), ) return def test_polynomial_variables(self): # The zero polynomial has no variables. self.assertEqual(self.p0.variables(), set()) self.assertEqual(self.p1.variables(), set()) # The total variables are the union of the variables # from the monomials. self.assertEqual(self.p4.variables(), {1, 2, 3}) # The monomials with coefficient 0 should be dropped. self.assertEqual(self.p5.variables(), {1, 3}) return def test_polynomial_subs(self): # Anything substitued in the zero polynomial # should evaluate to 0. self.assertEqual(self.p1.subs(2), 0) self.assertEqual(self.p0.subs(-101231), 0) # Should raise a ValueError if not enough variables are supplied. self.assertRaises(ValueError, lambda x, y: x.subs(y), self.p4, {1: 3, 2: 2}) self.assertRaises(ValueError, lambda x, y: x.subs(y), self.p4, {}) # Should work fine if a complete subsitution map is provided. self.assertAlmostEqual( self.p4.subs({1: 1, 2: 1, 3: 1}), (1 + math.pi + Fraction(2, 3)), delta=1e-9 ) # Should work fine if more than enough substitutions are provided. self.assertAlmostEqual( self.p4.subs({1: 1, 2: 1, 3: 1, 4: 1}), (1 + math.pi + Fraction(2, 3)), delta=1e-9, ) return def test_polynomial_clone(self): # The zero polynomial always clones to itself. self.assertEqual(self.p0.clone(), self.p0) self.assertEqual(self.p1.clone(), self.p0) self.assertEqual(self.p0.clone(), self.p1) self.assertEqual(self.p1.clone(), self.p1) # The polynomial should clone nicely. self.assertEqual(self.p4.clone(), self.p4) # The monomial with a zero coefficient should be dropped # in the clone. self.assertEqual(self.p5.clone(), Polynomial([Monomial({1: -1, 3: 2}, 1)])) return def test_polynomial_long_division(self): """ Test polynomial long division """ # Dividend: 4a_1^3 + 3a_1^2 - 2a_1 + 5 dividend = Polynomial( [ Monomial({1: 3}, 4), # 4(a_1)^3 Monomial({1: 2}, 3), # 3(a_1)^2 Monomial({1: 1}, -2), # -2(a_1) Monomial({}, 5), # +5 ] ) # Divisor: 2a_1 - 1 divisor = Polynomial( [ Monomial({1: 1}, 2), # 2(a_1) Monomial({}, -1), # -1 ] ) # Expected Quotient: 2a_1^2 + (5/2)a_1 + 1/4 expected_quotient = Polynomial( [ Monomial({1: 2}, 2), # 2(a_1)^2 Monomial({1: 1}, Fraction(5, 2)), # (5/2)(a_1) Monomial({}, Fraction(1, 4)), # +1/4 ] ) # Expected Remainder: 21/4 expected_remainder = Polynomial( [ Monomial({}, Fraction(21, 4)) # 21/4 ] ) quotient_long_div, remainder_long_div = dividend.poly_long_division(divisor) quotient_truediv = ( dividend / divisor ) # Calls __truediv__, which returns only the quotient # Check if quotient from poly_long_division matches expected self.assertEqual(quotient_long_div, expected_quotient) # Check if remainder from poly_long_division matches expected self.assertEqual(remainder_long_div, expected_remainder) # Check if quotient from __truediv__ matches quotient from poly_long_division self.assertEqual(quotient_truediv, quotient_long_div) return ================================================ FILE: tests/test_queue.py ================================================ import unittest from algorithms.queue import ( ArrayQueue, LinkedListQueue, PriorityQueue, max_sliding_window, reconstruct_queue, ) class TestQueue(unittest.TestCase): """ Test suite for the Queue data structures. """ def test_array_queue(self): queue = ArrayQueue() queue.enqueue(1) queue.enqueue(2) queue.enqueue(3) # test __iter__() it = iter(queue) self.assertEqual(1, next(it)) self.assertEqual(2, next(it)) self.assertEqual(3, next(it)) self.assertRaises(StopIteration, next, it) # test __len__() self.assertEqual(3, len(queue)) # test is_empty() self.assertFalse(queue.is_empty()) # test peek() self.assertEqual(1, queue.peek()) # test dequeue() self.assertEqual(1, queue.dequeue()) self.assertEqual(2, queue.dequeue()) self.assertEqual(3, queue.dequeue()) self.assertTrue(queue.is_empty()) def test_linked_list_queue(self): queue = LinkedListQueue() queue.enqueue(1) queue.enqueue(2) queue.enqueue(3) # test __iter__() it = iter(queue) self.assertEqual(1, next(it)) self.assertEqual(2, next(it)) self.assertEqual(3, next(it)) self.assertRaises(StopIteration, next, it) # test __len__() self.assertEqual(3, len(queue)) # test is_empty() self.assertFalse(queue.is_empty()) # test peek() self.assertEqual(1, queue.peek()) # test dequeue() self.assertEqual(1, queue.dequeue()) self.assertEqual(2, queue.dequeue()) self.assertEqual(3, queue.dequeue()) self.assertTrue(queue.is_empty()) class TestSuite(unittest.TestCase): def test_max_sliding_window(self): array = [1, 3, -1, -3, 5, 3, 6, 7] self.assertEqual(max_sliding_window(array, k=5), [5, 5, 6, 7]) self.assertEqual(max_sliding_window(array, k=3), [3, 3, 5, 5, 6, 7]) self.assertEqual(max_sliding_window(array, k=7), [6, 7]) array = [8, 5, 10, 7, 9, 4, 15, 12, 90, 13] self.assertEqual(max_sliding_window(array, k=4), [10, 10, 10, 15, 15, 90, 90]) self.assertEqual(max_sliding_window(array, k=7), [15, 15, 90, 90]) self.assertEqual( max_sliding_window(array, k=2), [8, 10, 10, 9, 9, 15, 15, 90, 90] ) def test_reconstruct_queue(self): self.assertEqual( [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]], reconstruct_queue([[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]), ) class TestPriorityQueue(unittest.TestCase): """Test suite for the PriorityQueue data structures.""" def test_priority_queue(self): queue = PriorityQueue([3, 4, 1, 6]) self.assertEqual(4, queue.size()) self.assertEqual(1, queue.pop()) self.assertEqual(3, queue.size()) queue.push(2) self.assertEqual(4, queue.size()) self.assertEqual(2, queue.pop()) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_searching.py ================================================ import unittest from algorithms.searching import ( binary_search, binary_search_recur, find_min_rotate, find_min_rotate_recur, first_occurrence, interpolation_search, jump_search, last_occurrence, linear_search, next_greatest_letter, next_greatest_letter_v1, next_greatest_letter_v2, search_insert, search_range, search_rotate, search_rotate_recur, ternary_search, two_sum, two_sum1, two_sum2, ) class TestSuite(unittest.TestCase): def test_first_occurrence(self): def helper(array, query): idx = array.index(query) if query in array else -1 return idx array = [1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 6, 6] self.assertEqual(first_occurrence(array, 1), helper(array, 1)) self.assertEqual(first_occurrence(array, 3), helper(array, 3)) self.assertEqual(first_occurrence(array, 5), helper(array, 5)) self.assertEqual(first_occurrence(array, 6), helper(array, 6)) self.assertEqual(first_occurrence(array, 7), helper(array, 7)) self.assertEqual(first_occurrence(array, -1), helper(array, -1)) def test_binary_search(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6] self.assertEqual(10, binary_search(array, 5)) self.assertEqual(11, binary_search(array, 6)) self.assertEqual(-1, binary_search(array, 7)) self.assertEqual(-1, binary_search(array, -1)) # Test binary_search_recur self.assertEqual(10, binary_search_recur(array, 0, 11, 5)) self.assertEqual(11, binary_search_recur(array, 0, 11, 6)) self.assertEqual(-1, binary_search_recur(array, 0, 11, 7)) self.assertEqual(-1, binary_search_recur(array, 0, 11, -1)) def test_ternary_search(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6] self.assertEqual(10, ternary_search(0, 11, 5, array)) self.assertEqual(3, ternary_search(0, 10, 3, array)) self.assertEqual(10, ternary_search(0, 10, 5, array)) self.assertEqual(-1, ternary_search(0, 11, 7, array)) self.assertEqual(-1, ternary_search(0, 11, -1, array)) def test_last_occurrence(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 6, 6] self.assertEqual(5, last_occurrence(array, 3)) self.assertEqual(10, last_occurrence(array, 5)) self.assertEqual(-1, last_occurrence(array, 7)) self.assertEqual(0, last_occurrence(array, 1)) self.assertEqual(13, last_occurrence(array, 6)) def test_linear_search(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 6, 6] self.assertEqual(6, linear_search(array, 4)) self.assertEqual(10, linear_search(array, 5)) self.assertEqual(-1, linear_search(array, 7)) self.assertEqual(-1, linear_search(array, -1)) def test_search_insert(self): array = [1, 3, 5, 6] self.assertEqual(2, search_insert(array, 5)) self.assertEqual(1, search_insert(array, 2)) self.assertEqual(4, search_insert(array, 7)) self.assertEqual(0, search_insert(array, 0)) def test_two_sum(self): array = [2, 7, 11, 15] # test two_sum self.assertEqual([1, 2], two_sum(array, 9)) self.assertEqual([2, 4], two_sum(array, 22)) # test two_sum1 self.assertEqual([1, 2], two_sum1(array, 9)) self.assertEqual([2, 4], two_sum1(array, 22)) # test two_sum2 self.assertEqual([1, 2], two_sum2(array, 9)) self.assertEqual([2, 4], two_sum2(array, 22)) def test_search_range(self): array = [5, 7, 7, 8, 8, 8, 10] self.assertEqual([3, 5], search_range(array, 8)) self.assertEqual([1, 2], search_range(array, 7)) self.assertEqual([-1, -1], search_range(array, 11)) array = [5, 7, 7, 7, 7, 8, 8, 8, 8, 10] self.assertEqual([5, 8], search_range(array, 8)) self.assertEqual([1, 4], search_range(array, 7)) self.assertEqual([-1, -1], search_range(array, 11)) def test_find_min_rotate(self): array = [4, 5, 6, 7, 0, 1, 2] self.assertEqual(0, find_min_rotate(array)) array = [10, 20, -1, 0, 1, 2, 3, 4, 5] self.assertEqual(-1, find_min_rotate(array)) # Test find min using recursion array = [4, 5, 6, 7, 0, 1, 2] self.assertEqual(0, find_min_rotate_recur(array, 0, 6)) array = [10, 20, -1, 0, 1, 2, 3, 4, 5] self.assertEqual(-1, find_min_rotate_recur(array, 0, 8)) def test_search_rotate(self): array = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14] self.assertEqual(8, search_rotate(array, 5)) self.assertEqual(-1, search_rotate(array, 9)) self.assertEqual(8, search_rotate_recur(array, 0, 11, 5)) self.assertEqual(-1, search_rotate_recur(array, 0, 11, 9)) def test_jump_search(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6] self.assertEqual(10, jump_search(array, 5)) self.assertEqual(2, jump_search(array, 3)) self.assertEqual(-1, jump_search(array, 7)) self.assertEqual(-1, jump_search(array, -1)) def test_next_greatest_letter(self): letters = ["c", "f", "j"] target = "a" self.assertEqual("c", next_greatest_letter(letters, target)) self.assertEqual("c", next_greatest_letter_v1(letters, target)) self.assertEqual("c", next_greatest_letter_v2(letters, target)) letters = ["c", "f", "j"] target = "d" self.assertEqual("f", next_greatest_letter(letters, target)) self.assertEqual("f", next_greatest_letter_v1(letters, target)) self.assertEqual("f", next_greatest_letter_v2(letters, target)) letters = ["c", "f", "j"] target = "j" self.assertEqual("c", next_greatest_letter(letters, target)) self.assertEqual("c", next_greatest_letter_v1(letters, target)) self.assertEqual("c", next_greatest_letter_v2(letters, target)) def test_interpolation_search(self): array = [0, 3, 5, 5, 9, 12, 12, 15, 16, 19, 20] self.assertEqual(1, interpolation_search(array, 3)) self.assertEqual(2, interpolation_search(array, 5)) self.assertEqual(6, interpolation_search(array, 12)) self.assertEqual(-1, interpolation_search(array, 22)) self.assertEqual(-1, interpolation_search(array, -10)) self.assertEqual(10, interpolation_search(array, 20)) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_set.py ================================================ import unittest from algorithms.set import find_keyboard_row class TestFindKeyboardRow(unittest.TestCase): def test_find_keyboard_row(self): self.assertEqual( ["Alaska", "Dad"], find_keyboard_row(["Hello", "Alaska", "Dad", "Peace"]) ) ================================================ FILE: tests/test_sorting.py ================================================ import unittest from algorithms.sorting import ( bitonic_sort, bogo_sort, bubble_sort, bucket_sort, cocktail_shaker_sort, comb_sort, counting_sort, cycle_sort, exchange_sort, gnome_sort, insertion_sort, max_heap_sort, merge_sort, min_heap_sort, pancake_sort, pigeonhole_sort, quick_sort, radix_sort, selection_sort, shell_sort, ) def is_sorted(array): """ Helper function to check if the given array is sorted. :param array: Array to check if sorted :return: True if sorted in ascending order, else False """ return all(array[i] <= array[i + 1] for i in range(len(array) - 1)) class TestSuite(unittest.TestCase): def test_bogo_sort(self): self.assertTrue(is_sorted(bogo_sort([1, 23, 5]))) def test_bitonic_sort(self): self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_bubble_sort(self): self.assertTrue(is_sorted(bubble_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_comb_sort(self): self.assertTrue(is_sorted(comb_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_counting_sort(self): self.assertTrue(is_sorted(counting_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_cycle_sort(self): self.assertTrue(is_sorted(cycle_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_exchange_sort(self): self.assertTrue(is_sorted(exchange_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_heap_sort(self): self.assertTrue(is_sorted(max_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) self.assertTrue(is_sorted(min_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_insertion_sort(self): self.assertTrue(is_sorted(insertion_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_merge_sort(self): self.assertTrue(is_sorted(merge_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_pancake_sort(self): self.assertTrue(is_sorted(pancake_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_pigeonhole_sort(self): self.assertTrue(is_sorted(pigeonhole_sort([1, 5, 65, 23, 57, 1232]))) def test_quick_sort(self): self.assertTrue(is_sorted(quick_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_selection_sort(self): self.assertTrue(is_sorted(selection_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_bucket_sort(self): self.assertTrue(is_sorted(bucket_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_shell_sort(self): self.assertTrue(is_sorted(shell_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_radix_sort(self): self.assertTrue(is_sorted(radix_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_gnome_sort(self): self.assertTrue(is_sorted(gnome_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_cocktail_shaker_sort(self): self.assertTrue(is_sorted(cocktail_shaker_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_stack.py ================================================ import unittest from algorithms.stack import ( ArrayStack, LinkedListStack, OrderedStack, first_is_consecutive, first_stutter, first_switch_pairs, is_sorted, is_valid, remove_min, second_is_consecutive, second_stutter, second_switch_pairs, simplify_path, ) class TestSuite(unittest.TestCase): def test_is_consecutive(self): self.assertTrue(first_is_consecutive([3, 4, 5, 6, 7])) self.assertFalse(first_is_consecutive([3, 4, 6, 7])) self.assertFalse(first_is_consecutive([3, 2, 1])) self.assertTrue(second_is_consecutive([3, 4, 5, 6, 7])) self.assertFalse(second_is_consecutive([3, 4, 6, 7])) self.assertFalse(second_is_consecutive([3, 2, 1])) def test_is_sorted(self): # Test case: bottom [6, 3, 5, 1, 2, 4] top self.assertFalse(is_sorted([6, 3, 5, 1, 2, 4])) self.assertTrue(is_sorted([1, 2, 3, 4, 5, 6])) self.assertFalse(is_sorted([3, 4, 7, 8, 5, 6])) def test_remove_min(self): # Test case: bottom [2, 8, 3, -6, 7, 3] top self.assertEqual([2, 8, 3, 7, 3], remove_min([2, 8, 3, -6, 7, 3])) # Test case: 2 smallest value [2, 8, 3, 7, 3] self.assertEqual([4, 8, 7], remove_min([4, 8, 3, 7, 3])) def test_stutter(self): # Test case: bottom [3, 7, 1, 14, 9] top self.assertEqual( [3, 3, 7, 7, 1, 1, 14, 14, 9, 9], first_stutter([3, 7, 1, 14, 9]) ) self.assertEqual( [3, 3, 7, 7, 1, 1, 14, 14, 9, 9], second_stutter([3, 7, 1, 14, 9]) ) def test_switch_pairs(self): # Test case: even number of values in stack # bottom [3, 8, 17, 9, 1, 10] top self.assertEqual([8, 3, 9, 17, 10, 1], first_switch_pairs([3, 8, 17, 9, 1, 10])) self.assertEqual( [8, 3, 9, 17, 10, 1], second_switch_pairs([3, 8, 17, 9, 1, 10]) ) # Test case: odd number of values in stack # bottom [3, 8, 17, 9, 1] top self.assertEqual([8, 3, 9, 17, 1], first_switch_pairs([3, 8, 17, 9, 1])) self.assertEqual([8, 3, 9, 17, 1], second_switch_pairs([3, 8, 17, 9, 1])) def test_is_valid_parenthesis(self): self.assertTrue(is_valid("[]")) self.assertTrue(is_valid("[]()[]")) self.assertFalse(is_valid("[[[]]")) self.assertTrue(is_valid("{([])}")) self.assertFalse(is_valid("(}")) def test_simplify_path(self): p = "/my/name/is/..//keon" self.assertEqual("/my/name/keon", simplify_path(p)) class TestStack(unittest.TestCase): def test_array_stack(self): stack = ArrayStack() stack.push(1) stack.push(2) stack.push(3) # test __iter__() it = iter(stack) self.assertEqual(3, next(it)) self.assertEqual(2, next(it)) self.assertEqual(1, next(it)) self.assertRaises(StopIteration, next, it) # test __len__() self.assertEqual(3, len(stack)) # test __str__() self.assertEqual(str(stack), "Top-> 3 2 1") # test is_empty() self.assertFalse(stack.is_empty()) # test peek() self.assertEqual(3, stack.peek()) # test pop() self.assertEqual(3, stack.pop()) self.assertEqual(2, stack.pop()) self.assertEqual(1, stack.pop()) self.assertTrue(stack.is_empty()) def test_linked_list_stack(self): stack = LinkedListStack() stack.push(1) stack.push(2) stack.push(3) # test __iter__() it = iter(stack) self.assertEqual(3, next(it)) self.assertEqual(2, next(it)) self.assertEqual(1, next(it)) self.assertRaises(StopIteration, next, it) # test __len__() self.assertEqual(3, len(stack)) # test __str__() self.assertEqual(str(stack), "Top-> 3 2 1") # test is_empty() self.assertFalse(stack.is_empty()) # test peek() self.assertEqual(3, stack.peek()) # test pop() self.assertEqual(3, stack.pop()) self.assertEqual(2, stack.pop()) self.assertEqual(1, stack.pop()) self.assertTrue(stack.is_empty()) class TestOrderedStack(unittest.TestCase): def test_ordered_stack(self): stack = OrderedStack() self.assertTrue(stack.is_empty()) stack.push(1) stack.push(4) stack.push(3) stack.push(6) "bottom - > 1 3 4 6 " self.assertEqual(6, stack.pop()) self.assertEqual(4, stack.peek()) self.assertEqual(3, stack.size()) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_streaming.py ================================================ import unittest from algorithms.streaming import one_sparse from algorithms.streaming.misra_gries import ( misras_gries, ) class TestMisraGreis(unittest.TestCase): def test_misra_correct(self): self.assertEqual({"4": 5}, misras_gries([1, 4, 4, 4, 5, 4, 4])) self.assertEqual({"1": 4}, misras_gries([0, 0, 0, 1, 1, 1, 1])) self.assertEqual({"0": 4, "1": 3}, misras_gries([0, 0, 0, 0, 1, 1, 1, 2, 2], 3)) def test_misra_incorrect(self): self.assertEqual(None, misras_gries([1, 2, 5, 4, 5, 4, 4, 5, 4, 4, 5])) self.assertEqual(None, misras_gries([0, 0, 0, 2, 1, 1, 1])) self.assertEqual(None, misras_gries([0, 0, 0, 1, 1, 1])) class TestOneSparse(unittest.TestCase): def test_one_sparse_correct(self): self.assertEqual( 4, one_sparse([(4, "+"), (2, "+"), (2, "-"), (4, "+"), (3, "+"), (3, "-")]) ) self.assertEqual( 2, one_sparse( [(2, "+"), (2, "+"), (2, "+"), (2, "+"), (2, "+"), (2, "+"), (2, "+")] ), ) def test_one_sparse_incorrect(self): self.assertEqual( None, one_sparse( [(2, "+"), (2, "+"), (2, "+"), (2, "+"), (2, "+"), (2, "+"), (1, "+")] ), ) # Two values remaining self.assertEqual( None, one_sparse( [ (2, "+"), (2, "+"), (2, "+"), (2, "+"), (2, "-"), (2, "-"), (2, "-"), (2, "-"), ] ), ) # No values remaining # Bitsum sum of sign is inccorect self.assertEqual(None, one_sparse([(2, "+"), (2, "+"), (4, "+"), (4, "+")])) ================================================ FILE: tests/test_string.py ================================================ import unittest from algorithms.string import ( add_binary, atbash, bracket, caesar_cipher, check_pangram, contain_string, convert_morse_word, count_binary_substring, decode, decode_string, delete_reoccurring_characters, domain_name_1, domain_name_2, encode, first_unique_char, fizzbuzz, group_anagrams, int_to_roman, is_merge_iterative, is_merge_recursive, is_one_edit, is_one_edit2, is_palindrome, is_palindrome_deque, is_palindrome_reverse, is_palindrome_stack, is_palindrome_two_pointer, is_rotated, is_rotated_v1, is_valid_coordinates_0, iterative, judge_circle, knuth_morris_pratt, license_number, longest_common_prefix_v1, longest_common_prefix_v2, longest_common_prefix_v3, longest_palindrome, make_sentence, match_symbol, match_symbol_1, min_distance, min_distance_dp, multiply, panagram, pythonic, rabin_karp, recursive, repeat_string, repeat_substring, reverse_vowel, reverse_words, roman_to_int, rotate, rotate_alt, strip_url_params1, strip_url_params2, strip_url_params3, strong_password, text_justification, ultra_pythonic, unique_morse, word_squares, ) class TestAddBinary(unittest.TestCase): """[summary] Test for the file add_binary.py Arguments: unittest {[type]} -- [description] """ def test_add_binary(self): self.assertEqual("100", add_binary("11", "1")) self.assertEqual("101", add_binary("100", "1")) self.assertEqual("10", add_binary("1", "1")) class TestBreakingBad(unittest.TestCase): """[summary] Test for the file breaking_bad.py Arguments: unittest {[type]} -- [description] """ def setUp(self): self.words = ["Amazon", "Microsoft", "Google"] self.symbols = ["i", "Am", "cro", "le", "abc"] self.result = ["M[i]crosoft", "[Am]azon", "Mi[cro]soft", "Goog[le]"] def test_match_symbol(self): self.assertEqual(self.result, match_symbol(self.words, self.symbols)) def test_match_symbol_1(self): self.assertEqual( ["[Am]azon", "Mi[cro]soft", "Goog[le]"], match_symbol_1(self.words, self.symbols), ) def test_bracket(self): self.assertEqual( ("[Am]azon", "Mi[cro]soft", "Goog[le]"), bracket(self.words, self.symbols) ) self.assertEqual( ("Amazon", "Microsoft", "Google"), bracket(self.words, ["thisshouldnotmatch"]), ) self.assertEqual( ("Amazon", "M[i]crosoft", "Google"), bracket(self.words, ["i", "i"]) ) class TestDecodeString(unittest.TestCase): """[summary] Test for the file decode_string.py Arguments: unittest {[type]} -- [description] """ def test_decode_string(self): self.assertEqual("aaabcbc", decode_string("3[a]2[bc]")) self.assertEqual("accaccacc", decode_string("3[a2[c]]")) class TestDeleteReoccurring(unittest.TestCase): """[summary] Test for the file delete_reoccurring.py Arguments: unittest {[type]} -- [description] """ def test_delete_reoccurring_characters(self): self.assertEqual("abc", delete_reoccurring_characters("aaabcccc")) class TestDomainExtractor(unittest.TestCase): """[summary] Test for the file domain_extractor.py Arguments: unittest {[type]} -- [description] """ def test_valid(self): self.assertEqual(domain_name_1("https://github.com/SaadBenn"), "github") def test_invalid(self): self.assertEqual(domain_name_2("http://google.com"), "google") class TestEncodeDecode(unittest.TestCase): """[summary] Test for the file encode_decode.py Arguments: unittest {[type]} -- [description] """ def test_encode(self): self.assertEqual("4:keon2:is7:awesome", encode("keon is awesome")) def test_decode(self): self.assertEqual(["keon", "is", "awesome"], decode("4:keon2:is7:awesome")) class TestGroupAnagrams(unittest.TestCase): """[summary] Test for the file group_anagrams.py Arguments: unittest {[type]} -- [description] """ def test_group_anagrams(self): self.assertEqual( [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]], group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]), ) class TestIntToRoman(unittest.TestCase): """[summary] Test for the file int_to_roman.py Arguments: unittest {[type]} -- [description] """ def test_int_to_roman(self): self.assertEqual("DCXLIV", int_to_roman(644)) self.assertEqual("I", int_to_roman(1)) self.assertEqual("MMMCMXCIX", int_to_roman(3999)) class TestIsPalindrome(unittest.TestCase): """[summary] Test for the file is_palindrome.py Arguments: unittest {[type]} -- [description] """ def test_is_palindrome(self): # 'Otto' is a old german name. self.assertTrue(is_palindrome("Otto")) self.assertFalse(is_palindrome("house")) def test_is_palindrome_reverse(self): # 'Otto' is a old german name. self.assertTrue(is_palindrome_reverse("Otto")) self.assertFalse(is_palindrome_reverse("house")) def test_is_palindrome_two_pointer(self): # 'Otto' is a old german name. self.assertTrue(is_palindrome_two_pointer("Otto")) self.assertFalse(is_palindrome_two_pointer("house")) def test_is_palindrome_stack(self): # 'Otto' is a old german name. self.assertTrue(is_palindrome_stack("Otto")) self.assertFalse(is_palindrome_stack("house")) def test_is_palindrome_deque(self): # 'Otto' is a old german name. self.assertTrue(is_palindrome_deque("Otto")) self.assertFalse(is_palindrome_deque("house")) class TestIsRotated(unittest.TestCase): """[summary] Test for the file is_rotated.py Arguments: unittest {[type]} -- [description] """ def test_is_rotated(self): self.assertTrue(is_rotated("hello", "hello")) self.assertTrue(is_rotated("hello", "llohe")) self.assertFalse(is_rotated("hello", "helol")) self.assertFalse(is_rotated("hello", "lloh")) self.assertTrue(is_rotated("", "")) def test_is_rotated_v1(self): self.assertTrue(is_rotated_v1("hello", "hello")) self.assertTrue(is_rotated_v1("hello", "llohe")) self.assertFalse(is_rotated_v1("hello", "helol")) self.assertFalse(is_rotated_v1("hello", "lloh")) self.assertTrue(is_rotated_v1("", "")) class TestRotated(unittest.TestCase): def test_rotate(self): self.assertEqual("llohe", rotate("hello", 2)) self.assertEqual("hello", rotate("hello", 5)) self.assertEqual("elloh", rotate("hello", 6)) self.assertEqual("llohe", rotate("hello", 7)) def test_rotate_alt(self): self.assertEqual("llohe", rotate_alt("hello", 2)) self.assertEqual("hello", rotate_alt("hello", 5)) self.assertEqual("elloh", rotate_alt("hello", 6)) self.assertEqual("llohe", rotate_alt("hello", 7)) class TestLicenseNumber(unittest.TestCase): """[summary] Test for the file license_number.py Arguments: unittest {[type]} -- [description] """ def test_license_number(self): self.assertEqual("a-b-c-d-f-d-d-f", license_number("a-bc-dfd-df", 1)) self.assertEqual("ab-cd-fd-df", license_number("a-bc-dfd-df", 2)) self.assertEqual("ab-cdf-ddf", license_number("a-bc-dfd-df", 3)) self.assertEqual("abcd-fddf", license_number("a-bc-dfd-df", 4)) self.assertEqual("abc-dfddf", license_number("a-bc-dfd-df", 5)) class TestMakeSentence(unittest.TestCase): """[summary] Test for the file make_sentence.py Arguments: unittest {[type]} -- [description] """ def test_make_sentence(self): dictionarys = ["", "app", "let", "t", "apple", "applet"] word = "applet" self.assertTrue(make_sentence(word, dictionarys)) class TestMergeStringChecker(unittest.TestCase): """[summary] Test for the file merge_string_checker.py Arguments: unittest {[type]} -- [description] """ def test_is_merge_recursive(self): self.assertTrue(is_merge_recursive("codewars", "cdw", "oears")) def test_is_merge_iterative(self): self.assertTrue(is_merge_iterative("codewars", "cdw", "oears")) class TestMultiplyStrings(unittest.TestCase): """[summary] Test for the file multiply_strings.py Arguments: unittest {[type]} -- [description] """ def test_multiply(self): self.assertEqual("23", multiply("1", "23")) self.assertEqual("529", multiply("23", "23")) self.assertEqual("0", multiply("0", "23")) self.assertEqual("1000000", multiply("100", "10000")) class TestOneEditDistance(unittest.TestCase): """[summary] Test for the file one_edit_distance.py Arguments: unittest {[type]} -- [description] """ def test_is_one_edit(self): self.assertTrue(is_one_edit("abc", "abd")) self.assertFalse(is_one_edit("abc", "aed")) self.assertFalse(is_one_edit("abcd", "abcd")) def test_is_one_edit2(self): self.assertTrue(is_one_edit2("abc", "abd")) self.assertFalse(is_one_edit2("abc", "aed")) self.assertFalse(is_one_edit2("abcd", "abcd")) class TestRabinKarp(unittest.TestCase): """[summary] Test for the file rabin_karp.py Arguments: unittest {[type]} -- [description] """ def test_rabin_karp(self): self.assertEqual(3, rabin_karp("abc", "zsnabckfkd")) self.assertEqual(None, rabin_karp("abc", "zsnajkskfkd")) class TestReverseString(unittest.TestCase): """[summary] Test for the file reverse_string.py Arguments: unittest {[type]} -- [description] """ def test_recursive(self): self.assertEqual("ereht olleh", recursive("hello there")) def test_iterative(self): self.assertEqual("ereht olleh", iterative("hello there")) def test_pythonic(self): self.assertEqual("ereht olleh", pythonic("hello there")) def test_ultra_pythonic(self): self.assertEqual("ereht olleh", ultra_pythonic("hello there")) class TestReverseVowel(unittest.TestCase): """[summary] Test for the file reverse_vowel.py Arguments: unittest {[type]} -- [description] """ def test_reverse_vowel(self): self.assertEqual("holle", reverse_vowel("hello")) class TestReverseWords(unittest.TestCase): """[summary] Test for the file reverse_words.py Arguments: unittest {[type]} -- [description] """ def test_reverse_words(self): self.assertEqual( "pizza like I and kim keon am I", reverse_words("I am keon kim and I like pizza"), ) class TestRomanToInt(unittest.TestCase): """[summary] Test for the file roman_to_int.py Arguments: unittest {[type]} -- [description] """ def test_roman_to_int(self): self.assertEqual(621, roman_to_int("DCXXI")) self.assertEqual(1, roman_to_int("I")) self.assertEqual(3999, roman_to_int("MMMCMXCIX")) class TestStripUrlParams(unittest.TestCase): """Tests for strip_url_params.py — all three implementations.""" def test_strip_url_params1(self): self.assertEqual( strip_url_params1("www.saadbenn.com?a=1&b=2&a=2"), "www.saadbenn.com?a=1&b=2", ) self.assertEqual( strip_url_params1("www.saadbenn.com?a=1&b=2", ["b"]), "www.saadbenn.com?a=1", ) def test_strip_url_params2(self): self.assertEqual( strip_url_params2("www.saadbenn.com?a=1&b=2&a=2"), "www.saadbenn.com?a=1&b=2", ) self.assertEqual( strip_url_params2("www.saadbenn.com?a=1&b=2", ["b"]), "www.saadbenn.com?a=1", ) def test_strip_url_params3(self): self.assertEqual( strip_url_params3("www.saadbenn.com?a=1&b=2&a=2"), "www.saadbenn.com?a=1&b=2", ) self.assertEqual( strip_url_params3("www.saadbenn.com?a=1&b=2", ["b"]), "www.saadbenn.com?a=1", ) class TestValidateCoordinates(unittest.TestCase): """[summary] Test for the file validate_coordinates.py Arguments: unittest {[type]} -- [description] """ def test_valid(self): valid_coordinates = ["-23, 25", "4, -3", "90, 180", "-90, -180"] for coordinate in valid_coordinates: self.assertTrue(is_valid_coordinates_0(coordinate)) def test_invalid(self): invalid_coordinates = [ "23.234, - 23.4234", "99.234, 12.324", "6.325624, 43.34345.345", "0, 1,2", "23.245, 1e1", ] for coordinate in invalid_coordinates: self.assertFalse(is_valid_coordinates_0(coordinate)) class TestWordSquares(unittest.TestCase): """[summary] Test for the file word_squares.py Arguments: unittest {[type]} -- [description] """ def test_word_squares(self): self.assertEqual( [["wall", "area", "lead", "lady"], ["ball", "area", "lead", "lady"]], word_squares(["area", "lead", "wall", "lady", "ball"]), ) class TestUniqueMorse(unittest.TestCase): def test_convert_morse_word(self): self.assertEqual("--...-.", convert_morse_word("gin")) self.assertEqual("--...--.", convert_morse_word("msg")) def test_unique_morse(self): self.assertEqual(2, unique_morse(["gin", "zen", "gig", "msg"])) class TestJudgeCircle(unittest.TestCase): def test_judge_circle(self): self.assertTrue(judge_circle("UDLRUD")) self.assertFalse(judge_circle("LLRU")) class TestStrongPassword(unittest.TestCase): def test_strong_password(self): self.assertEqual(3, strong_password(3, "Ab1")) self.assertEqual(1, strong_password(11, "#Algorithms")) class TestCaesarCipher(unittest.TestCase): def test_caesar_cipher(self): self.assertEqual("Lipps_Asvph!", caesar_cipher("Hello_World!", 4)) self.assertEqual("okffng-Qwvb", caesar_cipher("middle-Outz", 2)) class TestCheckPangram(unittest.TestCase): def test_check_pangram(self): self.assertTrue(check_pangram("The quick brown fox jumps over the lazy dog")) self.assertFalse(check_pangram("The quick brown fox")) class TestContainString(unittest.TestCase): def test_contain_string(self): self.assertEqual(-1, contain_string("mississippi", "issipi")) self.assertEqual(0, contain_string("Hello World", "")) self.assertEqual(2, contain_string("hello", "ll")) class TestCountBinarySubstring(unittest.TestCase): def test_count_binary_substring(self): self.assertEqual(6, count_binary_substring("00110011")) self.assertEqual(4, count_binary_substring("10101")) self.assertEqual(3, count_binary_substring("00110")) class TestRepeatString(unittest.TestCase): def test_repeat_string(self): self.assertEqual(3, repeat_string("abcd", "cdabcdab")) self.assertEqual(4, repeat_string("bb", "bbbbbbb")) class TestTextJustification(unittest.TestCase): def test_text_justification(self): self.assertEqual( ["This is an", "example of text", "justification. "], text_justification( ["This", "is", "an", "example", "of", "text", "justification."], 16 ), ) self.assertEqual( ["What must be", "acknowledgment ", "shall be "], text_justification( ["What", "must", "be", "acknowledgment", "shall", "be"], 16 ), ) class TestMinDistance(unittest.TestCase): def test_min_distance(self): self.assertEqual(2, min_distance("sea", "eat")) self.assertEqual(6, min_distance("abAlgocrithmf", "Algorithmmd")) self.assertEqual(4, min_distance("acbbd", "aabcd")) class TestMinDistanceDP(unittest.TestCase): def test_min_distance(self): self.assertEqual(2, min_distance_dp("sea", "eat")) self.assertEqual(6, min_distance_dp("abAlgocrithmf", "Algorithmmd")) self.assertEqual(4, min_distance("acbbd", "aabcd")) class TestLongestCommonPrefix(unittest.TestCase): def test_longest_common_prefix(self): # Test first solution self.assertEqual("fl", longest_common_prefix_v1(["flower", "flow", "flight"])) self.assertEqual("", longest_common_prefix_v1(["dog", "racecar", "car"])) # Test second solution self.assertEqual("fl", longest_common_prefix_v2(["flower", "flow", "flight"])) self.assertEqual("", longest_common_prefix_v2(["dog", "racecar", "car"])) # Test third solution self.assertEqual("fl", longest_common_prefix_v3(["flower", "flow", "flight"])) self.assertEqual("", longest_common_prefix_v3(["dog", "racecar", "car"])) class TestFirstUniqueChar(unittest.TestCase): def test_first_unique_char(self): self.assertEqual(0, first_unique_char("leetcode")) self.assertEqual(2, first_unique_char("loveleetcode")) class TestRepeatSubstring(unittest.TestCase): def test_repeat_substring(self): self.assertTrue(repeat_substring("abab")) self.assertFalse(repeat_substring("aba")) self.assertTrue(repeat_substring("abcabcabcabc")) class TestAtbashCipher(unittest.TestCase): """[summary] Test for the file atbash_cipher.py Arguments: unittest {[type]} -- [description] """ def test_atbash_cipher(self): self.assertEqual("zyxwvutsrqponml", atbash("abcdefghijklmno")) self.assertEqual("KbgslM", atbash("PythoN")) self.assertEqual("AttaCK at DawN", atbash("ZggzXP zg WzdM")) self.assertEqual("ZggzXP zg WzdM", atbash("AttaCK at DawN")) class TestLongestPalindromicSubstring(unittest.TestCase): """[summary] Test for the file longest_palindromic_substring.py Arguments: unittest {[type]} -- [description] """ def test_longest_palindromic_substring(self): self.assertEqual("bb", longest_palindrome("cbbd")) self.assertEqual("abba", longest_palindrome("abba")) self.assertEqual("asdadsa", longest_palindrome("dasdasdasdasdasdadsa")) self.assertEqual("abba", longest_palindrome("cabba")) class TestKnuthMorrisPratt(unittest.TestCase): """[summary] Test for the file knuth_morris_pratt.py Arguments: unittest {[type]} -- [description] """ def test_knuth_morris_pratt(self): self.assertEqual([0, 1, 2, 3, 4], knuth_morris_pratt("aaaaaaa", "aaa")) self.assertEqual([0, 4], knuth_morris_pratt("abcdabc", "abc")) self.assertEqual([], knuth_morris_pratt("aabcdaab", "aba")) self.assertEqual([0, 4], knuth_morris_pratt([0, 0, 1, 1, 0, 0, 1, 0], [0, 0])) class TestPanagram(unittest.TestCase): """[summary] Test for the file panagram.py Arguments: unittest {[type]} -- [description] """ def test_empty_string(self): # Arrange string = "" # Act res = panagram(string) # Assert self.assertEqual(False, res) def test_single_word_non_panagram(self): # Arrange string = "sentence" # Act res = panagram(string) # Assert self.assertEqual(False, res) def test_fox_panagram_no_spaces(self): # Arrange string = "thequickbrownfoxjumpsoverthelazydog" # Act res = panagram(string) # Assert self.assertEqual(True, res) def test_fox_panagram_mixed_case(self): # Arrange string = "theqUiCkbrOwnfOxjUMPSOVErThELAzYDog" # Act res = panagram(string) # Assert self.assertEqual(True, res) def test_whitespace_punctuation(self): # Arrange string = "\n\t\r,.-_!?" # Act res = panagram(string) # Assert self.assertEqual(False, res) def test_fox_panagram(self): # Arrange string = "the quick brown fox jumps over the lazy dog" # Act res = panagram(string) # Assert self.assertEqual(True, res) def test_swedish_panagram(self): # Arrange string = "Yxmördaren Julia Blomqvist på fäktning i Schweiz" # Act res = panagram(string) # Assert self.assertEqual(True, res) class TestFizzbuzz(unittest.TestCase): """[summary] Tests for the fizzbuzz method in file fizzbuzz.py """ def test_fizzbuzz(self): # Testing that n < 0 returns a Value Error self.assertRaises(ValueError, fizzbuzz.fizzbuzz, -2) # Testing that a string returns a Type Error. self.assertRaises(TypeError, fizzbuzz.fizzbuzz, "hello") # Testing a base case, n = 3 result = fizzbuzz.fizzbuzz(3) expected = [1, 2, "Fizz"] self.assertEqual(result, expected) # Testing a base case, n = 5 result = fizzbuzz.fizzbuzz(5) expected = [1, 2, "Fizz", 4, "Buzz"] self.assertEqual(result, expected) # Testing a base case, n = 15 i.e. mod 3 and 5 result = fizzbuzz.fizzbuzz(15) expected = [ 1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", ] self.assertEqual(result, expected) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_tree.py ================================================ import unittest from algorithms.data_structures.b_tree import BTree from algorithms.data_structures.fenwick_tree import Fenwick_Tree from algorithms.tree import construct_tree_postorder_preorder as ctpp from algorithms.tree.traversal_inorder import inorder, inorder_rec from algorithms.tree.traversal_postorder import postorder, postorder_rec from algorithms.tree.traversal_preorder import preorder, preorder_rec class Node: def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right class TestTraversal(unittest.TestCase): def test_preorder(self): tree = create_tree() self.assertEqual([100, 50, 25, 75, 150, 125, 175], preorder(tree)) self.assertEqual([100, 50, 25, 75, 150, 125, 175], preorder_rec(tree)) def test_postorder(self): tree = create_tree() self.assertEqual([25, 75, 50, 125, 175, 150, 100], postorder(tree)) self.assertEqual([25, 75, 50, 125, 175, 150, 100], postorder_rec(tree)) def test_inorder(self): tree = create_tree() self.assertEqual([25, 50, 75, 100, 125, 150, 175], inorder(tree)) self.assertEqual([25, 50, 75, 100, 125, 150, 175], inorder_rec(tree)) def create_tree(): n1 = Node(100) n2 = Node(50) n3 = Node(150) n4 = Node(25) n5 = Node(75) n6 = Node(125) n7 = Node(175) n1.left, n1.right = n2, n3 n2.left, n2.right = n4, n5 n3.left, n3.right = n6, n7 return n1 class TestBTree(unittest.TestCase): @classmethod def setUpClass(cls): import random random.seed(18719) cls.random = random cls.range = 10000 def setUp(self): self.keys_to_insert = [ self.random.randrange(-self.range, self.range) for i in range(self.range) ] def test_insertion_and_find_even_degree(self): btree = BTree(4) for i in self.keys_to_insert: btree.insert_key(i) for _ in range(100): key = self.random.choice(self.keys_to_insert) self.assertTrue(btree.find(key)) def test_insertion_and_find_odd_degree(self): btree = BTree(3) for i in self.keys_to_insert: btree.insert_key(i) for _ in range(100): key = self.random.choice(self.keys_to_insert) self.assertTrue(btree.find(key)) def test_deletion_even_degree(self): btree = BTree(4) key_list = set(self.keys_to_insert) for i in key_list: btree.insert_key(i) for key in key_list: btree.remove_key(key) self.assertFalse(btree.find(key)) self.assertEqual(btree.root.keys, []) self.assertEqual(btree.root.children, []) def test_deletion_odd_degree(self): btree = BTree(3) key_list = set(self.keys_to_insert) for i in key_list: btree.insert_key(i) for key in key_list: btree.remove_key(key) self.assertFalse(btree.find(key)) self.assertEqual(btree.root.keys, []) self.assertEqual(btree.root.children, []) class TestConstructTreePreorderPostorder(unittest.TestCase): def test_construct_tree(self): # Test 1 ctpp.pre_index = 0 pre1 = [1, 2, 4, 8, 9, 5, 3, 6, 7] post1 = [8, 9, 4, 5, 2, 6, 7, 3, 1] size1 = len(pre1) self.assertEqual( ctpp.construct_tree(pre1, post1, size1), [8, 4, 9, 2, 5, 1, 6, 3, 7] ) # Test 2 ctpp.pre_index = 0 pre2 = [1, 2, 4, 5, 3, 6, 7] post2 = [4, 5, 2, 6, 7, 3, 1] size2 = len(pre2) self.assertEqual(ctpp.construct_tree(pre2, post2, size2), [4, 2, 5, 1, 6, 3, 7]) # Test 3 ctpp.pre_index = 0 pre3 = [12, 7, 16, 21, 5, 1, 9] post3 = [16, 21, 7, 1, 9, 5, 12] size3 = len(pre3) self.assertEqual( ctpp.construct_tree(pre3, post3, size3), [16, 7, 21, 12, 1, 5, 9] ) class TestFenwickTree(unittest.TestCase): def test_construct_tree_with_update_1(self): freq = [2, 1, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9] ft = Fenwick_Tree(freq) bit_tree = ft.construct() self.assertEqual(12, ft.get_sum(bit_tree, 5)) freq[3] += 6 ft.update_bit(bit_tree, 3, 6) self.assertEqual(18, ft.get_sum(bit_tree, 5)) def test_construct_tree_with_update_2(self): freq = [1, 2, 3, 4, 5] ft = Fenwick_Tree(freq) bit_tree = ft.construct() self.assertEqual(10, ft.get_sum(bit_tree, 3)) freq[3] -= 5 ft.update_bit(bit_tree, 3, -5) self.assertEqual(5, ft.get_sum(bit_tree, 3)) def test_construct_tree_with_update_3(self): freq = [2, 1, 4, 6, -1, 5, -32, 0, 1] ft = Fenwick_Tree(freq) bit_tree = ft.construct() self.assertEqual(12, ft.get_sum(bit_tree, 4)) freq[2] += 11 ft.update_bit(bit_tree, 2, 11) self.assertEqual(23, ft.get_sum(bit_tree, 4)) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_veb_tree.py ================================================ import unittest from algorithms.data_structures.veb_tree import VEBTree class TestVEBTree(unittest.TestCase): def setUp(self): self.veb = VEBTree(16) def test_insert_and_member(self): values = [2, 3, 4, 7, 14] for v in values: self.veb.insert(v) for v in values: self.assertTrue(self.veb.member(v)) self.assertFalse(self.veb.member(5)) def test_min_max(self): self.veb.insert(10) self.veb.insert(2) self.veb.insert(15) self.assertEqual(2, self.veb.minimum()) self.assertEqual(15, self.veb.maximum()) def test_successor(self): for v in [2, 4, 8, 12]: self.veb.insert(v) self.assertEqual(4, self.veb.successor(2)) self.assertEqual(8, self.veb.successor(4)) self.assertIsNone(self.veb.successor(12)) def test_delete(self): for v in [1, 3, 5, 7]: self.veb.insert(v) self.veb.delete(3) self.assertFalse(self.veb.member(3)) self.assertEqual(5, self.veb.successor(1)) def test_invalid_universe(self): with self.assertRaises(ValueError): VEBTree(15) # not power of 2 if __name__ == "__main__": unittest.main()