diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 000000000..1eb737ecd --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,36 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + python -m pytest diff --git a/.gitignore b/.gitignore index 86f7f584d..5152c3d1b 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ __pycache__/ *.py[cod] -*.iml -*.xml +*.iml +*.xml .idea/ .cache/ .pytest_cache/ @@ -11,3 +11,9 @@ __pycache__/ # 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/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b3be12855..94607d486 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,30 @@ group: travis_latest +dist: xenial # Travis CI's default distro language: python -cache: pip -python: - - 3.6 - #- nightly - #- pypy3 +cache: + directories: + - $HOME/.cache/pip matrix: - allow_failures: - - python: nightly - - python: pypy3 + include: + - python: 3.6 + env: TOX_ENV=py36 + - python: 3.7 + env: TOX_ENV=py37 install: - - pip install -r requirements.txt - - pip install flake8 # pytest # add another testing frameworks later - - pip install python-coveralls - - pip install coverage - - pip install nose + - pip install -r test_requirements.txt before_script: + # run black to check if some files would need reformatting + - black --check . || true # stop the build if there are Python syntax errors or undefined names - - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics script: # Check python install package - - pip3 install -e . - # Check unittest running - - python3 -m unittest discover tests - # Check pytest running - - python3 -m pytest tests - # Run nose with coverage support - - nosetests --with-coverage + - pip install -e . + - tox -e $TOX_ENV # Check python uninstall package - - pip3 uninstall -y algorithms + - pip uninstall -y algorithms notifications: on_success: change on_failure: change # `always` will be the setting once code changes slow down - -after_success: - - coveralls diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 380bd3d64..6eac6c029 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,9 @@ agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md). * 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 fixe a bug on a certain algorithm or data structure. + * 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 @@ -23,6 +24,7 @@ it doesn't fall under any section. Make sure that your implementation works. - 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]. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..50af9b9c9 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include LICENSE +include algorithms/* diff --git a/README.md b/README.md index fce6c05a3..65caeb5ca 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -English | [简体中文](https://github.com/yunshuipiao/algorithms/blob/master/README_CN.md) | [Deutsch](README_GE.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) [![Build Status](https://travis-ci.org/keon/algorithms.svg?branch=master)](https://travis-ci.org/keon/algorithms) [![Coverage Status](https://coveralls.io/repos/github/keon/algorithms/badge.svg?branch=master)](https://coveralls.io/github/keon/algorithms?branch=master) +

+ Pythonic Data Structures and Algorithms ========================================= @@ -12,7 +13,6 @@ Minimal and clean example implementations of data structures and algorithms in P ## Contributing Thanks for your interest in contributing! There are many ways to contribute to this project. [Get started here](CONTRIBUTING.md) - ## Tests ### Use unittest @@ -32,16 +32,16 @@ For running all tests write down: ## Install If you want to use the API algorithms in your code, it is as simple as: - $ pip3 install git+https://github.com/keon/algorithms + $ pip3 install algorithms You can test by creating a python file: (Ex: use `merge_sort` in `sort`) ```python3 -from sort import merge_sort +from algorithms.sort import merge_sort if __name__ == "__main__": my_list = [1, 8, 3, 5, 6] - my_list = merge_sort.merge_sort(my_list) + my_list = merge_sort(my_list) print(my_list) ``` @@ -52,270 +52,373 @@ If you want to uninstall algorithms, it is as simple as: ## List of Implementations -- [arrays](arrays) - - [delete_nth](arrays/delete_nth.py) - - [flatten](arrays/flatten.py) - - [garage](arrays/garage.py) - - [josephus_problem](arrays/josephus_problem.py) - - [longest_non_repeat](arrays/longest_non_repeat.py/) - - [merge_intervals](arrays/merge_intervals.py) - - [missing_ranges](arrays/missing_ranges.py) - - [plus_one](arrays/plus_one.py) -    - [rotate_array](arrays/rotate_array.py) - - [summary_ranges](arrays/summary_ranges.py) - - [three_sum](arrays/three_sum.py) - - [two_sum](arrays/two_sum.py) - - [move_zeros_to_end](arrays/move_zeros_to_end.py) -- [backtrack](backtrack) - - [general_solution.md](backtrack/) - - [anagram](backtrack/anagram.py) - - [array_sum_combinations](backtrack/array_sum_combinations.py) - - [combination_sum](backtrack/combination_sum.py) - - [expression_add_operators](backtrack/expression_add_operators.py) - - [factor_combinations](backtrack/factor_combinations.py) - - [generate_abbreviations](backtrack/generate_abbreviations.py) - - [generate_parenthesis](backtrack/generate_parenthesis.py) - - [letter_combination](backtrack/letter_combination.py) - - [palindrome_partitioning](backtrack/palindrome_partitioning.py) - - [pattern_match](backtrack/pattern_match.py) - - [permute](backtrack/permute.py) - - [permute_unique](backtrack/permute_unique.py) - - [subsets](backtrack/subsets.py) - - [subsets_unique](backtrack/subsets_unique.py) -- [bfs](bfs) - - [shortest_distance_from_all_buildings](bfs/shortest_distance_from_all_buildings.py) - - [word_ladder](bfs/word_ladder.py) -- [bit](bit) - - [bytes_int_conversion](bit/bytes_int_conversion.py) - - [count_ones](bit/count_ones.py) - - [find_missing_number](bit/find_missing_number.py) - - [power_of_two](bit/power_of_two.py) - - [reverse_bits](bit/reverse_bits.py) - - [single_number](bit/single_number.py) - - [single_number2](bit/single_number2.py) - - [single_number3](bit/single_number3.py) - - [subsets](bit/subsets.py) - - [add_bitwise_operator](bit/add_bitwise_operator.py) - - [bit_operation](bit/bit_operation.py) - - [swap_pair](bit/swap_pair.py) - - [find_difference](bit/find_difference.py) - - [has_alternative_bit](bit/has_alternative_bit.py) - - [insert_bit](bit/insert_bit.py) - - [remove_bit](bit/remove_bit.py) -- [calculator](calculator) - - [math_parser](calculator/math_parser.py) -- [dfs](dfs) - - [all_factors](dfs/all_factors.py) - - [count_islands](dfs/count_islands.py) - - [pacific_atlantic](dfs/pacific_atlantic.py) - - [sudoku_solver](dfs/sudoku_solver.py) - - [walls_and_gates](dfs/walls_and_gates.py) -- [dp](dp) - - [buy_sell_stock](dp/buy_sell_stock.py) - - [climbing_stairs](dp/climbing_stairs.py) - - [coin_change](dp/coin_change.py) - - [combination_sum](dp/combination_sum.py) - - [egg_drop](dp/egg_drop.py) - - [house_robber](dp/house_robber.py) - - [job_scheduling](dp/job_scheduling.py) - - [knapsack](dp/knapsack.py) - - [longest_increasing](dp/longest_increasing.py) - - [matrix_chain_order](dp/matrix_chain_order.py) - - [max_product_subarray](dp/max_product_subarray.py) - - [max_subarray](dp/max_subarray.py) - - [min_cost_path](dp/min_cost_path.py) - - [num_decodings](dp/num_decodings.py) - - [regex_matching](dp/regex_matching.py) - - [rod_cut](dp/rod_cut.py) - - [word_break](dp/word_break.py) - - [fibonacci](dp/fib.py) -- [graph](graph) - - [strongly_connected](graph/checkDiGraphStronglyConnected.py) - - [clone_graph](graph/clone_graph.py) - - [cycle_detection](graph/cycle_detection.py) - - [find_all_cliques](graph/find_all_cliques.py) - - [find_path](graph/find_path.py) - - [graph](graph/graph.py) - - [markov_chain](graph/markov_chain.py) - - [minimum_spanning_tree](graph/minimum_spanning_tree.py) - - [satisfiability](graph/satisfiability.py) - - [tarjan](graph/tarjan.py) - - [traversal](graph/traversal.py) -- [heap](heap) - - [merge_sorted_k_lists](heap/merge_sorted_k_lists.py) - - [skyline](heap/skyline.py) - - [sliding_window_max](heap/sliding_window_max.py) - - [binary_heap](heap/binary_heap.py) -- [linkedlist](linkedlist) - - [add_two_numbers](linkedlist/add_two_numbers.py) - - [copy_random_pointer](linkedlist/copy_random_pointer.py) - - [delete_node](linkedlist/delete_node.py) - - [first_cyclic_node](linkedlist/first_cyclic_node.py) - - [is_cyclic](linkedlist/is_cyclic.py) - - [is_palindrome](linkedlist/is_palindrome.py) - - [kth_to_last](linkedlist/kth_to_last.py) - - [linkedlist](linkedlist/linkedlist.py) - - [remove_duplicates](linkedlist/remove_duplicates.py) - - [reverse](linkedlist/reverse.py) - - [rotate_list](linkedlist/rotate_list.py) - - [swap_in_pairs](linkedlist/swap_in_pairs.py) - - [is_sorted](linkedlist/is_sorted.py) - - [remove_range](linkedlist/remove_range.py) -- [map](map) - - [hashtable](map/hashtable.py) - - [separate_chaining_hashtable](map/separate_chaining_hashtable.py) - - [longest_common_subsequence](map/longest_common_subsequence.py) - - [randomized_set](map/randomized_set.py) - - [valid_sudoku](map/valid_sudoku.py) -- [maths](maths) - - [base_conversion](maths/base_conversion.py) - - [extended_gcd](maths/extended_gcd.py) - - [gcd/lcm](maths/gcd.py) - - [generate_strobogrammtic](maths/generate_strobogrammtic.py) - - [is_strobogrammatic](maths/is_strobogrammatic.py) - - [next_bigger](maths/next_bigger.py) - - [next_perfect_square](maths/next_perfect_square.py) - - [nth_digit](maths/nth_digit.py) - - [prime_check](maths/prime_check.py) - - [primes_sieve_of_eratosthenes](maths/primes_sieve_of_eratosthenes.py) - - [pythagoras](maths/pythagoras.py) - - [rabin_miller](maths/rabin_miller.py) - - [rsa](maths/rsa.py) - - [sqrt_precision_factor](maths/sqrt_precision_factor.py) - - [summing_digits](maths/summing_digits.py) -- [matrix](matrix) - - [sudoku_validator](matrix/sudoku_validator.py) - - [bomb_enemy](matrix/bomb_enemy.py) - - [copy_transform](matrix/copy_transform.py) - - [count_paths](matrix/count_paths.py) - - [matrix_rotation.txt](matrix/matrix_rotation.txt) - - [rotate_image](matrix/rotate_image.py) - - [search_in_sorted_matrix](matrix/search_in_sorted_matrix.py) - - [sparse_dot_vector](matrix/sparse_dot_vector.py) - - [sparse_mul](matrix/sparse_mul.py) - - [spiral_traversal](matrix/spiral_traversal.py) -- [queues](queues) - - [max_sliding_window](queues/max_sliding_window.py) - - [moving_average](queues/moving_average.py) - - [queue](queues/queue.py) - - [reconstruct_queue](queues/reconstruct_queue.py) - - [zigzagiterator](queues/zigzagiterator.py) -- [search](search) - - [binary_search](search/binary_search.py) - - [first_occurance](search/first_occurance.py) - - [last_occurance](search/last_occurance.py) - - [search_insert](search/search_insert.py) - - [two_sum](search/two_sum.py) - - [search_range](search/search_range.py) - - [find_min_rotate](search/find_min_rotate.py) - - [search_rotate](search/search_rotate.py) -- [set](set) - - [randomized_set](set/randomized_set.py) - - [set_covering](set/set_covering.py) -- [sort](sort) - - [bubble_sort](sort/bubble_sort.py) - - [comb_sort](sort/comb_sort.py) - - [counting_sort](sort/counting_sort.py) - - [heap_sort](sort/heap_sort.py) - - [insertion_sort](sort/insertion_sort.py) - - [meeting_rooms](sort/meeting_rooms.py) - - [merge_sort](sort/merge_sort.py) - - [quick_sort](sort/quick_sort.py) - - [selection_sort](sort/selection_sort.py) - - [sort_colors](sort/sort_colors.py) - - [topsort](sort/topsort.py) - - [wiggle_sort](sort/wiggle_sort.py) -- [stack](stack) - - [longest_abs_path](stack/longest_abs_path.py) - - [simplify_path](stack/simplify_path.py) - - [stack](stack/stack.py) - - [valid_parenthesis](stack/valid_parenthesis.py) - - [stutter](stack/stutter.py) - - [switch_pairs](stack/switch_pairs.py) - - [is_consecutive](stack/is_consecutive.py) - - [remove_min](stack/remove_min.py) - - [is_sorted](stack/is_sorted.py) -- [strings](strings) - - [fizzbuzz](strings/fizzbuzz.py) - - [delete_reoccurring_characters](strings/delete_reoccurring_characters.py) - - [strip_url_params](strings/strip_url_params.py) - - [validate_coordinates](strings/validate_coordinates.py) - - [domain_extractor](strings/domain_extractor.py) - - [merge_string_checker](strings/merge_string_checker.py) - - [add_binary](strings/add_binary.py) - - [breaking_bad](strings/breaking_bad.py) - - [decode_string](strings/decode_string.py) - - [encode_decode](strings/encode_decode.py) - - [group_anagrams](strings/group_anagrams.py) - - [int_to_roman](strings/int_to_roman.py) - - [is_palindrome](strings/is_palindrome.py) - - [license_number](strings/license_number.py) - - [make_sentence](strings/make_sentence.py) - - [multiply_strings](strings/multiply_strings.py) - - [one_edit_distance](strings/one_edit_distance.py) - - [rabin_karp](strings/rabin_karp.py) - - [reverse_string](strings/reverse_string.py) - - [reverse_vowel](strings/reverse_vowel.py) - - [reverse_words](strings/reverse_words.py) - - [roman_to_int](strings/roman_to_int.py) - - [word_squares](strings/word_squares.py) -- [tree](tree) - - [bst](tree/tree/bst) - - [array2bst](tree/bst/array2bst.py) - - [bst_closest_value](tree/bst/bst_closest_value.py) - - [BSTIterator](tree/bst/BSTIterator.py) - - [delete_node](tree/bst/delete_node.py) - - [is_bst](tree/bst/is_bst.py) - - [kth_smallest](tree/bst/kth_smallest.py) - - [lowest_common_ancestor](tree/bst/lowest_common_ancestor.py) - - [predecessor](tree/bst/predecessor.py) - - [serialize_deserialize](tree/bst/serialize_deserialize.py) - - [successor](tree/bst/successor.py) - - [unique_bst](tree/bst/unique_bst.py) - - [depth_sum](tree/bst/depth_sum.py) - - [count_left_node](tree/bst/count_left_node.py) - - [num_empty](tree/bst/num_empty.py) - - [height](tree/bst/height.py) - - [red_black_tree](tree/red_black_tree) - - [red_black_tree](tree/red_black_tree/red_black_tree.py) - - [segment_tree](tree/segment_tree) - - [segment_tree](tree/segment_tree/segment_tree.py) - - [traversal](tree/traversal) - - [inorder](tree/traversal/inorder.py) - - [level_order](tree/traversal/level_order.py) - - [zigzag](tree/traversal/zigzag.py) - - [trie](tree/trie) - - [add_and_search](tree/trie/add_and_search.py) - - [trie](tree/trie/trie.py) - - [binary_tree_paths](tree/binary_tree_paths.py) - - [bintree2list](tree/bintree2list.py) - - [deepest_left](tree/deepest_left.py) - - [invert_tree](tree/invert_tree.py) - - [is_balanced](tree/is_balanced.py) - - [is_subtree](tree/is_subtree.py) - - [is_symmetric](tree/is_symmetric.py) - - [longest_consecutive](tree/longest_consecutive.py) - - [lowest_common_ancestor](tree/lowest_common_ancestor.py) - - [max_height](tree/max_height.py) - - [max_path_sum](tree/max_path_sum.py) - - [min_height](tree/min_height.py) - - [path_sum](tree/path_sum.py) - - [path_sum2](tree/path_sum2.py) - - [pretty_print](tree/pretty_print.py) - - [same_tree](tree/same_tree.py) - - [tree](tree/tree.py) -- [union-find](union-find) - - [count_islands](union-find/count_islands.py) +- [arrays](algorithms/arrays) + - [delete_nth](algorithms/arrays/delete_nth.py) + - [flatten](algorithms/arrays/flatten.py) + - [garage](algorithms/arrays/garage.py) + - [josephus_problem](algorithms/arrays/josephus.py) + - [limit](algorithms/arrays/limit.py) + - [longest_non_repeat](algorithms/arrays/longest_non_repeat.py/) + - [max_ones_index](algorithms/arrays/max_ones_index.py) + - [merge_intervals](algorithms/arrays/merge_intervals.py) + - [missing_ranges](algorithms/arrays/missing_ranges.py) + - [plus_one](algorithms/arrays/plus_one.py) + - [remove_duplicates](algorithms/arrays/remove_duplicates.py) + - [rotate](algorithms/arrays/rotate.py) + - [summarize_ranges](algorithms/arrays/summarize_ranges.py) + - [three_sum](algorithms/arrays/three_sum.py) + - [trimmean](algorithms/arrays/trimmean.py) + - [top_1](algorithms/arrays/top_1.py) + - [two_sum](algorithms/arrays/two_sum.py) + - [move_zeros](algorithms/arrays/move_zeros.py) + - [n_sum](algorithms/arrays/n_sum.py) +- [greedy](algorithms/greedy/) + - [max_contiguous_subsequence_sum](algorithms/greedy/max_contiguous_subsequence_sum.py) +- [automata](algorithms/automata) + - [DFA](algorithms/automata/dfa.py) +- [backtrack](algorithms/backtrack) + - [general_solution.md](algorithms/backtrack/) + - [add_operators](algorithms/backtrack/add_operators.py) + - [anagram](algorithms/backtrack/anagram.py) + - [array_sum_combinations](algorithms/backtrack/array_sum_combinations.py) + - [combination_sum](algorithms/backtrack/combination_sum.py) + - [factor_combinations](algorithms/backtrack/factor_combinations.py) + - [generate_abbreviations](algorithms/backtrack/generate_abbreviations.py) + - [generate_parenthesis](algorithms/backtrack/generate_parenthesis.py) + - [letter_combination](algorithms/backtrack/letter_combination.py) + - [palindrome_partitioning](algorithms/backtrack/palindrome_partitioning.py) + - [pattern_match](algorithms/backtrack/pattern_match.py) + - [permute](algorithms/backtrack/permute.py) + - [permute_unique](algorithms/backtrack/permute_unique.py) + - [subsets](algorithms/backtrack/subsets.py) + - [subsets_unique](algorithms/backtrack/subsets_unique.py) +- [bfs](algorithms/bfs) + - [maze_search](algorithms/bfs/maze_search.py) + - [shortest_distance_from_all_buildings](algorithms/bfs/shortest_distance_from_all_buildings.py) + - [word_ladder](algorithms/bfs/word_ladder.py) +- [bit](algorithms/bit) + - [add_bitwise_operator](algorithms/bit/add_bitwise_operator.py) + - [bit_operation](algorithms/bit/bit_operation.py) + - [bytes_int_conversion](algorithms/bit/bytes_int_conversion.py) + - [count_flips_to_convert](algorithms/bit/count_flips_to_convert.py) + - [count_ones](algorithms/bit/count_ones.py) + - [find_difference](algorithms/bit/find_difference.py) + - [find_missing_number](algorithms/bit/find_missing_number.py) + - [flip_bit_longest_sequence](algorithms/bit/flip_bit_longest_sequence.py) + - [power_of_two](algorithms/bit/power_of_two.py) + - [reverse_bits](algorithms/bit/reverse_bits.py) + - [single_number](algorithms/bit/single_number.py) + - [single_number2](algorithms/bit/single_number2.py) + - [single_number3](algorithms/bit/single_number3.py) + - [subsets](algorithms/bit/subsets.py) + - [swap_pair](algorithms/bit/swap_pair.py) + - [has_alternative_bit](algorithms/bit/has_alternative_bit.py) + - [insert_bit](algorithms/bit/insert_bit.py) + - [remove_bit](algorithms/bit/remove_bit.py) + - [binary_gap](algorithms/bit/binary_gap.py) +- [compression](algorithms/compression) + - [huffman_coding](algorithms/compression/huffman_coding.py) + - [rle_compression](algorithms/compression/rle_compression.py) + - [elias](algorithms/compression/elias.py) +- [dfs](algorithms/dfs) + - [all_factors](algorithms/dfs/all_factors.py) + - [count_islands](algorithms/dfs/count_islands.py) + - [pacific_atlantic](algorithms/dfs/pacific_atlantic.py) + - [sudoku_solver](algorithms/dfs/sudoku_solver.py) + - [walls_and_gates](algorithms/dfs/walls_and_gates.py) +- [distribution](algorithms/distribution) + - [histogram](algorithms/distribution/histogram.py) +- [dp](algorithms/dp) + - [buy_sell_stock](algorithms/dp/buy_sell_stock.py) + - [climbing_stairs](algorithms/dp/climbing_stairs.py) + - [coin_change](algorithms/dp/coin_change.py) + - [combination_sum](algorithms/dp/combination_sum.py) + - [egg_drop](algorithms/dp/egg_drop.py) + - [house_robber](algorithms/dp/house_robber.py) + - [int_divide](algorithms/dp/int_divide.py) + - [job_scheduling](algorithms/dp/job_scheduling.py) + - [knapsack](algorithms/dp/knapsack.py) + - [longest_increasing](algorithms/dp/longest_increasing.py) + - [matrix_chain_order](algorithms/dp/matrix_chain_order.py) + - [max_product_subarray](algorithms/dp/max_product_subarray.py) + - [max_subarray](algorithms/dp/max_subarray.py) + - [min_cost_path](algorithms/dp/min_cost_path.py) + - [num_decodings](algorithms/dp/num_decodings.py) + - [regex_matching](algorithms/dp/regex_matching.py) + - [rod_cut](algorithms/dp/rod_cut.py) + - [word_break](algorithms/dp/word_break.py) + - [fibonacci](algorithms/dp/fib.py) + - [hosoya triangle](algorithms/dp/hosoya_triangle.py) + - [K-Factor_strings](algorithms/dp/k_factor.py) + - [planting_trees](algorithms/dp/planting_trees.py) +- [graph](algorithms/graph) + - [check_bipartite](algorithms/graph/check_bipartite.py) + - [strongly_connected](algorithms/graph/check_digraph_strongly_connected.py) + - [clone_graph](algorithms/graph/clone_graph.py) + - [cycle_detection](algorithms/graph/cycle_detection.py) + - [find_all_cliques](algorithms/graph/find_all_cliques.py) + - [find_path](algorithms/graph/find_path.py) + - [graph](algorithms/graph/graph.py) + - [dijkstra](algorithms/graph/dijkstra.py) + - [markov_chain](algorithms/graph/markov_chain.py) + - [minimum_spanning_tree](algorithms/graph/minimum_spanning_tree.py) + - [satisfiability](algorithms/graph/satisfiability.py) + - [minimum_spanning_tree_prims](algorithms/graph/prims_minimum_spanning.py) + - [tarjan](algorithms/graph/tarjan.py) + - [traversal](algorithms/graph/traversal.py) + - [maximum_flow](algorithms/graph/maximum_flow.py) + - [maximum_flow_bfs](algorithms/graph/maximum_flow_bfs.py) + - [maximum_flow_dfs](algorithms/graph/maximum_flow_dfs.py) + - [all_pairs_shortest_path](algorithms/graph/all_pairs_shortest_path.py) + - [bellman_ford](algorithms/graph/bellman_ford.py) + - [Count Connected Components](algorithms/graph/count_connected_number_of_component.py) +- [heap](algorithms/heap) + - [merge_sorted_k_lists](algorithms/heap/merge_sorted_k_lists.py) + - [skyline](algorithms/heap/skyline.py) + - [sliding_window_max](algorithms/heap/sliding_window_max.py) + - [binary_heap](algorithms/heap/binary_heap.py) + - [k_closest_points](algorithms/heap/k_closest_points.py) +- [linkedlist](algorithms/linkedlist) + - [add_two_numbers](algorithms/linkedlist/add_two_numbers.py) + - [copy_random_pointer](algorithms/linkedlist/copy_random_pointer.py) + - [delete_node](algorithms/linkedlist/delete_node.py) + - [first_cyclic_node](algorithms/linkedlist/first_cyclic_node.py) + - [is_cyclic](algorithms/linkedlist/is_cyclic.py) + - [is_palindrome](algorithms/linkedlist/is_palindrome.py) + - [kth_to_last](algorithms/linkedlist/kth_to_last.py) + - [linkedlist](algorithms/linkedlist/linkedlist.py) + - [remove_duplicates](algorithms/linkedlist/remove_duplicates.py) + - [reverse](algorithms/linkedlist/reverse.py) + - [rotate_list](algorithms/linkedlist/rotate_list.py) + - [swap_in_pairs](algorithms/linkedlist/swap_in_pairs.py) + - [is_sorted](algorithms/linkedlist/is_sorted.py) + - [remove_range](algorithms/linkedlist/remove_range.py) +- [map](algorithms/map) + - [hashtable](algorithms/map/hashtable.py) + - [separate_chaining_hashtable](algorithms/map/separate_chaining_hashtable.py) + - [longest_common_subsequence](algorithms/map/longest_common_subsequence.py) + - [longest_palindromic_subsequence](algorithms/map/longest_palindromic_subsequence.py) + - [randomized_set](algorithms/map/randomized_set.py) + - [valid_sudoku](algorithms/map/valid_sudoku.py) + - [word_pattern](algorithms/map/word_pattern.py) + - [is_isomorphic](algorithms/map/is_isomorphic.py) + - [is_anagram](algorithms/map/is_anagram.py) +- [maths](algorithms/maths) + - [base_conversion](algorithms/maths/base_conversion.py) + - [chinese_remainder_theorem](algorithms/maths/chinese_remainder_theorem.py) + - [combination](algorithms/maths/combination.py) + - [cosine_similarity](algorithms/maths/cosine_similarity.py) + - [decimal_to_binary_ip](algorithms/maths/decimal_to_binary_ip.py) + - [diffie_hellman_key_exchange](algorithms/maths/diffie_hellman_key_exchange.py) + - [euler_totient](algorithms/maths/euler_totient.py) + - [extended_gcd](algorithms/maths/extended_gcd.py) + - [factorial](algorithms/maths/factorial.py) + - [find_order](algorithms/maths/find_order_simple.py) + - [find_primitive_root](algorithms/maths/find_primitive_root_simple.py) + - [gcd/lcm](algorithms/maths/gcd.py) + - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) + - [hailstone](algorithms/maths/hailstone.py) + - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) + - [krishnamurthy_number](algorithms/maths/krishnamurthy_number.py) + - [magic_number](algorithms/maths/magic_number.py) + - [modular_exponential](algorithms/maths/modular_exponential.py) + - [modular_inverse](algorithms/maths/modular_inverse.py) + - [next_bigger](algorithms/maths/next_bigger.py) + - [next_perfect_square](algorithms/maths/next_perfect_square.py) + - [nth_digit](algorithms/maths/nth_digit.py) + - [num_perfect_squares](algorithms/maths/num_perfect_squares.py) + - [polynomial](algorithms/maths/polynomial.py) + - [power](algorithms/maths/power.py) + - [prime_check](algorithms/maths/prime_check.py) + - [primes_sieve_of_eratosthenes](algorithms/maths/primes_sieve_of_eratosthenes.py) + - [pythagoras](algorithms/maths/pythagoras.py) + - [rabin_miller](algorithms/maths/rabin_miller.py) + - [recursive_binomial_coefficient](algorithms/maths/recursive_binomial_coefficient.py) + - [rsa](algorithms/maths/rsa.py) + - [sqrt_precision_factor](algorithms/maths/sqrt_precision_factor.py) + - [summing_digits](algorithms/maths/summing_digits.py) + - [symmetry_group_cycle_index](algorithms/maths/symmetry_group_cycle_index.py) +- [matrix](algorithms/matrix) + - [sudoku_validator](algorithms/matrix/sudoku_validator.py) + - [bomb_enemy](algorithms/matrix/bomb_enemy.py) + - [copy_transform](algorithms/matrix/copy_transform.py) + - [count_paths](algorithms/matrix/count_paths.py) + - [matrix_exponentiation](algorithms/matrix/matrix_exponentiation.py) + - [matrix_inversion](algorithms/matrix/matrix_inversion.py) + - [matrix_multiplication](algorithms/matrix/multiply.py) + - [rotate_image](algorithms/matrix/rotate_image.py) + - [search_in_sorted_matrix](algorithms/matrix/search_in_sorted_matrix.py) + - [sparse_dot_vector](algorithms/matrix/sparse_dot_vector.py) + - [sparse_mul](algorithms/matrix/sparse_mul.py) + - [spiral_traversal](algorithms/matrix/spiral_traversal.py) + - [crout_matrix_decomposition](algorithms/matrix/crout_matrix_decomposition.py) + - [cholesky_matrix_decomposition](algorithms/matrix/cholesky_matrix_decomposition.py) + - [sum_sub_squares](algorithms/matrix/sum_sub_squares.py) + - [sort_matrix_diagonally](algorithms/matrix/sort_matrix_diagonally.py) +- [queues](algorithms/queues) + - [max_sliding_window](algorithms/queues/max_sliding_window.py) + - [moving_average](algorithms/queues/moving_average.py) + - [queue](algorithms/queues/queue.py) + - [reconstruct_queue](algorithms/queues/reconstruct_queue.py) + - [zigzagiterator](algorithms/queues/zigzagiterator.py) +- [search](algorithms/search) + - [binary_search](algorithms/search/binary_search.py) + - [first_occurrence](algorithms/search/first_occurrence.py) + - [last_occurrence](algorithms/search/last_occurrence.py) + - [linear_search](algorithms/search/linear_search.py) + - [search_insert](algorithms/search/search_insert.py) + - [two_sum](algorithms/search/two_sum.py) + - [search_range](algorithms/search/search_range.py) + - [find_min_rotate](algorithms/search/find_min_rotate.py) + - [search_rotate](algorithms/search/search_rotate.py) + - [jump_search](algorithms/search/jump_search.py) + - [next_greatest_letter](algorithms/search/next_greatest_letter.py) + - [interpolation_search](algorithms/search/interpolation_search.py) +- [set](algorithms/set) + - [randomized_set](algorithms/set/randomized_set.py) + - [set_covering](algorithms/set/set_covering.py) + - [find_keyboard_row](algorithms/set/find_keyboard_row.py) +- [sort](algorithms/sort) + - [bitonic_sort](algorithms/sort/bitonic_sort.py) + - [bogo_sort](algorithms/sort/bogo_sort.py) + - [bubble_sort](algorithms/sort/bubble_sort.py) + - [bucket_sort](algorithms/sort/bucket_sort.py) + - [cocktail_shaker_sort](algorithms/sort/cocktail_shaker_sort.py) + - [comb_sort](algorithms/sort/comb_sort.py) + - [counting_sort](algorithms/sort/counting_sort.py) + - [cycle_sort](algorithms/sort/cycle_sort.py) + - [exchange_sort](algorithms/sort/exchange_sort.py) + - [gnome_sort](algorithms/sort/gnome_sort.py) + - [heap_sort](algorithms/sort/heap_sort.py) + - [insertion_sort](algorithms/sort/insertion_sort.py) + - [meeting_rooms](algorithms/sort/meeting_rooms.py) + - [merge_sort](algorithms/sort/merge_sort.py) + - [pancake_sort](algorithms/sort/pancake_sort.py) + - [pigeonhole_sort](algorithms/sort/pigeonhole_sort.py) + - [quick_sort](algorithms/sort/quick_sort.py) + - [radix_sort](algorithms/sort/radix_sort.py) + - [selection_sort](algorithms/sort/selection_sort.py) + - [shell_sort](algorithms/sort/shell_sort.py) + - [sort_colors](algorithms/sort/sort_colors.py) + - [stooge_sort](algorithms/sort/stooge_sort.py) + - [top_sort](algorithms/sort/top_sort.py) + - [wiggle_sort](algorithms/sort/wiggle_sort.py) +- [stack](algorithms/stack) + - [longest_abs_path](algorithms/stack/longest_abs_path.py) + - [simplify_path](algorithms/stack/simplify_path.py) + - [stack](algorithms/stack/stack.py) + - [valid_parenthesis](algorithms/stack/valid_parenthesis.py) + - [stutter](algorithms/stack/stutter.py) + - [switch_pairs](algorithms/stack/switch_pairs.py) + - [is_consecutive](algorithms/stack/is_consecutive.py) + - [remove_min](algorithms/stack/remove_min.py) + - [is_sorted](algorithms/stack/is_sorted.py) +- [streaming](algorithms/streaming) + - [1-sparse-recovery](algorithms/streaming/one_sparse_recovery.py) + - [misra-gries](algorithms/streaming/misra_gries.py) +- [strings](algorithms/strings) + - [fizzbuzz](algorithms/strings/fizzbuzz.py) + - [delete_reoccurring](algorithms/strings/delete_reoccurring.py) + - [strip_url_params](algorithms/strings/strip_url_params.py) + - [validate_coordinates](algorithms/strings/validate_coordinates.py) + - [domain_extractor](algorithms/strings/domain_extractor.py) + - [merge_string_checker](algorithms/strings/merge_string_checker.py) + - [add_binary](algorithms/strings/add_binary.py) + - [breaking_bad](algorithms/strings/breaking_bad.py) + - [decode_string](algorithms/strings/decode_string.py) + - [encode_decode](algorithms/strings/encode_decode.py) + - [group_anagrams](algorithms/strings/group_anagrams.py) + - [int_to_roman](algorithms/strings/int_to_roman.py) + - [is_palindrome](algorithms/strings/is_palindrome.py) + - [license_number](algorithms/strings/license_number.py) + - [make_sentence](algorithms/strings/make_sentence.py) + - [multiply_strings](algorithms/strings/multiply_strings.py) + - [one_edit_distance](algorithms/strings/one_edit_distance.py) + - [rabin_karp](algorithms/strings/rabin_karp.py) + - [reverse_string](algorithms/strings/reverse_string.py) + - [reverse_vowel](algorithms/strings/reverse_vowel.py) + - [reverse_words](algorithms/strings/reverse_words.py) + - [roman_to_int](algorithms/strings/roman_to_int.py) + - [word_squares](algorithms/strings/word_squares.py) + - [unique_morse](algorithms/strings/unique_morse.py) + - [judge_circle](algorithms/strings/judge_circle.py) + - [strong_password](algorithms/strings/strong_password.py) + - [caesar_cipher](algorithms/strings/caesar_cipher.py) + - [check_pangram](algorithms/strings/check_pangram.py) + - [contain_string](algorithms/strings/contain_string.py) + - [count_binary_substring](algorithms/strings/count_binary_substring.py) + - [repeat_string](algorithms/strings/repeat_string.py) + - [min_distance](algorithms/strings/min_distance.py) + - [longest_common_prefix](algorithms/strings/longest_common_prefix.py) + - [rotate](algorithms/strings/rotate.py) + - [first_unique_char](algorithms/strings/first_unique_char.py) + - [repeat_substring](algorithms/strings/repeat_substring.py) + - [atbash_cipher](algorithms/strings/atbash_cipher.py) + - [longest_palindromic_substring](algorithms/strings/longest_palindromic_substring.py) + - [knuth_morris_pratt](algorithms/strings/knuth_morris_pratt.py) + - [panagram](algorithms/strings/panagram.py) +- [tree](algorithms/tree) + - [bst](algorithms/tree/bst) + - [array_to_bst](algorithms/tree/bst/array_to_bst.py) + - [bst_closest_value](algorithms/tree/bst/bst_closest_value.py) + - [BSTIterator](algorithms/tree/bst/BSTIterator.py) + - [delete_node](algorithms/tree/bst/delete_node.py) + - [is_bst](algorithms/tree/bst/is_bst.py) + - [kth_smallest](algorithms/tree/bst/kth_smallest.py) + - [lowest_common_ancestor](algorithms/tree/bst/lowest_common_ancestor.py) + - [predecessor](algorithms/tree/bst/predecessor.py) + - [serialize_deserialize](algorithms/tree/bst/serialize_deserialize.py) + - [successor](algorithms/tree/bst/successor.py) + - [unique_bst](algorithms/tree/bst/unique_bst.py) + - [depth_sum](algorithms/tree/bst/depth_sum.py) + - [count_left_node](algorithms/tree/bst/count_left_node.py) + - [num_empty](algorithms/tree/bst/num_empty.py) + - [height](algorithms/tree/bst/height.py) + - [fenwick_tree](algorithms/tree/fenwick_tree/fenwick_tree.py) + - [red_black_tree](algorithms/tree/red_black_tree) + - [red_black_tree](algorithms/tree/red_black_tree/red_black_tree.py) + - [segment_tree](algorithms/tree/segment_tree) + - [segment_tree](algorithms/tree/segment_tree/segment_tree.py) + - [iterative_segment_tree](algorithms/tree/segment_tree/iterative_segment_tree.py) + - [traversal](algorithms/tree/traversal) + - [inorder](algorithms/tree/traversal/inorder.py) + - [level_order](algorithms/tree/traversal/level_order.py) + - [postorder](algorithms/tree/traversal/postorder.py) + - [preorder](algorithms/tree/traversal/preorder.py) + - [zigzag](algorithms/tree/traversal/zigzag.py) + - [trie](algorithms/tree/trie) + - [add_and_search](algorithms/tree/trie/add_and_search.py) + - [trie](algorithms/tree/trie/trie.py) + - [b_tree](algorithms/tree/b_tree.py) + - [binary_tree_paths](algorithms/tree/binary_tree_paths.py) + - [bin_tree_to_list](algorithms/tree/bin_tree_to_list.py) + - [construct_tree_preorder_postorder](algorithms/tree/construct_tree_postorder_preorder.py) + - [deepest_left](algorithms/tree/deepest_left.py) + - [invert_tree](algorithms/tree/invert_tree.py) + - [is_balanced](algorithms/tree/is_balanced.py) + - [is_subtree](algorithms/tree/is_subtree.py) + - [is_symmetric](algorithms/tree/is_symmetric.py) + - [longest_consecutive](algorithms/tree/longest_consecutive.py) + - [lowest_common_ancestor](algorithms/tree/lowest_common_ancestor.py) + - [max_height](algorithms/tree/max_height.py) + - [max_path_sum](algorithms/tree/max_path_sum.py) + - [min_height](algorithms/tree/min_height.py) + - [path_sum](algorithms/tree/path_sum.py) + - [path_sum2](algorithms/tree/path_sum2.py) + - [pretty_print](algorithms/tree/pretty_print.py) + - [same_tree](algorithms/tree/same_tree.py) + - [tree](algorithms/tree/tree.py) +- [unix](algorithms/unix) + - [path](algorithms/unix/path/) + - [join_with_slash](algorithms/unix/path/join_with_slash.py) + - [full_path](algorithms/unix/path/full_path.py) + - [split](algorithms/unix/path/split.py) + - [simplify_path](algorithms/unix/path/simplify_path.py) +- [unionfind](algorithms/unionfind) + - [count_islands](algorithms/unionfind/count_islands.py) -## Contributors -The repo is maintained by -* [Keon Kim](https://github.com/keon) -* [Rahul Goswami](https://github.com/goswami-rahul) -* [Christian Bender](https://github.com/christianbender) -* [Ankit Agarwal](https://github.com/ankit167) -* [Hai Hoang Dang](https://github.com/danghai) -* [Saad](https://github.com/SaadBenn) +## Contributors -And thanks to [all the contributors](https://github.com/keon/algorithms/graphs/contributors) +Thanks to [all the contributors](https://github.com/keon/algorithms/graphs/contributors) who helped in building the repo. diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index c612cd522..000000000 --- a/README_CN.md +++ /dev/null @@ -1,287 +0,0 @@ -[English](https://github.com/yunshuipiao/algorithms/blob/master/README.md) | 简体中文 - -Python版数据结构和算法 -========================================= - -python版数据结构和算法实现的简约版小示例 - -谢谢关注。有多种方法可以贡献你的代码。[从这里开始吧](https://github.com/keon/algorithms/blob/master/CONTRIBUTING.md) - -[或者可以用不同语言来完成上述算法,期待加入](https://github.com/yunshuipiao/sw-algorithms):https://github.com/yunshuipiao/sw-algorithms - -## 测试 - -### 单元测试 -如下代码可以运行全部测试: -``` - -python3 -m unittest discover tests - -``` - -针对特定模块(比如:sort)的测试, 可以使用如下代码: -``` - -python3 -m unittest tests.test_sort - -``` - -### 使用pytest -如下代码运行所有测试代码: -``` - -pyhton3 -m pytest tests - -``` - -## 安装 -如果想在代码中使用算法API, 可按如下步骤进行: -``` - -pip3 install git+https://github.com/keon/algorithms - -``` - -通过创建python文件(比如:在sort模块使用merge_sort)进行测试: -``` - -from sort import merge_sort - -if __name__ == "__main__": - my_list = [1, 8, 3, 5, 6] - my_list = merge_sort.merge_sort(my_list) - print(my_list) - -``` - -## 卸载 -如下代码可卸载该API: - -``` - -pip3 uninstall -y algorithms - -``` - -## 实现列表 - -- [array:数组](arrays) - - [delete_nth: 删除第n项](arrays/delete_nth.py) - - [flatten:数组降维](arrays/flatten.py) - - [garage:停车场](arrays/garage.py) - - [josephus_problem: 约瑟夫问题](arrays/josephus_problem.py) - - [longest_non_repeat:最长不重复子串](arrays/longest_non_repeat.py/) - - [merge_intervals:合并重叠间隔](arrays/merge_intervals.py) - - [missing_ranges:遗失的范围](arrays/missing_ranges.py) - - [plus_one:加一运算](arrays/plus_one.py) - - [rotate_array:反转数组](arrays/rotate_array.py) - - [summary_ranges:数组范围](arrays/summary_ranges.py) - - [three_sum:三数和为零](arrays/three_sum.py) - - [two_sum:两数和](arrays/two_sum.py) - - [move_zeros_to_end: 0后置问题](arrays/move_zeros_to_end.py) -- [backtrack:回溯](backtrack) - - [general_solution.md:一般方法](backtrack/) - - [anagram:同字母异序词](backtrack/anagram.py) - - [array_sum_combinations:数组和](backtrack/array_sum_combinations.py) - - [combination_sum:和的合并](backtrack/combination_sum.py) - - [expression_add_operators:给表达式添加运算符](backtrack/expression_add_operators.py) - - [factor_combinations:因素组合](backtrack/factor_combinations.py) - - [generate_abbreviations:缩写生成](backtrack/generate_abbreviations.py) - - [generate_parenthesis:括号生成](backtrack/generate_parenthesis.py) - - [letter_combination:字母组合](backtrack/letter_combination.py) - - [palindrome_partitioning:字符串的所有回文子串](backtrack/palindrome_partitioning.py) - - [pattern_match:模式匹配](backtrack/pattern_match.py) - - [permute:排列](backtrack/permute.py) - - [permute_unique:唯一排列](backtrack/permute_unique.py) - - [subsets:子集](backtrack/subsets.py) - - [subsets_unique:唯一子集](backtrack/subsets_unique.py) -- [bfs:广度优先搜索](bfs) - - [shortest_distance_from_all_buildings:所有建筑物的最短路径:](bfs/shortest_distance_from_all_buildings.py) - - [word_ladder:词语阶梯](bfs/word_ladder.py) -- [bit:位操作](bit) - - [bytes_int_conversion:字节整数转换](bit/bytes_int_conversion.py) - - [count_ones:统计1出现的次数](bit/count_ones.py) - - [find_missing_number:寻找缺失数](bit/find_missing_number.py) - - [power_of_two:2的n次方数判断](bit/power_of_two.py) - - [reverse_bits:反转位](bit/reverse_bits.py) - - [single_number2:寻找出现1次的数(2)](bit/single_number2.py) - - [single_number:寻找出现1次的数(1)](bit/single_number.py) - - [subsets: 求所有子集](bit/subsets.py) - - [add_bitwise_operator:无操作符的加法](bit/add_bitwise_operator.py) -- [calculator:计算](calculator) - - [math_parser: 数字解析](calculator/math_parser.py) -- [dfs:深度优先搜索](dfs) - - [all_factors:因素分解](dfs/all_factors.py) - - [count_islands:岛计数](dfs/count_islands.py) - - [pacific_atlantic:太平洋大西洋](dfs/pacific_atlantic.py) - - [sudoku_solver:数独解法](dfs/sudoku_solver.py) - - [walls_and_gates:墙和门](dfs/walls_and_gates.py) -- [dp:动态规划](dp) - - [buy_sell_stock:股票买卖](dp/buy_sell_stock.py) - - [climbing_stairs:爬梯子问题](dp/climbing_stairs.py) - - [combination_sum:和组合问题](dp/combination_sum.py) - - [house_robber:打家劫舍](dp/house_robber.py) - - [knapsack:背包问题](dp/knapsack.py) - - [longest_increasing:最长递增子序列](dp/longest_increasing.py) - - [max_product_subarray:最大子数组乘积](dp/max_product_subarray.py) - - [max_subarray:最大子数组](dp/max_subarray.py) - - [num_decodings:数字解码](dp/num_decodings.py) - - [regex_matching:正则匹配](dp/regex_matching.py) - - [word_break:单词分割](dp/word_break.py) -- [graph:图](graph) - - [2-sat:2-sat](graph/satisfiability.py) - - [clone_graph:克隆图](graph/clone_graph.py) - - [cycle_detection:判断圈算法](graph/cycle_detection.py) - - [find_path:发现路径](graph/find_path.py) - - [graph:图](graph/graph.py) - - [traversal:遍历](graph/traversal.py) - - [markov_chain:马尔可夫链](graph/markov_chain.py) -- [heap:堆](heap) - - [merge_sorted_k_lists:合并k个有序链](heap/merge_sorted_k_lists.py) - - [skyline:天际线](heap/skyline.py) - - [sliding_window_max:滑动窗口最大值](heap/sliding_window_max.py) -- [linkedlist:链表](linkedlist) - - [add_two_numbers:链表数相加](linkedlist/add_two_numbers.py) - - [copy_random_pointer:复制带有随机指针的链表](linkedlist/copy_random_pointer.py) - - [delete_node:删除节点](linkedlist/delete_node.py) - - [first_cyclic_node:环链表的第一个节点](linkedlist/first_cyclic_node.py) - - [is_cyclic:判断环链表](linkedlist/is_cyclic.py) - - [is_palindrome:回文链表](linkedlist/is_palindrome.py) - - [kth_to_last:倒数第k个节点](linkedlist/kth_to_last.py) - - [linkedlist: 链表](linkedlist/linkedlist.py) - - [remove_duplicates:删除重复元素](linkedlist/remove_duplicates.py) - - [reverse:反转链表](linkedlist/reverse.py) - - [rotate_list:旋转链表](linkedlist/rotate_list.py) - - [swap_in_pairs:链表节点交换](linkedlist/swap_in_pairs.py) -- [map:映射](map) - - [hashtable:哈希表](map/hashtable.py) - - [separate_chaining_hashtable:拉链法哈希表](map/separate_chaining_hashtable.py) - - [longest_common_subsequence:最长公共子序列](map/longest_common_subsequence.py) - - [randomized_set:随机集](map/randomized_set.py) - - [valid_sudoku:有效数独](map/valid_sudoku.py) -- [math:数学问题](maths) - - [extended_gcd:扩展欧几里得算法](maths/extended_gcd.py) - - [gcd/lcm:最大公约数和最小公倍数](maths/gcd.py) - - [prime_test:主要测试](maths/prime_test.py) - - [primes_sieve_of_eratosthenes:埃拉托色尼的质数筛](maths/primes_sieve_of_eratosthenes.py) - - [generate_strobogrammtic:生成对称数](maths/generate_strobogrammtic.py) - - [is_strobogrammatic:判断对称数](maths/is_strobogrammatic.py) - - [nth_digit:第n位](maths/nth_digit.py) - - [rabin_miller:米勒-拉宾素性检验](maths/rabin_miller.py) - - [rsa:rsa加密](maths/rsa.py) - - [sqrt_precision_factor:开发精度因素](maths/sqrt_precision_factor.py) - - [pythagoras:毕达哥拉斯](maths/pythagoras.py) -- [matrix:矩阵](matrix) - - [matrix_rotation:矩阵旋转](matrix/matrix_rotation.txt) - - [copy_transform:复制变换](matrix/copy_transform.py) - - [bomb_enemy:炸弹人](matrix/bomb_enemy.py) - - [rotate_image:旋转图像](matrix/rotate_image.py) - - [sparse_dot_vector:解析点向量](matrix/sparse_dot_vector.py) - - [sparse_mul:稀疏矩阵](matrix/sparse_mul.py) - - [spiral_traversal:循环遍历](matrix/spiral_traversal.py) - - [count_paths:计算路径](matrix/count_paths.py) -- [queue:队列](queues) - - [max_sliding_window:最大移动窗口](queues/max_sliding_window.py) - - [moving_average:移动平均](queues/moving_average.py) - - [queue:队列](queues/queue.py) - - [reconstruct_queue:重建队列](queues/reconstruct_queue.py) - - [zigzagiterator:锯齿形迭代](queues/zigzagiterator.py) -- [search:查找](search) - - [binary_search:二分查找](search/binary_search.py) - - [count_elem:元素计数](search/count_elem.py) - - [first_occurance:首次出现](search/first_occurance.py) - - [last_occurance:最后一次出现](search/last_occurance.py) -- [set:集合](set) - - [randomized_set:随机集合](set/randomized_set.py) - - [set_covering:集合覆盖](set/set_covering.py) -- [sort:排序](sort) - - [bubble_sort:冒泡排序](sort/bubble_sort.py) - - [comb_sort:梳排序](sort/comb_sort.py) - - [counting_sort:计数排序](sort/counting_sort.py) - - [heap_sort:堆排序](sort/heap_sort.py) - - [insertion_sort:插入排序](sort/insertion_sort.py) - - [meeting_rooms:会议室](sort/meeting_rooms.py) - - [merge_sort:归并排序](sort/merge_sort.py) - - [quick_sort:快速排序](sort/quick_sort.py) - - [selection_sort:选择排序](sort/selection_sort.py) - - [sort_colors:颜色排序](sort/sort_colors.py) - - [topsort:top排序](sort/topsort.py) - - [wiggle_sort:摇摆排序](sort/wiggle_sort.py) -- [stack:栈](stack) - - [longest_abs_path:最长相对路径](stack/longest_abs_path.py) - - [simplify_path:简化路径](stack/simplify_path.py) - - [stack:栈](stack/stack.py) - - [valid_parenthesis:验证括号](stack/valid_parenthesis.py) -- [string:字符串](strings) - - [add_binary:二进制数相加](strings/add_binary.py) - - [breaking_bad:打破坏](strings/breaking_bad.py) - - [decode_string:字符串编码](strings/decode_string.py) - - [encode_decode:编解码](strings/encode_decode.py) - - [group_anagrams:群组错位词](strings/group_anagrams.py) - - [int_to_roman:整数转换罗马数字](strings/int_to_roman.py) - - [is_palindrome:回文字符串](strings/is_palindrome.py) - - [license_number:拍照号码](strings/license_number.py) - - [make_sentence:造句](strings/make_sentence.py) - - [multiply_strings:字符串相乘](strings/multiply_strings.py) - - [one_edit_distance:一个编辑距离](strings/one_edit_distance.py) - - [rabin_karp:Rabin-Karp 算法](strings/rabin_karp.py) - - [reverse_string:反转字符串](strings/reverse_string.py) - - [reverse_vowel:反转元音](strings/reverse_vowel.py) - - [reverse_words:反转单词](strings/reverse_words.py) - - [roman_to_int:罗马数转换整数](strings/roman_to_int.py) - - [word_squares:单词平方](strings/word_squares.py) -- [tree:树](tree) - - [segment-tree:线段树](tree/segment_tree) - - [segment_tree:线段树](tree/segment_tree/segment_tree.py) - - [binary_tree_paths:二叉树路径](tree/binary_tree_paths.py) - - [bintree2list:二叉树转换链表](tree/bintree2list.py) - - [bst:二叉搜索树](tree/tree/bst) - - [array2bst:数组转换](tree/bst/array2bst.py) - - [bst_closest_value:最近二叉搜索树值](tree/bst/bst_closest_value.py) - - [BSTIterator:二叉搜索树迭代](tree/bst/BSTIterator.py) - - [delete_node:删除节点](tree/bst/delete_node.py) - - [is_bst:判断二叉搜索树](tree/bst/is_bst.py) - - [kth_smallest:二叉搜索树的第k小节点](tree/bst/kth_smallest.py) - - [lowest_common_ancestor:最近公共祖先](tree/bst/lowest_common_ancestor.py) - - [predecessor:前任](tree/bst/predecessor.py) - - [serialize_deserialize:序列化反序列化](tree/bst/serialize_deserialize.py) - - [successor:继承者](tree/bst/successor.py) - - [unique_bst:唯一BST](tree/bst/unique_bst.py) - - [deepest_left:最深叶子节点](tree/deepest_left.py) - - [invert_tree:反转树](tree/invert_tree.py) - - [is_balanced:判断平衡树](tree/is_balanced.py) - - [is_subtree:判断子树](tree/is_subtree.py) - - [is_symmetric:判断对称树](tree/is_symmetric.py) - - [longest_consecutive:最长连续节点](tree/longest_consecutive.py) - - [lowest_common_ancestor:最近公共祖先](tree/lowest_common_ancestor.py) - - [max_height:最大高度](tree/max_height.py) - - [max_path_sum:最长路径和](tree/max_path_sum.py) - - [min_height:最小高度](tree/min_height.py) - - [path_sum2:路径和2](tree/path_sum2.py) - - [path_sum:路径和](tree/path_sum.py) - - [pretty_print:完美打印](tree/pretty_print.py) - - [same_tree:相同树](tree/same_tree.py) - - [traversal:遍历](tree/traversal) - - [inorder:中序遍历](tree/traversal/inorder.py) - - [level_order:层次遍历](tree/traversal/level_order.py) - - [zigzag:锯齿形遍历](tree/traversal/zigzag.py) - - [tree:树](tree/tree.py) - - [trie:字典树](tree/trie) - - [add_and_search:添加和查找](tree/trie/add_and_search.py) - - [trie:字典](tree/trie/trie.py) -- [union-find:并查集](union-find) - - [count_islands:岛计数](union-find/count_islands.py) - - -## 贡献 -谢谢主要维护人员: - -* [Keon Kim](https://github.com/keon) -* [Rahul Goswami](https://github.com/goswami-rahul) -* [Christian Bender](https://github.com/christianbender) -* [Ankit Agarwal](https://github.com/ankit167) -* [Hai Hoang Dang](https://github.com/danghai) -* [Saad](https://github.com/SaadBenn) - -以及[所有贡献者](https://github.com/keon/algorithms/graphs/contributors) - diff --git a/README_GE.md b/README_GE.md deleted file mode 100644 index af1b8b2c4..000000000 --- a/README_GE.md +++ /dev/null @@ -1,329 +0,0 @@ -[English](README.md) | [简体中文](https://github.com/yunshuipiao/algorithms/blob/master/README_CN.md) | Deutsch - -[![Open Source Helpers](https://www.codetriage.com/keon/algorithms/badges/users.svg)](https://www.codetriage.com/keon/algorithms) -[![Build Status](https://travis-ci.org/keon/algorithms.svg?branch=master)](https://travis-ci.org/keon/algorithms) -[![Coverage Status](https://coveralls.io/repos/github/keon/algorithms/badge.svg?branch=master)](https://coveralls.io/github/keon/algorithms?branch=master) - -Pythonische Datenstrukturen und Algorithmen -========================================= - -In diesem Repository finden Sie eine große Auswahl an Algorithmen und Datenstrukturen implementiert in Python 3. - -## Beteiligen - -Sie können sich gerne auch an diesem Projekt beteiligen. Zum Beispiel selbst Algorithmen und Datenstrukturen beisteuern, oder bereits bestehende Implementierungen verbessern, oder auch dokumentieren. Fühlen Sie sich frei und machen Sie einen Pull-Request. Alternativ können Sie auch den Issue-Tracker benutzen um auf Probleme (Bugs) in bereits bestehenden Implementierungen hinzuweisen. - -In diesem Projekt halten wir uns an die [PEP8](https://www.python.org/dev/peps/pep-0008/) Codestyle Konventionen. - -## Tests - -### Benutzen der Unittests - -Um alle Tests laufen zu lassen, tippen Sie die unten stehende Befehlzeile in die Kommandozeile: - - $ python3 -m unittest discover tests - -Um einen besonderen Test laufen zu lassen, tippen Sie folgendes: - - $ python3 -m unittest tests.test_sort - -### Benutzen von pytest - -Zum ausführen aller Tests: - - $ python3 -m pytest tests - -## Install - -Wenn Sie das Projekt installieren wollen, um es als Module in Ihren Projekten nutzen zu können. Dann tippen Sie unten stehende Befehlzeile in die Kommandozeile: - - $ pip3 install git+https://github.com/keon/algorithms - -Sie können die Installation testen in dem Sie unten stehenden Code in eine Datei packen und ausführen. - -```python3 -from sort import merge_sort - -if __name__ == "__main__": - my_list = [1, 8, 3, 5, 6] - my_list = merge_sort.merge_sort(my_list) - print(my_list) -``` - -## Uninstall - -Um das Projekt zu deinstallieren tippen Sie folgendes: - - $ pip3 uninstall -y algorithms - - -## Liste von Implementierungen - -- [arrays](arrays) - - [delete_nth](arrays/delete_nth.py) - - [flatten](arrays/flatten.py) - - [garage](arrays/garage.py) - - [josephus_problem](arrays/josephus_problem.py) - - [longest_non_repeat](arrays/longest_non_repeat.py/) - - [merge_intervals](arrays/merge_intervals.py) - - [missing_ranges](arrays/missing_ranges.py) - - [plus_one](arrays/plus_one.py) -    - [rotate_array](arrays/rotate_array.py) - - [summary_ranges](arrays/summary_ranges.py) - - [three_sum](arrays/three_sum.py) - - [two_sum](arrays/two_sum.py) - - [move_zeros_to_end](arrays/move_zeros_to_end.py) -- [backtrack](backtrack) - - [general_solution.md](backtrack/) - - [anagram](backtrack/anagram.py) - - [array_sum_combinations](backtrack/array_sum_combinations.py) - - [combination_sum](backtrack/combination_sum.py) - - [expression_add_operators](backtrack/expression_add_operators.py) - - [factor_combinations](backtrack/factor_combinations.py) - - [generate_abbreviations](backtrack/generate_abbreviations.py) - - [generate_parenthesis](backtrack/generate_parenthesis.py) - - [letter_combination](backtrack/letter_combination.py) - - [palindrome_partitioning](backtrack/palindrome_partitioning.py) - - [pattern_match](backtrack/pattern_match.py) - - [permute](backtrack/permute.py) - - [permute_unique](backtrack/permute_unique.py) - - [subsets](backtrack/subsets.py) - - [subsets_unique](backtrack/subsets_unique.py) -- [bfs](bfs) - - [shortest_distance_from_all_buildings](bfs/shortest_distance_from_all_buildings.py) - - [word_ladder](bfs/word_ladder.py) -- [bit](bit) - - [bytes_int_conversion](bit/bytes_int_conversion.py) - - [count_ones](bit/count_ones.py) - - [find_missing_number](bit/find_missing_number.py) - - [power_of_two](bit/power_of_two.py) - - [reverse_bits](bit/reverse_bits.py) - - [single_number](bit/single_number.py) - - [single_number2](bit/single_number2.py) - - [single_number3](bit/single_number3.py) - - [subsets](bit/subsets.py) - - [add_bitwise_operator](bit/add_bitwise_operator.py) - - [bit_operation](bit/bit_operation.py) - - [swap_pair](bit/swap_pair.py) - - [find_difference](bit/find_difference.py) - - [has_alternative_bit](bit/has_alternative_bit.py) - - [insert_bit](bit/insert_bit.py) - - [remove_bit](bit/remove_bit.py) -- [calculator](calculator) - - [math_parser](calculator/math_parser.py) -- [dfs](dfs) - - [all_factors](dfs/all_factors.py) - - [count_islands](dfs/count_islands.py) - - [pacific_atlantic](dfs/pacific_atlantic.py) - - [sudoku_solver](dfs/sudoku_solver.py) - - [walls_and_gates](dfs/walls_and_gates.py) -- [dp](dp) - - [buy_sell_stock](dp/buy_sell_stock.py) - - [climbing_stairs](dp/climbing_stairs.py) - - [coin_change](dp/coin_change.py) - - [combination_sum](dp/combination_sum.py) - - [egg_drop](dp/egg_drop.py) - - [house_robber](dp/house_robber.py) - - [job_scheduling](dp/job_scheduling.py) - - [knapsack](dp/knapsack.py) - - [longest_increasing](dp/longest_increasing.py) - - [matrix_chain_order](dp/matrix_chain_order.py) - - [max_product_subarray](dp/max_product_subarray.py) - - [max_subarray](dp/max_subarray.py) - - [min_cost_path](dp/min_cost_path.py) - - [num_decodings](dp/num_decodings.py) - - [regex_matching](dp/regex_matching.py) - - [rod_cut](dp/rod_cut.py) - - [word_break](dp/word_break.py) - - [fibonacci](dp/fib.py) -- [graph](graph) - - [strongly_connected](graph/checkDiGraphStronglyConnected.py) - - [clone_graph](graph/clone_graph.py) - - [cycle_detection](graph/cycle_detection.py) - - [find_all_cliques](graph/find_all_cliques.py) - - [find_path](graph/find_path.py) - - [graph](graph/graph.py) - - [markov_chain](graph/markov_chain.py) - - [minimum_spanning_tree](graph/minimum_spanning_tree.py) - - [satisfiability](graph/satisfiability.py) - - [tarjan](graph/tarjan.py) - - [traversal](graph/traversal.py) -- [heap](heap) - - [merge_sorted_k_lists](heap/merge_sorted_k_lists.py) - - [skyline](heap/skyline.py) - - [sliding_window_max](heap/sliding_window_max.py) - - [binary_heap](heap/binary_heap.py) -- [linkedlist](linkedlist) - - [add_two_numbers](linkedlist/add_two_numbers.py) - - [copy_random_pointer](linkedlist/copy_random_pointer.py) - - [delete_node](linkedlist/delete_node.py) - - [first_cyclic_node](linkedlist/first_cyclic_node.py) - - [is_cyclic](linkedlist/is_cyclic.py) - - [is_palindrome](linkedlist/is_palindrome.py) - - [kth_to_last](linkedlist/kth_to_last.py) - - [linkedlist](linkedlist/linkedlist.py) - - [remove_duplicates](linkedlist/remove_duplicates.py) - - [reverse](linkedlist/reverse.py) - - [rotate_list](linkedlist/rotate_list.py) - - [swap_in_pairs](linkedlist/swap_in_pairs.py) - - [is_sorted](linkedlist/is_sorted.py) - - [remove_range](linkedlist/remove_range.py) -- [map](map) - - [hashtable](map/hashtable.py) - - [separate_chaining_hashtable](map/separate_chaining_hashtable.py) - - [longest_common_subsequence](map/longest_common_subsequence.py) - - [randomized_set](map/randomized_set.py) - - [valid_sudoku](map/valid_sudoku.py) -- [maths](maths) - - [base_conversion](maths/base_conversion.py) - - [extended_gcd](maths/extended_gcd.py) - - [gcd/lcm](maths/gcd.py) - - [generate_strobogrammtic](maths/generate_strobogrammtic.py) - - [is_strobogrammatic](maths/is_strobogrammatic.py) - - [next_bigger](maths/next_bigger.py) - - [next_perfect_square](maths/next_perfect_square.py) - - [nth_digit](maths/nth_digit.py) - - [prime_check](maths/prime_check.py) - - [primes_sieve_of_eratosthenes](maths/primes_sieve_of_eratosthenes.py) - - [pythagoras](maths/pythagoras.py) - - [rabin_miller](maths/rabin_miller.py) - - [rsa](maths/rsa.py) - - [sqrt_precision_factor](maths/sqrt_precision_factor.py) - - [summing_digits](maths/summing_digits.py) -- [matrix](matrix) - - [sudoku_validator](matrix/sudoku_validator.py) - - [bomb_enemy](matrix/bomb_enemy.py) - - [copy_transform](matrix/copy_transform.py) - - [count_paths](matrix/count_paths.py) - - [matrix_rotation.txt](matrix/matrix_rotation.txt) - - [rotate_image](matrix/rotate_image.py) - - [search_in_sorted_matrix](matrix/search_in_sorted_matrix.py) - - [sparse_dot_vector](matrix/sparse_dot_vector.py) - - [sparse_mul](matrix/sparse_mul.py) - - [spiral_traversal](matrix/spiral_traversal.py) -- [queues](queues) - - [max_sliding_window](queues/max_sliding_window.py) - - [moving_average](queues/moving_average.py) - - [queue](queues/queue.py) - - [reconstruct_queue](queues/reconstruct_queue.py) - - [zigzagiterator](queues/zigzagiterator.py) -- [search](search) - - [binary_search](search/binary_search.py) - - [first_occurance](search/first_occurance.py) - - [last_occurance](search/last_occurance.py) - - [search_insert](search/search_insert.py) - - [two_sum](search/two_sum.py) - - [search_range](search/search_range.py) - - [find_min_rotate](search/find_min_rotate.py) - - [search_rotate](search/search_rotate.py) -- [set](set) - - [randomized_set](set/randomized_set.py) - - [set_covering](set/set_covering.py) -- [sort](sort) - - [bubble_sort](sort/bubble_sort.py) - - [comb_sort](sort/comb_sort.py) - - [counting_sort](sort/counting_sort.py) - - [heap_sort](sort/heap_sort.py) - - [insertion_sort](sort/insertion_sort.py) - - [meeting_rooms](sort/meeting_rooms.py) - - [merge_sort](sort/merge_sort.py) - - [quick_sort](sort/quick_sort.py) - - [selection_sort](sort/selection_sort.py) - - [sort_colors](sort/sort_colors.py) - - [topsort](sort/topsort.py) - - [wiggle_sort](sort/wiggle_sort.py) -- [stack](stack) - - [longest_abs_path](stack/longest_abs_path.py) - - [simplify_path](stack/simplify_path.py) - - [stack](stack/stack.py) - - [valid_parenthesis](stack/valid_parenthesis.py) - - [stutter](stack/stutter.py) - - [switch_pairs](stack/switch_pairs.py) - - [is_consecutive](stack/is_consecutive.py) - - [remove_min](stack/remove_min.py) - - [is_sorted](stack/is_sorted.py) -- [strings](strings) - - [fizzbuzz](strings/fizzbuzz.py) - - [delete_reoccurring_characters](strings/delete_reoccurring_characters.py) - - [strip_url_params](strings/strip_url_params.py) - - [validate_coordinates](strings/validate_coordinates.py) - - [domain_extractor](strings/domain_extractor.py) - - [merge_string_checker](strings/merge_string_checker.py) - - [add_binary](strings/add_binary.py) - - [breaking_bad](strings/breaking_bad.py) - - [decode_string](strings/decode_string.py) - - [encode_decode](strings/encode_decode.py) - - [group_anagrams](strings/group_anagrams.py) - - [int_to_roman](strings/int_to_roman.py) - - [is_palindrome](strings/is_palindrome.py) - - [license_number](strings/license_number.py) - - [make_sentence](strings/make_sentence.py) - - [multiply_strings](strings/multiply_strings.py) - - [one_edit_distance](strings/one_edit_distance.py) - - [rabin_karp](strings/rabin_karp.py) - - [reverse_string](strings/reverse_string.py) - - [reverse_vowel](strings/reverse_vowel.py) - - [reverse_words](strings/reverse_words.py) - - [roman_to_int](strings/roman_to_int.py) - - [word_squares](strings/word_squares.py) -- [tree](tree) - - [bst](tree/tree/bst) - - [array2bst](tree/bst/array2bst.py) - - [bst_closest_value](tree/bst/bst_closest_value.py) - - [BSTIterator](tree/bst/BSTIterator.py) - - [delete_node](tree/bst/delete_node.py) - - [is_bst](tree/bst/is_bst.py) - - [kth_smallest](tree/bst/kth_smallest.py) - - [lowest_common_ancestor](tree/bst/lowest_common_ancestor.py) - - [predecessor](tree/bst/predecessor.py) - - [serialize_deserialize](tree/bst/serialize_deserialize.py) - - [successor](tree/bst/successor.py) - - [unique_bst](tree/bst/unique_bst.py) - - [depth_sum](tree/bst/depth_sum.py) - - [count_left_node](tree/bst/count_left_node.py) - - [num_empty](tree/bst/num_empty.py) - - [height](tree/bst/height.py) - - [red_black_tree](tree/red_black_tree) - - [red_black_tree](tree/red_black_tree/red_black_tree.py) - - [segment_tree](tree/segment_tree) - - [segment_tree](tree/segment_tree/segment_tree.py) - - [traversal](tree/traversal) - - [inorder](tree/traversal/inorder.py) - - [level_order](tree/traversal/level_order.py) - - [zigzag](tree/traversal/zigzag.py) - - [trie](tree/trie) - - [add_and_search](tree/trie/add_and_search.py) - - [trie](tree/trie/trie.py) - - [binary_tree_paths](tree/binary_tree_paths.py) - - [bintree2list](tree/bintree2list.py) - - [deepest_left](tree/deepest_left.py) - - [invert_tree](tree/invert_tree.py) - - [is_balanced](tree/is_balanced.py) - - [is_subtree](tree/is_subtree.py) - - [is_symmetric](tree/is_symmetric.py) - - [longest_consecutive](tree/longest_consecutive.py) - - [lowest_common_ancestor](tree/lowest_common_ancestor.py) - - [max_height](tree/max_height.py) - - [max_path_sum](tree/max_path_sum.py) - - [min_height](tree/min_height.py) - - [path_sum](tree/path_sum.py) - - [path_sum2](tree/path_sum2.py) - - [pretty_print](tree/pretty_print.py) - - [same_tree](tree/same_tree.py) - - [tree](tree/tree.py) -- [union-find](union-find) - - [count_islands](union-find/count_islands.py) - -## Mitwirkende - -Das Projekt wird von folgenden Personen betreut. - -* [Keon Kim](https://github.com/keon) -* [Rahul Goswami](https://github.com/goswami-rahul) -* [Christian Bender](https://github.com/christianbender) -* [Ankit Agarwal](https://github.com/ankit167) -* [Hai Hoang Dang](https://github.com/danghai) -* [Saad](https://github.com/SaadBenn) - -Und danke an alle [Contributors](https://github.com/keon/algorithms/graphs/contributors) -die geholfen haben das Projekt aufzubauen! diff --git a/arrays/__init__.py b/algorithms/__init__.py similarity index 100% rename from arrays/__init__.py rename to algorithms/__init__.py diff --git a/algorithms/arrays/__init__.py b/algorithms/arrays/__init__.py new file mode 100644 index 000000000..2632ca1c7 --- /dev/null +++ b/algorithms/arrays/__init__.py @@ -0,0 +1,19 @@ +from .delete_nth import * +from .flatten import * +from .garage import * +from .josephus import * +from .longest_non_repeat import * +from .max_ones_index import * +from .merge_intervals import * +from .missing_ranges import * +from .move_zeros import * +from .plus_one import * +from .rotate import * +from .summarize_ranges import * +from .three_sum import * +from .trimmean import * +from .top_1 import * +from .two_sum import * +from .limit import * +from .n_sum import * +from .remove_duplicates import * \ No newline at end of file diff --git a/arrays/delete_nth.py b/algorithms/arrays/delete_nth.py similarity index 100% rename from arrays/delete_nth.py rename to algorithms/arrays/delete_nth.py diff --git a/arrays/flatten.py b/algorithms/arrays/flatten.py similarity index 60% rename from arrays/flatten.py rename to algorithms/arrays/flatten.py index 2b047461a..52f4ec56d 100644 --- a/arrays/flatten.py +++ b/algorithms/arrays/flatten.py @@ -3,7 +3,7 @@ Given an array that may contain nested arrays, produce a single resultant array. """ -from collections import Iterable +from collections.abc import Iterable # return list @@ -11,10 +11,10 @@ def flatten(input_arr, output_arr=None): if output_arr is None: output_arr = [] for ele in input_arr: - if isinstance(ele, Iterable): - flatten(ele, output_arr) + if not isinstance(ele, str) and isinstance(ele, Iterable): + flatten(ele, output_arr) #tail-recursion else: - output_arr.append(ele) + output_arr.append(ele) #produce the result return output_arr @@ -25,7 +25,7 @@ def flatten_iter(iterable): returns generator which produces one dimensional output. """ for element in iterable: - if isinstance(element, Iterable): - yield from flatten(element) + if not isinstance(element, str) and isinstance(element, Iterable): + yield from flatten_iter(element) else: yield element diff --git a/arrays/garage.py b/algorithms/arrays/garage.py similarity index 56% rename from arrays/garage.py rename to algorithms/arrays/garage.py index d590343b7..474655abd 100644 --- a/arrays/garage.py +++ b/algorithms/arrays/garage.py @@ -34,14 +34,14 @@ def garage(initial, final): - initial = initial[::] # create a copy to prevent changes in original 'initial'. + initial = initial[::] # prevent changes in original 'initial' + seq = [] # list of each step in sequence steps = 0 - seq = [] # list of each step in sequence while initial != final: zero = initial.index(0) - if zero != final.index(0): - car_to_move = final[zero] - pos = initial.index(car_to_move) + if zero != final.index(0): # if zero isn't where it should be, + car_to_move = final[zero] # what should be where zero is, + pos = initial.index(car_to_move) # and where is it? initial[zero], initial[pos] = initial[pos], initial[zero] else: for i in range(len(initial)): @@ -51,4 +51,18 @@ def garage(initial, final): seq.append(initial[::]) steps += 1 - return steps, seq + return steps, seq + # e.g.: 4, [{0, 2, 3, 1, 4}, {2, 0, 3, 1, 4}, + # {2, 3, 0, 1, 4}, {0, 3, 2, 1, 4}] + +""" +thus: +1 2 3 0 4 -- zero = 3, true, car_to_move = final[3] = 1, + pos = initial.index(1) = 0, switched [0], [3] +0 2 3 1 4 -- zero = 0, f, initial[1] != final[1], switched 0,1 +2 0 3 1 4 -- zero = 1, t, car_to_move = final[1] = 3, + pos = initial.index(3) = 2, switched [1], [2] +2 3 0 1 4 -- zero = 2, t, car_to_move = final[2] = 2, + pos = initial.index(2) = 0, switched [0], [2] +0 3 2 1 4 -- initial == final +""" \ No newline at end of file diff --git a/arrays/josephus_problem.py b/algorithms/arrays/josephus.py similarity index 77% rename from arrays/josephus_problem.py rename to algorithms/arrays/josephus.py index f77c48a5c..7805a388d 100644 --- a/arrays/josephus_problem.py +++ b/algorithms/arrays/josephus.py @@ -9,12 +9,11 @@ Output: 369485271 """ - def josephus(int_list, skip): - skip = skip - 1 # list starts with 0 index + skip = skip - 1 # list starts with 0 index idx = 0 len_list = (len(int_list)) while len_list > 0: - idx = (skip+idx) % len_list # hash index to every 3rd + idx = (skip + idx) % len_list # hash index to every 3rd yield int_list.pop(idx) len_list -= 1 diff --git a/algorithms/arrays/limit.py b/algorithms/arrays/limit.py new file mode 100644 index 000000000..57f6561f8 --- /dev/null +++ b/algorithms/arrays/limit.py @@ -0,0 +1,25 @@ +""" +Sometimes you need to limit array result to use. Such as you only need the + value over 10 or, you need value under than 100. By use this algorithms, you + can limit your array to specific value + +If array, Min, Max value was given, it returns array that contains values of + given array which was larger than Min, and lower than Max. You need to give + 'unlimit' to use only Min or Max. + +ex) limit([1,2,3,4,5], None, 3) = [1,2,3] + +Complexity = O(n) +""" + +# tl:dr -- array slicing by value +def limit(arr, min_lim=None, max_lim=None): + if len(arr) == 0: + return arr + + if min_lim is None: + min_lim = min(arr) + if max_lim is None: + max_lim = max(arr) + + return list(filter(lambda x: (min_lim <= x <= max_lim), arr)) diff --git a/algorithms/arrays/longest_non_repeat.py b/algorithms/arrays/longest_non_repeat.py new file mode 100644 index 000000000..b25a952ca --- /dev/null +++ b/algorithms/arrays/longest_non_repeat.py @@ -0,0 +1,109 @@ +""" +Given a string, find the length of the longest substring +without repeating characters. + +Examples: +Given "abcabcbb", the answer is "abc", which the length is 3. +Given "bbbbb", the answer is "b", with the length of 1. +Given "pwwkew", the answer is "wke", with the length of 3. +Note that the answer must be a substring, +"pwke" is a subsequence and not a substring. +""" + + +def longest_non_repeat_v1(string): + """ + Find the length of the longest substring + without repeating characters. + """ + if string is None: + return 0 + dict = {} + max_length = 0 + j = 0 + for i in range(len(string)): + if string[i] in dict: + j = max(dict[string[i]], j) + dict[string[i]] = i + 1 + max_length = max(max_length, i - j + 1) + return max_length + +def longest_non_repeat_v2(string): + """ + Find the length of the longest substring + without repeating characters. + Uses alternative algorithm. + """ + if string is None: + return 0 + start, max_len = 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_len = max(max_len, index - start + 1) + used_char[char] = index + return max_len + +# get functions of above, returning the max_len and substring +def get_longest_non_repeat_v1(string): + """ + Find the length of the longest substring + without repeating characters. + Return max_len and the substring as a tuple + """ + if string is None: + return 0, '' + sub_string = '' + dict = {} + max_length = 0 + j = 0 + for i in range(len(string)): + if string[i] in dict: + j = max(dict[string[i]], j) + dict[string[i]] = i + 1 + if i - j + 1 > max_length: + max_length = i - j + 1 + sub_string = string[j: i + 1] + return max_length, sub_string + +def get_longest_non_repeat_v2(string): + """ + Find the length of the longest substring + without repeating characters. + Uses alternative algorithm. + Return max_len and the substring as a tuple + """ + if string is None: + return 0, '' + sub_string = '' + start, max_len = 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_len: + max_len = index - start + 1 + sub_string = string[start: index + 1] + used_char[char] = index + return max_len, sub_string + +def get_longest_non_repeat_v3(string): + """ + Find the length of the longest substring + without repeating characters. + Uses window sliding approach. + Return max_len and the substring as a tuple + """ + longest_substring = '' + seen = set() + start_idx = 0 + for i in range(len(string)): + while string[i] in seen: + seen.remove(string[start_idx]) + start_idx += 1 + seen.add(string[i]) + longest_substring = max(longest_substring, string[start_idx: i+1], key=len) + return len(longest_substring), longest_substring diff --git a/algorithms/arrays/max_ones_index.py b/algorithms/arrays/max_ones_index.py new file mode 100644 index 000000000..40abdf505 --- /dev/null +++ b/algorithms/arrays/max_ones_index.py @@ -0,0 +1,43 @@ +""" +Find the index of 0 to be replaced with 1 to get +longest continuous sequence +of 1s in a binary array. +Returns index of 0 to be +replaced with 1 to get longest +continuous sequence of 1s. +If there is no 0 in array, then +it returns -1. + +e.g. +let input array = [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1] +If we replace 0 at index 3 with 1, we get the longest continuous +sequence of 1s in the array. +So the function return => 3 +""" + + +def max_ones_index(arr): + + n = len(arr) + max_count = 0 + max_index = 0 + prev_zero = -1 + prev_prev_zero = -1 + + for curr in range(n): + + # If current element is 0, + # then calculate the difference + # between curr and prev_prev_zero + if arr[curr] == 0: + if curr - prev_prev_zero > max_count: + max_count = curr - prev_prev_zero + max_index = prev_zero + + prev_prev_zero = prev_zero + prev_zero = curr + + if n - prev_prev_zero > max_count: + max_index = prev_zero + + return max_index diff --git a/arrays/merge_intervals.py b/algorithms/arrays/merge_intervals.py similarity index 75% rename from arrays/merge_intervals.py rename to algorithms/arrays/merge_intervals.py index 606ca3427..fae4162f6 100644 --- a/arrays/merge_intervals.py +++ b/algorithms/arrays/merge_intervals.py @@ -1,15 +1,16 @@ """ -Given a collection of intervals, merge all overlapping intervals. +In mathematics, a (real) interval is a set of real + numbers with the property that any number that lies + between two numbers in the set is also included in the set. """ class Interval: """ - In mathematics, a (real) interval is a set of real - numbers with the property that any number that lies - between two numbers in the set is also included in the set. + A set of real numbers with methods to determine if other + numbers are included in the set. + Includes related methods to merge and print interval sets. """ - def __init__(self, start=0, end=0): self.start = start self.end = end @@ -44,7 +45,7 @@ def as_list(self): @staticmethod def merge(intervals): - """ Merges two intervals into one. """ + """ Merge two intervals into one. """ out = [] for i in sorted(intervals, key=lambda i: i.start): if out and i.start <= out[-1].end: @@ -55,17 +56,15 @@ def merge(intervals): @staticmethod def print_intervals(intervals): - """ - Prints out the intervals. - """ + """ Print out the intervals. """ res = [] for i in intervals: res.append(repr(i)) print("".join(res)) -def merge_v2(intervals): - """ Merges intervals in the form of list. """ +def merge_intervals(intervals): + """ Merge intervals in the form of a list. """ if intervals is None: return None intervals.sort(key=lambda i: i[0]) diff --git a/arrays/missing_ranges.py b/algorithms/arrays/missing_ranges.py similarity index 72% rename from arrays/missing_ranges.py rename to algorithms/arrays/missing_ranges.py index 68fcc4391..7f34bcd13 100644 --- a/arrays/missing_ranges.py +++ b/algorithms/arrays/missing_ranges.py @@ -3,7 +3,6 @@ Ex) [3, 5] lo=1 hi=10 => answer: [(1, 2), (4, 4), (6, 10)] """ - def missing_ranges(arr, lo, hi): res = [] @@ -17,7 +16,7 @@ def missing_ranges(arr, lo, hi): res.append((start, n-1)) start = n + 1 - if start <= hi: - res.append((start, hi)) + if start <= hi: # after done iterating thru array, + res.append((start, hi)) # append remainder to list return res diff --git a/arrays/move_zeros_to_end.py b/algorithms/arrays/move_zeros.py similarity index 62% rename from arrays/move_zeros_to_end.py rename to algorithms/arrays/move_zeros.py index b2acd759d..606afbfd5 100644 --- a/arrays/move_zeros_to_end.py +++ b/algorithms/arrays/move_zeros.py @@ -8,15 +8,19 @@ """ +# False == 0 is True def move_zeros(array): result = [] zeros = 0 for i in array: - if i is 0: # not using `not i` to avoid `False`, `[]`, etc. - zeros += 1 - else: - result.append(i) + if i == 0 and type(i) != bool: # not using `not i` to avoid `False`, `[]`, etc. + zeros += 1 + else: + result.append(i) result.extend([0] * zeros) return result + + +print(move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"])) \ No newline at end of file diff --git a/algorithms/arrays/n_sum.py b/algorithms/arrays/n_sum.py new file mode 100644 index 000000000..da7ee3666 --- /dev/null +++ b/algorithms/arrays/n_sum.py @@ -0,0 +1,140 @@ +""" +Given an array of n integers, are there elements a, b, .. , n in nums +such that a + b + .. + n = target? + +Find all unique n-tuplets in the array which gives the sum of target. + +Example: + basic: + Given: + n = 4 + nums = [1, 0, -1, 0, -2, 2] + target = 0, + return [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]] + + advanced: + Given: + n = 2 + nums = [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]] + target = -5 + def sum(a, b): + return [a[0] + b[1], a[1] + b[0]] + def compare(num, target): + if num[0] < target: + return -1 + elif if num[0] > target: + return 1 + else: + return 0 + return [[-9, 5], [8, 4]] +(TL:DR) because -9 + 4 = -5 +""" + + +def n_sum(n, nums, target, **kv): + """ + n: int + nums: list[object] + target: object + sum_closure: function, optional + Given two elements of nums, return sum of both. + compare_closure: function, optional + Given one object of nums and target, return -1, 1, or 0. + same_closure: function, optional + Given two object of nums, return bool. + return: list[list[object]] + + Note: + 1. type of sum_closure's return should be same + as type of compare_closure's first param + """ + + def sum_closure_default(a, b): + return a + b + + def compare_closure_default(num, target): + """ above, below, or right on? """ + if num < target: + return -1 + elif num > target: + return 1 + else: + return 0 + + def same_closure_default(a, b): + return a == b + + def n_sum(n, nums, target): + if n == 2: # want answers with only 2 terms? easy! + 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( # recursive call + n - 1, # a + nums[index + 1:], # b + target - num # c + ) # x = n_sum( a, b, c ) + ) # n_minus1_results = x + + n_minus1_results = ( + append_elem_to_each_list(num, n_minus1_results) + ) + results += n_minus1_results + return union(results) + + def two_sum(nums, target): + nums.sort() + lt = 0 + rt = len(nums) - 1 + results = [] + while lt < rt: + sum_ = sum_closure(nums[lt], nums[rt]) + flag = compare_closure(sum_, target) + if flag == -1: + lt += 1 + elif flag == 1: + rt -= 1 + else: + results.append(sorted([nums[lt], nums[rt]])) + lt += 1 + rt -= 1 + while (lt < len(nums) and + same_closure(nums[lt - 1], nums[lt])): + lt += 1 + while (0 <= rt and + same_closure(nums[rt], nums[rt + 1])): + rt -= 1 + return results + + def append_elem_to_each_list(elem, container): + results = [] + for elems in container: + elems.append(elem) + results.append(sorted(elems)) + return results + + def union(duplicate_results): + 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(n, nums, target) diff --git a/arrays/plus_one.py b/algorithms/arrays/plus_one.py similarity index 75% rename from arrays/plus_one.py rename to algorithms/arrays/plus_one.py index 9edb02fa5..473b6c7d0 100644 --- a/arrays/plus_one.py +++ b/algorithms/arrays/plus_one.py @@ -1,13 +1,13 @@ """ Given a non-negative number represented as an array of digits, -plus one to the number. +adding one to each numeral. -The digits are stored such that the most significant +The digits are stored big-endian, such that the most significant digit is at the head of the list. """ -def plus_one(digits): +def plus_one_v1(digits): """ :type digits: List[int] :rtype: List[int] @@ -41,8 +41,8 @@ def plus_one_v2(digits): def plus_one_v3(num_arr): - for idx, digit in reversed(list(enumerate(num_arr))): - num_arr[idx] = (num_arr[idx] + 1) % 10 - if num_arr[idx]: + 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 diff --git a/algorithms/arrays/remove_duplicates.py b/algorithms/arrays/remove_duplicates.py new file mode 100644 index 000000000..1c0bc0a06 --- /dev/null +++ b/algorithms/arrays/remove_duplicates.py @@ -0,0 +1,18 @@ +""" +This algorithm removes any duplicates from an array and returns a new array with those duplicates +removed. + +For example: + +Input: [1, 1 ,1 ,2 ,2 ,3 ,4 ,4 ,"hey", "hey", "hello", True, True] +Output: [1, 2, 3, 4, 'hey', 'hello'] +""" + +def remove_duplicates(array): + new_array = [] + + for item in array: + if item not in new_array: + new_array.append(item) + + return new_array \ No newline at end of file diff --git a/arrays/rotate_array.py b/algorithms/arrays/rotate.py similarity index 95% rename from arrays/rotate_array.py rename to algorithms/arrays/rotate.py index d90035b58..974b511f3 100644 --- a/arrays/rotate_array.py +++ b/algorithms/arrays/rotate.py @@ -21,7 +21,7 @@ def rotate_v1(array, k): """ array = array[:] n = len(array) - for i in range(k): + for i in range(k): # unused variable is not a problem temp = array[n - 1] for j in range(n-1, 0, -1): array[j] = array[j - 1] diff --git a/algorithms/arrays/summarize_ranges.py b/algorithms/arrays/summarize_ranges.py new file mode 100644 index 000000000..58de7421a --- /dev/null +++ b/algorithms/arrays/summarize_ranges.py @@ -0,0 +1,25 @@ +""" +Given a sorted integer array without duplicates, +return the summary of its ranges. + +For example, given [0, 1, 2, 4, 5, 7], return [(0, 2), (4, 5), (7, 7)]. +""" + + +from typing import List + +def summarize_ranges(array: List[int]) -> List[str]: + res = [] + if len(array) == 1: + return [str(array[0])] + it = iter(array) + start = end = next(it) + for num in it: + if num - end == 1: + end = num + else: + res.append((start, end) if start != end else (start,)) + start = end = num + res.append((start, end) if start != end else (start,)) + return [f"{r[0]}-{r[1]}" if len(r) > 1 else str(r[0]) for r in res] + diff --git a/arrays/three_sum.py b/algorithms/arrays/three_sum.py similarity index 91% rename from arrays/three_sum.py rename to algorithms/arrays/three_sum.py index 524b83edd..ccf3d2669 100644 --- a/arrays/three_sum.py +++ b/algorithms/arrays/three_sum.py @@ -1,6 +1,6 @@ """ -Given an array S of n integers, are there elements a, b, c in S -such that a + b + c = 0? +Given an array S of n integers, are there three distinct elements +a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero. Note: The solution set must not contain duplicate triplets. diff --git a/algorithms/arrays/top_1.py b/algorithms/arrays/top_1.py new file mode 100644 index 000000000..71ac6b042 --- /dev/null +++ b/algorithms/arrays/top_1.py @@ -0,0 +1,37 @@ +""" +This algorithm receives an array and returns most_frequent_value +Also, sometimes it is possible to have multiple 'most_frequent_value's, +so this function returns a list. This result can be used to find a +representative value in an array. + +This algorithm gets an array, makes a dictionary of it, + finds the most frequent count, and makes the result list. + +For example: top_1([1, 1, 2, 2, 3, 4]) will return [1, 2] + +(TL:DR) Get mathematical Mode +Complexity: O(n) +""" +def top_1(arr): + values = {} + #reserve each value which first appears on keys + #reserve how many time each value appears by index number on values + result = [] + f_val = 0 + + for i in arr: + if i in values: + values[i] += 1 + else: + values[i] = 1 + + f_val = max(values.values()) + + for i in values.keys(): + if values[i] == f_val: + result.append(i) + else: + continue + + return result + \ No newline at end of file diff --git a/algorithms/arrays/trimmean.py b/algorithms/arrays/trimmean.py new file mode 100644 index 000000000..3d330a0e3 --- /dev/null +++ b/algorithms/arrays/trimmean.py @@ -0,0 +1,24 @@ +""" +When make reliable means, we need to neglect best and worst values. +For example, when making average score on athletes we need this option. +So, this algorithm affixes some percentage to neglect when making mean. +For example, if you suggest 20%, it will neglect the best 10% of values +and the worst 10% of values. + +This algorithm takes an array and percentage to neglect. After sorted, +if index of array is larger or smaller than desired ratio, we don't +compute it. + +Compleity: O(n) +""" +def trimmean(arr, per): + ratio = per/200 + # /100 for easy calculation by *, and /2 for easy adaption to best and worst parts. + cal_sum = 0 + # sum value to be calculated to trimmean. + arr.sort() + neg_val = int(len(arr)*ratio) + arr = arr[neg_val:len(arr)-neg_val] + for i in arr: + cal_sum += i + return cal_sum/len(arr) diff --git a/arrays/two_sum.py b/algorithms/arrays/two_sum.py similarity index 100% rename from arrays/two_sum.py rename to algorithms/arrays/two_sum.py diff --git a/algorithms/automata/__init__.py b/algorithms/automata/__init__.py new file mode 100644 index 000000000..bef182c6f --- /dev/null +++ b/algorithms/automata/__init__.py @@ -0,0 +1 @@ +from .dfa import * diff --git a/algorithms/automata/dfa.py b/algorithms/automata/dfa.py new file mode 100644 index 000000000..34f73a4b4 --- /dev/null +++ b/algorithms/automata/dfa.py @@ -0,0 +1,17 @@ +def DFA(transitions, start, final, string): + + num = len(string) + num_final = len(final) + cur = start + + for i in range(num): + + if transitions[cur][string[i]] is None: + return False + else: + cur = transitions[cur][string[i]] + + for i in range(num_final): + if cur == final[i]: + return True + return False diff --git a/algorithms/backtrack/__init__.py b/algorithms/backtrack/__init__.py new file mode 100644 index 000000000..f8cdab753 --- /dev/null +++ b/algorithms/backtrack/__init__.py @@ -0,0 +1,15 @@ +from .add_operators import * +from .anagram import * +from .array_sum_combinations import * +from .combination_sum import * +from .factor_combinations import * +from .find_words import * +from .generate_abbreviations import * +from .generate_parenthesis import * +from .letter_combination import * +from .palindrome_partitioning import * +from .pattern_match import * +from .permute_unique import * +from .permute import * +from .subsets_unique import * +from .subsets import * diff --git a/algorithms/backtrack/add_operators.py b/algorithms/backtrack/add_operators.py new file mode 100644 index 000000000..7a847396c --- /dev/null +++ b/algorithms/backtrack/add_operators.py @@ -0,0 +1,45 @@ +""" +Given a string that contains only digits 0-9 and a target value, +return all possibilities to add binary operators (not unary) +, -, or * +between the digits so they prevuate to the target value. + +Examples: +"123", 6 -> ["1+2+3", "1*2*3"] +"232", 8 -> ["2*3+2", "2+3*2"] +"105", 5 -> ["1*0+5","10-5"] +"00", 0 -> ["0+0", "0-0", "0*0"] +"3456237490", 9191 -> [] +""" + + +def add_operators(num, target): + """ + :type num: str + :type target: int + :rtype: List[str] + """ + + def dfs(res, path, num, target, pos, prev, multed): + if pos == len(num): + if target == prev: + res.append(path) + return + for i in range(pos, len(num)): + if i != pos and num[pos] == '0': # all digits have to be used + break + cur = int(num[pos:i+1]) + if pos == 0: + dfs(res, path + str(cur), num, target, i+1, cur, cur) + else: + dfs(res, path + "+" + str(cur), num, target, + i+1, prev + cur, cur) + dfs(res, path + "-" + str(cur), num, target, + i+1, prev - cur, -cur) + dfs(res, path + "*" + str(cur), num, target, + i+1, prev - multed + multed * cur, multed * cur) + + res = [] + if not num: + return res + dfs(res, "", num, target, 0, 0, 0) + return res diff --git a/algorithms/backtrack/anagram.py b/algorithms/backtrack/anagram.py new file mode 100644 index 000000000..df3f4ab57 --- /dev/null +++ b/algorithms/backtrack/anagram.py @@ -0,0 +1,22 @@ +""" +Given two strings, determine if they are equal after reordering. + +Examples: +"apple", "pleap" -> True +"apple", "cherry" -> False +""" + + +def anagram(s1, s2): + c1 = [0] * 26 + c2 = [0] * 26 + + for c in s1: + pos = ord(c)-ord('a') + c1[pos] = c1[pos] + 1 + + for c in s2: + pos = ord(c)-ord('a') + c2[pos] = c2[pos] + 1 + + return c1 == c2 diff --git a/algorithms/backtrack/array_sum_combinations.py b/algorithms/backtrack/array_sum_combinations.py new file mode 100644 index 000000000..b152e4d11 --- /dev/null +++ b/algorithms/backtrack/array_sum_combinations.py @@ -0,0 +1,84 @@ +""" +WAP to take one element from each of the array add it to the target sum. +Print all those three-element combinations. + +/* +A = [1, 2, 3, 3] +B = [2, 3, 3, 4] +C = [2, 3, 3, 4] +target = 7 +*/ + +Result: +[[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]] +""" +import itertools +from functools import partial + + +def array_sum_combinations(A, B, C, target): + + def over(constructed_sofar): + sum = 0 + to_stop, reached_target = False, False + for elem in constructed_sofar: + sum += elem + if sum >= target or len(constructed_sofar) >= 3: + to_stop = True + if sum == target and 3 == len(constructed_sofar): + reached_target = True + return to_stop, reached_target + + def construct_candidates(constructed_sofar): + array = A + if 1 == len(constructed_sofar): + array = B + elif 2 == len(constructed_sofar): + array = C + return array + + def backtrack(constructed_sofar=[], res=[]): + to_stop, reached_target = over(constructed_sofar) + if to_stop: + if reached_target: + res.append(constructed_sofar) + return + candidates = construct_candidates(constructed_sofar) + + for candidate in candidates: + constructed_sofar.append(candidate) + backtrack(constructed_sofar[:], res) + constructed_sofar.pop() + + res = [] + backtrack([], res) + return res + + +def unique_array_sum_combinations(A, B, C, target): + """ + 1. Sort all the arrays - a,b,c. - This improves average time complexity. + 2. If c[i] < Sum, then look for Sum - c[i] in array a and b. + When pair found, insert c[i], a[j] & b[k] into the result list. + This can be done in O(n). + 3. Keep on doing the above procedure while going through complete c array. + + Complexity: O(n(m+p)) + """ + def check_sum(n, *nums): + if sum(x for x in nums) == n: + return (True, nums) + else: + return (False, nums) + + pro = itertools.product(A, B, C) + func = partial(check_sum, target) + sums = list(itertools.starmap(func, pro)) + + res = set() + for s in sums: + if s[0] is True and s[1] not in res: + res.add(s[1]) + + return list(res) diff --git a/backtrack/combination_sum.py b/algorithms/backtrack/combination_sum.py similarity index 58% rename from backtrack/combination_sum.py rename to algorithms/backtrack/combination_sum.py index 8c6747681..4d4dfb176 100644 --- a/backtrack/combination_sum.py +++ b/algorithms/backtrack/combination_sum.py @@ -16,18 +16,18 @@ """ -def combination_sum(self, candidates, target): +def combination_sum(candidates, target): + + def dfs(nums, target, index, path, res): + if target < 0: + return # backtracking + if target == 0: + res.append(path) + return + for i in range(index, len(nums)): + dfs(nums, target-nums[i], i, path+[nums[i]], res) + res = [] candidates.sort() - self.dfs(candidates, target, 0, [], res) + dfs(candidates, target, 0, [], res) return res - - -def dfs(self, nums, target, index, path, res): - if target < 0: - return # backtracking - if target == 0: - res.append(path) - return - for i in range(index, len(nums)): - self.dfs(nums, target-nums[i], i, path+[nums[i]], res) diff --git a/backtrack/factor_combinations.py b/algorithms/backtrack/factor_combinations.py similarity index 88% rename from backtrack/factor_combinations.py rename to algorithms/backtrack/factor_combinations.py index d4de7288f..3240b05a8 100644 --- a/backtrack/factor_combinations.py +++ b/algorithms/backtrack/factor_combinations.py @@ -3,7 +3,8 @@ 8 = 2 x 2 x 2; = 2 x 4. -Write a function that takes an integer n and return all possible combinations of its factors. +Write a function that takes an integer n +and return all possible combinations of its factors. Note: You may assume that n is always positive. @@ -49,7 +50,7 @@ def get_factors(n): # Recursive: -def get_factors_recur(n): +def recursive_get_factors(n): def factor(n, i, combi, combis): while i * i <= n: diff --git a/algorithms/backtrack/find_words.py b/algorithms/backtrack/find_words.py new file mode 100644 index 000000000..da6b6347a --- /dev/null +++ b/algorithms/backtrack/find_words.py @@ -0,0 +1,73 @@ +''' +Given a matrix of words and a list of words to search, +return a list of words that exists in the board +This is Word Search II on LeetCode + +board = [ + ['o','a','a','n'], + ['e','t','a','e'], + ['i','h','k','r'], + ['i','f','l','v'] + ] + +words = ["oath","pea","eat","rain"] +''' + + +def find_words(board, words): + + def backtrack(board, i, j, trie, pre, used, result): + ''' + backtrack tries to build each words from + the board and return all words found + + @param: board, the passed in board of characters + @param: i, the row index + @param: j, the column index + @param: trie, a trie of the passed in words + @param: pre, a buffer of currently build string that differs + by recursion stack + @param: used, a replica of the board except in booleans + to state whether a character has been used + @param: result, the resulting set that contains all words found + + @return: list of words found + ''' + + if '#' in trie: + result.add(pre) + + if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]): + return + + if not used[i][j] and board[i][j] in trie: + used[i][j] = True + backtrack(board, i+1, j, trie[board[i][j]], + pre+board[i][j], used, result) + backtrack(board, i, j+1, trie[board[i][j]], + pre+board[i][j], used, result) + backtrack(board, i-1, j, trie[board[i][j]], + pre+board[i][j], used, result) + backtrack(board, i, j-1, trie[board[i][j]], + pre+board[i][j], used, result) + used[i][j] = False + + # make a trie structure that is essentially dictionaries of dictionaries + # that map each character to a potential next character + trie = {} + for word in words: + curr_trie = trie + for char in word: + if char not in curr_trie: + curr_trie[char] = {} + curr_trie = curr_trie[char] + curr_trie['#'] = '#' + + # result is a set of found words since we do not want repeats + result = set() + used = [[False]*len(board[0]) for _ in range(len(board))] + + for i in range(len(board)): + for j in range(len(board[0])): + backtrack(board, i, j, trie, '', used, result) + return list(result) diff --git a/algorithms/backtrack/generate_abbreviations.py b/algorithms/backtrack/generate_abbreviations.py new file mode 100644 index 000000000..674df93af --- /dev/null +++ b/algorithms/backtrack/generate_abbreviations.py @@ -0,0 +1,26 @@ +""" +given input word, return the list of abbreviations. +ex) +word => ['word', 'wor1', 'wo1d', 'wo2', 'w1rd', 'w1r1', 'w2d', 'w3', '1ord', '1or1', '1o1d', '1o2', '2rd', '2r1', '3d', '4'] +""" + + +def generate_abbreviations(word): + + def backtrack(result, word, pos, count, cur): + if pos == len(word): + if count > 0: + cur += str(count) + result.append(cur) + return + + if count > 0: # add the current word + backtrack(result, word, pos+1, 0, cur+str(count)+word[pos]) + else: + backtrack(result, word, pos+1, 0, cur+word[pos]) + # skip the current word + backtrack(result, word, pos+1, count+1, cur) + + result = [] + backtrack(result, word, 0, 0, "") + return result diff --git a/algorithms/backtrack/generate_parenthesis.py b/algorithms/backtrack/generate_parenthesis.py new file mode 100644 index 000000000..073cec96a --- /dev/null +++ b/algorithms/backtrack/generate_parenthesis.py @@ -0,0 +1,43 @@ +""" +Given n pairs of parentheses, write a function to generate +all combinations of well-formed parentheses. + +For example, given n = 3, a solution set is: + +[ + "((()))", + "(()())", + "(())()", + "()(())", + "()()()" +] +""" + + +def generate_parenthesis_v1(n): + def add_pair(res, s, left, right): + if left == 0 and right == 0: + res.append(s) + return + if right > 0: + add_pair(res, s + ")", left, right - 1) + if left > 0: + add_pair(res, s + "(", left - 1, right + 1) + + res = [] + add_pair(res, "", n, 0) + return res + + +def generate_parenthesis_v2(n): + def add_pair(res, s, left, right): + if left == 0 and right == 0: + res.append(s) + if left > 0: + add_pair(res, s + "(", left - 1, right) + if right > 0 and left < right: + add_pair(res, s + ")", left, right - 1) + + res = [] + add_pair(res, "", n, n) + return res diff --git a/backtrack/letter_combination.py b/algorithms/backtrack/letter_combination.py similarity index 75% rename from backtrack/letter_combination.py rename to algorithms/backtrack/letter_combination.py index f4a4c4e0b..09f8af826 100644 --- a/backtrack/letter_combination.py +++ b/algorithms/backtrack/letter_combination.py @@ -2,12 +2,22 @@ Given a digit string, return all possible letter combinations that the number could represent. +A mapping of digit to letters (just like on the telephone buttons) is given below: +2: "abc" +3: "def" +4: "ghi" +5: "jkl" +6: "mno" +7: "pqrs" +8: "tuv" +9: "wxyz" + Input:Digit string "23" Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. """ -def letter_combinations(digits:"str")->"List[str]": +def letter_combinations(digits): if digits == "": return [] kmaps = { @@ -28,8 +38,3 @@ def letter_combinations(digits:"str")->"List[str]": tmp.append(an + char) ans = tmp return ans - - -if __name__ == "__main__": - digit_string = "23" - print(letter_combinations(digit_string)) diff --git a/algorithms/backtrack/palindrome_partitioning.py b/algorithms/backtrack/palindrome_partitioning.py new file mode 100644 index 000000000..10f1da94d --- /dev/null +++ b/algorithms/backtrack/palindrome_partitioning.py @@ -0,0 +1,45 @@ +""" It looks like you need to be looking not for all palindromic substrings, +but rather for all the ways you can divide the input string +up into palindromic substrings. +(There's always at least one way, +since one-character substrings are always palindromes.) + +ex) +'abcbab' => [['abcba', 'b'], ['a', 'bcb', 'a', 'b'], ['a', 'b', 'c', 'bab'], ['a', 'b', 'c', 'b', 'a', 'b']] +""" + + +def palindromic_substrings(s): + if not s: + return [[]] + results = [] + for i in range(len(s), 0, -1): + sub = s[:i] + if sub == sub[::-1]: + for rest in palindromic_substrings(s[i:]): + results.append([sub] + rest) + return results + + +""" +There's two loops. +The outer loop checks each length of initial substring +(in descending length order) to see if it is a palindrome. +If so, it recurses on the rest of the string and loops over the returned +values, adding the initial substring to +each item before adding it to the results. +""" + + +def palindromic_substrings_iter(s): + """ + A slightly more Pythonic approach with a recursive generator + """ + if not s: + yield [] + return + for i in range(len(s), 0, -1): + sub = s[:i] + if sub == sub[::-1]: + for rest in palindromic_substrings_iter(s[i:]): + yield [sub] + rest diff --git a/algorithms/backtrack/pattern_match.py b/algorithms/backtrack/pattern_match.py new file mode 100644 index 000000000..03a23a4e9 --- /dev/null +++ b/algorithms/backtrack/pattern_match.py @@ -0,0 +1,42 @@ +""" +Given a pattern and a string str, +find if str follows the same pattern. + +Here follow means a full match, such that there is a bijection between +a letter in pattern and a non-empty substring in str. + +Examples: +pattern = "abab", str = "redblueredblue" should return true. +pattern = "aaaa", str = "asdasdasdasd" should return true. +pattern = "aabb", str = "xyzabcxzyabc" should return false. +Notes: +You may assume both pattern and str contains only lowercase letters. +""" + + +def pattern_match(pattern, string): + """ + :type pattern: str + :type string: str + :rtype: bool + """ + def backtrack(pattern, string, dic): + + if len(pattern) == 0 and len(string) > 0: + return False + + if len(pattern) == len(string) == 0: + return True + + for end in range(1, len(string)-len(pattern)+2): + if pattern[0] not in dic and string[:end] not in dic.values(): + dic[pattern[0]] = string[:end] + if backtrack(pattern[1:], string[end:], dic): + return True + del dic[pattern[0]] + elif pattern[0] in dic and dic[pattern[0]] == string[:end]: + if backtrack(pattern[1:], string[end:], dic): + return True + return False + + return backtrack(pattern, string, {}) diff --git a/algorithms/backtrack/permute.py b/algorithms/backtrack/permute.py new file mode 100644 index 000000000..27c7d972f --- /dev/null +++ b/algorithms/backtrack/permute.py @@ -0,0 +1,54 @@ +""" +Given a collection of distinct numbers, return all possible permutations. + +For example, +[1,2,3] have the following permutations: +[ + [1,2,3], + [1,3,2], + [2,1,3], + [2,3,1], + [3,1,2], + [3,2,1] +] +""" + + +def permute(elements): + """ + returns a list with the permuations. + """ + if len(elements) <= 1: + return [elements] + else: + tmp = [] + for perm in permute(elements[1:]): + for i in range(len(elements)): + tmp.append(perm[:i] + elements[0:1] + perm[i:]) + return tmp + + +def permute_iter(elements): + """ + iterator: returns a perumation by each call. + """ + 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:] + + +# DFS Version +def permute_recursive(nums): + def dfs(res, nums, path): + if not nums: + res.append(path) + for i in range(len(nums)): + print(nums[:i]+nums[i+1:]) + dfs(res, nums[:i]+nums[i+1:], path+[nums[i]]) + + res = [] + dfs(res, nums, []) + return res diff --git a/algorithms/backtrack/permute_unique.py b/algorithms/backtrack/permute_unique.py new file mode 100644 index 000000000..3b82e2c46 --- /dev/null +++ b/algorithms/backtrack/permute_unique.py @@ -0,0 +1,25 @@ +""" +Given a collection of numbers that might contain duplicates, +return all possible unique permutations. + +For example, +[1,1,2] have the following unique permutations: +[ + [1,1,2], + [1,2,1], + [2,1,1] +] +""" + + +def permute_unique(nums): + perms = [[]] + for n in nums: + new_perms = [] + for l in perms: + for i in range(len(l)+1): + new_perms.append(l[:i]+[n]+l[i:]) + if i < len(l) and l[i] == n: + break # handles duplication + perms = new_perms + return perms diff --git a/algorithms/backtrack/subsets.py b/algorithms/backtrack/subsets.py new file mode 100644 index 000000000..56eef44e8 --- /dev/null +++ b/algorithms/backtrack/subsets.py @@ -0,0 +1,59 @@ +""" +Given a set of distinct integers, nums, return all possible subsets. + +Note: The solution set must not contain duplicate subsets. + +For example, +If nums = [1,2,3], a solution is: + +[ + [3], + [1], + [2], + [1,2,3], + [1,3], + [2,3], + [1,2], + [] +] +""" + + +def subsets(nums): + """ + O(2**n) + """ + def backtrack(res, nums, stack, pos): + if pos == len(nums): + res.append(list(stack)) + else: + # take nums[pos] + stack.append(nums[pos]) + backtrack(res, nums, stack, pos+1) + stack.pop() + # dont take nums[pos] + backtrack(res, nums, stack, pos+1) + + res = [] + backtrack(res, nums, [], 0) + return res + + +""" +simplified backtrack + +def backtrack(res, nums, cur, pos): + if pos >= len(nums): + res.append(cur) + else: + backtrack(res, nums, cur+[nums[pos]], pos+1) + backtrack(res, nums, cur, pos+1) +""" + + +# Iteratively +def subsets_v2(nums): + res = [[]] + for num in sorted(nums): + res += [item+[num] for item in res] + return res diff --git a/algorithms/backtrack/subsets_unique.py b/algorithms/backtrack/subsets_unique.py new file mode 100644 index 000000000..da73a25ca --- /dev/null +++ b/algorithms/backtrack/subsets_unique.py @@ -0,0 +1,37 @@ +""" +Given a collection of integers that might contain duplicates, nums, +return all possible subsets. + +Note: The solution set must not contain duplicate subsets. + +For example, +If nums = [1,2,2], a solution is: + +[ + [2], + [1], + [1,2,2], + [2,2], + [1,2], + [] +] +""" + + +def subsets_unique(nums): + + def backtrack(res, nums, stack, pos): + if pos == len(nums): + res.add(tuple(stack)) + else: + # take + stack.append(nums[pos]) + backtrack(res, nums, stack, pos+1) + stack.pop() + + # don't take + backtrack(res, nums, stack, pos+1) + + res = set() + backtrack(res, nums, [], 0) + return list(res) diff --git a/algorithms/bfs/__init__.py b/algorithms/bfs/__init__.py new file mode 100644 index 000000000..e61e7dbe3 --- /dev/null +++ b/algorithms/bfs/__init__.py @@ -0,0 +1,4 @@ +from .count_islands import * +from .maze_search import * +from .shortest_distance_from_all_buildings import * +from .word_ladder import * diff --git a/algorithms/bfs/count_islands.py b/algorithms/bfs/count_islands.py new file mode 100644 index 000000000..5c9d00637 --- /dev/null +++ b/algorithms/bfs/count_islands.py @@ -0,0 +1,65 @@ +""" +This is a bfs-version of counting-islands problem in dfs section. +Given a 2d grid map of '1's (land) and '0's (water), +count the number of islands. +An island is surrounded by water and is formed by +connecting adjacent lands horizontally or vertically. +You may assume all four edges of the grid are all surrounded by water. + +Example 1: +11110 +11010 +11000 +00000 +Answer: 1 + +Example 2: +11000 +11000 +00100 +00011 +Answer: 3 + +Example 3: +111000 +110000 +100001 +001101 +001100 +Answer: 3 + +Example 4: +110011 +001100 +000001 +111100 +Answer: 5 +""" + + +def count_islands(grid): + row = len(grid) + col = len(grid[0]) + + num_islands = 0 + visited = [[0] * col for i in range(row)] + directions = [[-1, 0], [1, 0], [0, -1], [0, 1]] + queue = [] + + 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.pop(0) + for k in range(len(directions)): + nx_x = x + directions[k][0] + nx_y = y + directions[k][1] + if nx_x >= 0 and nx_y >= 0 and nx_x < row and nx_y < col: + if 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 diff --git a/algorithms/bfs/maze_search.py b/algorithms/bfs/maze_search.py new file mode 100644 index 000000000..f7410d46e --- /dev/null +++ b/algorithms/bfs/maze_search.py @@ -0,0 +1,67 @@ +from collections import deque + +''' +BFS time complexity : O(|E| + |V|) +BFS space complexity : O(|E| + |V|) + +do BFS from (0,0) of the grid and get the minimum number of steps needed to get to the lower right column + +only step on the columns whose value is 1 + +if there is no path, it returns -1 + +Ex 1) +If grid is +[[1,0,1,1,1,1], + [1,0,1,0,1,0], + [1,0,1,0,1,1], + [1,1,1,0,1,1]], +the answer is: 14 + +Ex 2) +If grid is +[[1,0,0], + [0,1,1], + [0,1,1]], +the answer is: -1 +''' + +def maze_search(maze): + 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 w in range(width)] for h 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 + diff --git a/bfs/shortest_distance_from_all_buildings.py b/algorithms/bfs/shortest_distance_from_all_buildings.py similarity index 94% rename from bfs/shortest_distance_from_all_buildings.py rename to algorithms/bfs/shortest_distance_from_all_buildings.py index 48c41d063..4d4249cd2 100644 --- a/bfs/shortest_distance_from_all_buildings.py +++ b/algorithms/bfs/shortest_distance_from_all_buildings.py @@ -38,6 +38,3 @@ def bfs(grid, matrix, i, j, count): matrix[k][l][0] += step+1 matrix[k][l][1] = count+1 q.append((k, l, step+1)) - -grid = [[1,0,2,0,1],[0,0,0,0,0],[0,0,1,0,0]] -print(shortest_distance(grid)) diff --git a/bfs/word_ladder.py b/algorithms/bfs/word_ladder.py similarity index 70% rename from bfs/word_ladder.py rename to algorithms/bfs/word_ladder.py index 2fdbf294e..6767571eb 100644 --- a/bfs/word_ladder.py +++ b/algorithms/bfs/word_ladder.py @@ -13,13 +13,12 @@ word_list = ["hot","dot","dog","lot","log"] As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", return its length 5. -. + Note: Return -1 if there is no such transformation sequence. All words have the same length. All words contain only lowercase alphabetic characters. """ -import unittest def ladder_length(begin_word, end_word, word_list): @@ -71,29 +70,3 @@ def word_range(word): for c in [chr(x) for x in range(ord('a'), ord('z') + 1)]: if c != temp: yield word[:ind] + c + word[ind + 1:] - - -class TestSuite(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'])) - - -if __name__ == '__main__': - - unittest.main() diff --git a/algorithms/bit/__init__.py b/algorithms/bit/__init__.py new file mode 100644 index 000000000..fa31d7ed8 --- /dev/null +++ b/algorithms/bit/__init__.py @@ -0,0 +1,19 @@ +from .add_bitwise_operator import * +from .count_ones import * +from .find_missing_number import * +from .power_of_two import * +from .reverse_bits import * +from .single_number import * +from .single_number2 import * +from .single_number3 import * +from .subsets import * +from .bit_operation import * +from .swap_pair import * +from .find_difference import * +from .has_alternative_bit import * +from .insert_bit import * +from .remove_bit import * +from .count_flips_to_convert import * +from .flip_bit_longest_sequence import * +from .binary_gap import * +from .bytes_int_conversion import * \ No newline at end of file diff --git a/bit/add_bitwise_operator.py b/algorithms/bit/add_bitwise_operator.py similarity index 100% rename from bit/add_bitwise_operator.py rename to algorithms/bit/add_bitwise_operator.py diff --git a/algorithms/bit/binary_gap.py b/algorithms/bit/binary_gap.py new file mode 100644 index 000000000..eed32208e --- /dev/null +++ b/algorithms/bit/binary_gap.py @@ -0,0 +1,57 @@ +""" +Given a positive integer N, find and return the longest distance between two +consecutive 1' in the binary representation of N. +If there are not two consecutive 1's, return 0 + +For example: +Input: 22 +Output: 2 +Explanation: +22 in binary is 10110 +In the binary representation of 22, there are three ones, and two consecutive pairs of 1's. +The first consecutive pair of 1's have distance 2. +The second consecutive pair of 1's have distance 1. +The answer is the largest of these two distances, which is 2 +""" + + +# 原方法为 binary_gap,但通过实验发现算法有误,不论是什么数,输出值最多为2。 +# 改进方法为binary_gap_improved。 +# The original method is binary_gap, +# but the experimental results show that the algorithm seems to be wrong, +# regardless of the number, the output value is up to 2. +# The improved method is binary_gap_improved. +def binary_gap(N): + last = None + ans = 0 + index = 0 + while N != 0: + if N & 1: + if last is not None: + ans = max(ans, index - last) + last = index + index = index + 1 + N = N >> 1 + return ans + + +def binary_gap_improved(N): + last = None + ans = 0 + index = 0 + while N != 0: + tes = N & 1 + if tes: + if last is not None: + ans = max(ans, index - last + 1) + else: + last = index + else: + last = index + 1 + index = index + 1 + N = N >> 1 + return ans + + +print(binary_gap(111)) +print(binary_gap_improved(111)) \ No newline at end of file diff --git a/bit/bit_operation.py b/algorithms/bit/bit_operation.py similarity index 100% rename from bit/bit_operation.py rename to algorithms/bit/bit_operation.py diff --git a/bit/bytes_int_conversion.py b/algorithms/bit/bytes_int_conversion.py similarity index 100% rename from bit/bytes_int_conversion.py rename to algorithms/bit/bytes_int_conversion.py diff --git a/algorithms/bit/count_flips_to_convert.py b/algorithms/bit/count_flips_to_convert.py new file mode 100644 index 000000000..375e68f21 --- /dev/null +++ b/algorithms/bit/count_flips_to_convert.py @@ -0,0 +1,19 @@ +""" +Write a function to determine the minimal number of bits you would need to +flip to convert integer A to integer B. +For example: +Input: 29 (or: 11101), 15 (or: 01111) +Output: 2 +""" + + +def count_flips_to_convert(a, b): + + diff = a ^ b + + # count number of ones in diff + count = 0 + while diff: + diff &= (diff - 1) + count += 1 + return count diff --git a/bit/count_ones.py b/algorithms/bit/count_ones.py similarity index 74% rename from bit/count_ones.py rename to algorithms/bit/count_ones.py index 700efe9e2..ac718f336 100644 --- a/bit/count_ones.py +++ b/algorithms/bit/count_ones.py @@ -1,9 +1,9 @@ """ Write a function that takes an unsigned integer and -returns the number of ’1' bits it has +returns the number of '1' bits it has (also known as the Hamming weight). -For example, the 32-bit integer ’11' has binary +For example, the 32-bit integer '11' has binary representation 00000000000000000000000000001011, so the function should return 3. @@ -15,7 +15,7 @@ Number of loops is equal to the number of 1s in the binary representation.""" def count_ones_recur(n): - """Using Brian Kernighan’s Algorithm. (Recursive Approach)""" + """Using Brian Kernighan's Algorithm. (Recursive Approach)""" if not n: return 0 @@ -23,7 +23,7 @@ def count_ones_recur(n): def count_ones_iter(n): - """Using Brian Kernighan’s Algorithm. (Iterative Approach)""" + """Using Brian Kernighan's Algorithm. (Iterative Approach)""" count = 0 while n: diff --git a/bit/find_difference.py b/algorithms/bit/find_difference.py similarity index 100% rename from bit/find_difference.py rename to algorithms/bit/find_difference.py diff --git a/bit/find_missing_number.py b/algorithms/bit/find_missing_number.py similarity index 89% rename from bit/find_missing_number.py rename to algorithms/bit/find_missing_number.py index 9aedc508b..2e47df4d1 100644 --- a/bit/find_missing_number.py +++ b/algorithms/bit/find_missing_number.py @@ -3,6 +3,10 @@ in range [0..n] in O(n) time and space. The difference between consecutive integers cannot be more than 1. If the sequence is already complete, the next integer in the sequence will be returned. + + For example: + Input: nums = [4, 1, 3, 0, 6, 5, 2] + Output: 7 """ def find_missing_number(nums): diff --git a/algorithms/bit/flip_bit_longest_sequence.py b/algorithms/bit/flip_bit_longest_sequence.py new file mode 100644 index 000000000..a952edb3e --- /dev/null +++ b/algorithms/bit/flip_bit_longest_sequence.py @@ -0,0 +1,30 @@ +""" +You have an integer and you can flip exactly one bit from a 0 to 1. +Write code to find the length of the longest sequence of 1s you could create. +For example: +Input: 1775 ( or: 11011101111) +Output: 8 +""" + + +def flip_bit_longest_seq(num): + + curr_len = 0 + prev_len = 0 + max_len = 0 + + while num: + if num & 1 == 1: # last digit is 1 + curr_len += 1 + + elif num & 1 == 0: # last digit is 0 + if num & 2 == 0: # second last digit is 0 + prev_len = 0 + else: + prev_len = curr_len + curr_len = 0 + + max_len = max(max_len, prev_len + curr_len) + num = num >> 1 # right shift num + + return max_len + 1 diff --git a/bit/has_alternative_bit.py b/algorithms/bit/has_alternative_bit.py similarity index 100% rename from bit/has_alternative_bit.py rename to algorithms/bit/has_alternative_bit.py diff --git a/bit/insert_bit.py b/algorithms/bit/insert_bit.py similarity index 94% rename from bit/insert_bit.py rename to algorithms/bit/insert_bit.py index f799489c8..a86e042f4 100644 --- a/bit/insert_bit.py +++ b/algorithms/bit/insert_bit.py @@ -6,7 +6,7 @@ Input: num = 10101 (21) insert_one_bit(num, 1, 2): 101101 (45) -insert_one_bit(num, 0 ,2): 101001 (41) +insert_one_bit(num, 0, 2): 101001 (41) insert_one_bit(num, 1, 5): 110101 (53) insert_one_bit(num, 1, 0): 101011 (43) @@ -25,7 +25,7 @@ Algorithm: 1. Create a mask having bit from i to the most significant bit, and append the new bit at 0 position 2. Keep the bit from 0 position to i position ( like 000...001111) -3) Merge mask and num +3. Merge mask and num """ def insert_one_bit(num, bit, i): # Create mask diff --git a/bit/power_of_two.py b/algorithms/bit/power_of_two.py similarity index 100% rename from bit/power_of_two.py rename to algorithms/bit/power_of_two.py diff --git a/bit/remove_bit.py b/algorithms/bit/remove_bit.py similarity index 100% rename from bit/remove_bit.py rename to algorithms/bit/remove_bit.py diff --git a/bit/reverse_bits.py b/algorithms/bit/reverse_bits.py similarity index 100% rename from bit/reverse_bits.py rename to algorithms/bit/reverse_bits.py diff --git a/bit/single_number.py b/algorithms/bit/single_number.py similarity index 100% rename from bit/single_number.py rename to algorithms/bit/single_number.py diff --git a/bit/single_number2.py b/algorithms/bit/single_number2.py similarity index 71% rename from bit/single_number2.py rename to algorithms/bit/single_number2.py index 9aff72cfe..2f0e36c59 100644 --- a/bit/single_number2.py +++ b/algorithms/bit/single_number2.py @@ -17,22 +17,6 @@ the remaining should be the exact bit of the single number. In this way, you get the 32 bits of the single number. """ -def single_number(nums): - """ - :type nums: List[int] - :rtype: int - """ - res = 0 - for i in range(0, 32): - count = 0 - for num in nums: - if (num >> i) & 1: - count += 1 - res |= ((count % 3) << i) - if res >= 2**31: - res -= 2**32 - return res - # Another awesome answer def single_number2(nums): diff --git a/bit/single_number3.py b/algorithms/bit/single_number3.py similarity index 100% rename from bit/single_number3.py rename to algorithms/bit/single_number3.py diff --git a/bit/subsets.py b/algorithms/bit/subsets.py similarity index 100% rename from bit/subsets.py rename to algorithms/bit/subsets.py diff --git a/bit/swap_pair.py b/algorithms/bit/swap_pair.py similarity index 100% rename from bit/swap_pair.py rename to algorithms/bit/swap_pair.py diff --git a/bit/__init__.py b/algorithms/compression/__init__.py similarity index 100% rename from bit/__init__.py rename to algorithms/compression/__init__.py diff --git a/algorithms/compression/elias.py b/algorithms/compression/elias.py new file mode 100644 index 000000000..974fc3772 --- /dev/null +++ b/algorithms/compression/elias.py @@ -0,0 +1,53 @@ +""" +Elias γ code or Elias gamma code is a universal code +encoding positive integers. +It is used most commonly when coding integers whose +upper-bound cannot be determined beforehand. +Elias δ code or Elias delta code is a universal code + encoding the positive integers, +that includes Elias γ code when calculating. +Both were developed by Peter Elias. + +""" +from math import log + +log2 = lambda x: log(x, 2) + +# Calculates the binary number +def binary(x, l=1): + fmt = '{0:0%db}' % l + return fmt.format(x) + +# Calculates the unary number +def unary(x): + return (x-1)*'1'+'0' + +def elias_generic(lencoding, x): + """ + The compressed data is calculated in two parts. + The first part is the unary number of 1 + ⌊log2(x)⌋. + The second part is the binary number of x - 2^(⌊log2(x)⌋). + For the final result we add these two parts. + """ + if x == 0: + return '0' + + first_part = 1 + int(log2(x)) + + a = x - 2**(int(log2(x))) + + k = int(log2(x)) + + return lencoding(first_part) + binary(a, k) + +def elias_gamma(x): + """ + For the first part we put the unary number of x. + """ + return elias_generic(unary, x) + +def elias_delta(x): + """ + For the first part we put the elias_g of the number. + """ + return elias_generic(elias_gamma, x) diff --git a/algorithms/compression/huffman_coding.py b/algorithms/compression/huffman_coding.py new file mode 100644 index 000000000..44effb19c --- /dev/null +++ b/algorithms/compression/huffman_coding.py @@ -0,0 +1,328 @@ +""" +Huffman coding is an efficient method of compressing data without losing information. +This algorithm analyzes the symbols that appear in a message. +Symbols that appear more often will be encoded as a shorter-bit string +while symbols that aren't used as much will be encoded as longer strings. +""" + +from collections import defaultdict, deque +import heapq + + +class Node: + def __init__(self, frequency=0, sign=None, left=None, right=None): + self.frequency = frequency + self.sign = sign + self.left = left + self.right = right + + def __lt__(self, other): + return self.frequency < other.frequency + + def __gt__(self, other): + return self.frequency > other.frequency + + def __eq__(self, other): + return self.frequency == other.frequency + + def __str__(self): + return "".format(self.sign, self.frequency) + + def __repr__(self): + return "".format(self.sign, self.frequency) + + +class HuffmanReader: + def __init__(self, file): + self.file = file + self.buffer = [] + self.is_last_byte = False + + def get_number_of_additional_bits_in_the_last_byte(self) -> int: + bin_num = self.get_bit() + self.get_bit() + self.get_bit() + return int(bin_num, 2) + + def load_tree(self) -> Node: + """ + Load tree from file + + :return: + """ + node_stack = deque() + queue_leaves = 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) # going to left node, right push on stack + 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): + """ + Load values to tree after reading tree + :param leaves_queue: + :return: + """ + leaves_queue.reverse() + while leaves_queue: + node = leaves_queue.pop() + s = int(self.get_byte(), 2) + node.sign = s + + def _load_byte(self, buff_limit=8) -> bool: + """ + Load next byte is buffer is less than buff_limit + :param buff_limit: + :return: True if there is enough bits in buffer to read + """ + if len(self.buffer) <= buff_limit: + byte = self.file.read(1) + + if not byte: + return False + + i = int.from_bytes(byte, "big") + self.buffer.extend(list("{0:08b}".format(i))) + + return True + + def get_bit(self, buff_limit=8): + if self._load_byte(buff_limit): + bit = self.buffer.pop(0) + return bit + else: + return -1 + + def get_byte(self): + if self._load_byte(): + byte_list = self.buffer[:8] + self.buffer = self.buffer[8:] + + return "".join(byte_list) + else: + return -1 + + +class HuffmanWriter: + def __init__(self, file): + self.file = file + self.buffer = "" + self.saved_bits = 0 + + def write_char(self, char): + self.write_int(ord(char)) + + def write_int(self, num): + bin_int = "{0:08b}".format(num) + self.write_bits(bin_int) + + def write_bits(self, bits): + self.saved_bits += len(bits) + + self.buffer += bits + + while len(self.buffer) >= 8: + i = int(self.buffer[:8], 2) + self.file.write(bytes([i])) + self.buffer = self.buffer[8:] + + def save_tree(self, tree): + """ + Generate and save tree code to file + :param tree: + :return: + """ + signs = [] + tree_code = "" + + def get_code_tree(T): + nonlocal tree_code + if T.sign is not None: + signs.append(T.sign) + if T.left: + tree_code += "0" + get_code_tree(T.left) + if T.right: + tree_code += "1" + get_code_tree(T.right) + + get_code_tree(tree) + self.write_bits(tree_code + "1") # "1" indicates that tree ended (it will be needed to load the tree) + for int_sign in signs: + self.write_int(int_sign) + + def _save_information_about_additional_bits(self, additional_bits: int): + """ + Overwrite first three bits in the file + :param additional_bits: number of bits that were appended to fill last byte + :return: + """ + self.file.seek(0) + first_byte_raw = self.file.read(1) + self.file.seek(0) + first_byte = "{0:08b}".format(int.from_bytes(first_byte_raw, "big")) + # overwrite first three bits + first_byte = first_byte[3:] + first_byte = "{0:03b}".format(additional_bits) + first_byte + + self.write_bits(first_byte) + + def close(self): + additional_bits = 8 - len(self.buffer) + if additional_bits != 8: # buffer is empty, no need to append extra "0" + self.write_bits("0" * additional_bits) + self._save_information_about_additional_bits(additional_bits) + + +class TreeFinder: + """ + Class to help find signs in tree + """ + + def __init__(self, tree): + self.root = tree + self.current_node = tree + self.found = None + + def find(self, bit): + """ + Find sign in tree + :param bit: + :return: True if sign is found + """ + 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 + else: + return False + + def _reset(self, found=""): + self.found = found + self.current_node = self.root + + +class HuffmanCoding: + def __init__(self): + pass + + @staticmethod + def decode_file(file_in_name, file_out_name): + 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) + + print("File decoded.") + + @staticmethod + def _decode_and_write_signs_to_file(file, reader: HuffmanReader, tree: Node, additional_bits: int): + 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): # read whole code + bit = reader.get_bit(0) + file.write(bytes([tree_finder.found])) + else: # There is last byte in buffer to parse + is_end_of_file = True + last_byte = reader.buffer + last_byte = last_byte[:-additional_bits] # remove additional "0" used to fill byte + for bit in last_byte: + if tree_finder.find(bit): + file.write(bytes([tree_finder.found])) + + @staticmethod + def encode_file(file_in_name, file_out_name): + 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") # leave space to save how many bits will be appended to fill the last byte + writer.save_tree(tree) + HuffmanCoding._encode_and_write_signs_to_file(file_in, writer, codes) + writer.close() + + print("File encoded.") + + @staticmethod + def _encode_and_write_signs_to_file(file, writer: HuffmanWriter, codes: dict): + 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) -> dict: + is_end_of_file = False + signs_frequency = 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: + codes = dict() + HuffmanCoding._go_through_tree_and_create_codes(tree, "", codes) + return codes + + @staticmethod + def _create_tree(signs_frequency: dict) -> Node: + 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] # root + + @staticmethod + def _go_through_tree_and_create_codes(tree: Node, code: str, dict_codes: dict): + 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) diff --git a/algorithms/compression/rle_compression.py b/algorithms/compression/rle_compression.py new file mode 100644 index 000000000..b6c6df44d --- /dev/null +++ b/algorithms/compression/rle_compression.py @@ -0,0 +1,58 @@ +""" +Run-length encoding (RLE) is a simple compression algorithm +that gets a stream of data as the input and returns a +sequence of counts of consecutive data values in a row. +When decompressed the data will be fully recovered as RLE +is a lossless data compression. +""" + +def encode_rle(input): + """ + Gets a stream of data and compresses it + under a Run-Length Encoding. + :param input: The data to be encoded. + :return: The encoded string. + """ + if not input: return '' + + encoded_str = '' + prev_ch = '' + count = 1 + + for ch in input: + + # Check If the subsequent character does not match + if ch != prev_ch: + # Add the count and character + if prev_ch: + encoded_str += str(count) + prev_ch + # Reset the count and set the character + count = 1 + prev_ch = ch + else: + # Otherwise increment the counter + count += 1 + else: + return encoded_str + (str(count) + prev_ch) + + +def decode_rle(input): + """ + Gets a stream of data and decompresses it + under a Run-Length Decoding. + :param input: The data to be decoded. + :return: The decoded string. + """ + decode_str = '' + count = '' + + for ch in input: + # If not numerical + if not ch.isdigit(): + # Expand it for the decoding + decode_str += ch * int(count) + count = '' + else: + # Add it in the counter + count += ch + return decode_str diff --git a/algorithms/dfs/__init__.py b/algorithms/dfs/__init__.py new file mode 100644 index 000000000..043414c1e --- /dev/null +++ b/algorithms/dfs/__init__.py @@ -0,0 +1,6 @@ +from .all_factors import * +from .count_islands import * +from .pacific_atlantic import * +from .sudoku_solver import * +from .walls_and_gates import * +from .maze_search import * diff --git a/dfs/all_factors.py b/algorithms/dfs/all_factors.py similarity index 78% rename from dfs/all_factors.py rename to algorithms/dfs/all_factors.py index 6c692f30b..13438d41f 100644 --- a/dfs/all_factors.py +++ b/algorithms/dfs/all_factors.py @@ -27,8 +27,6 @@ [2, 2, 2, 4], [2, 2, 2, 2, 2], """ -import unittest - def get_factors(n): """[summary] @@ -111,17 +109,3 @@ def get_factors_iterative2(n): n //= x else: x += 1 - -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)) - -if __name__ == "__main__": - unittest.main() diff --git a/dfs/count_islands.py b/algorithms/dfs/count_islands.py similarity index 79% rename from dfs/count_islands.py rename to algorithms/dfs/count_islands.py index 60f6802d2..21bcee85a 100644 --- a/dfs/count_islands.py +++ b/algorithms/dfs/count_islands.py @@ -24,20 +24,20 @@ def num_islands(grid): count = 0 - for i, row in enumerate(grid): + for i in range(len(grid)): for j, col in enumerate(grid[i]): - if col == '1': + if col == 1: dfs(grid, i, j) count += 1 return count def dfs(grid, i, j): - if (i < 0 or i >= len(grid)) or (j < 0 or len(grid[0])): + if (i < 0 or i >= len(grid)) or (j < 0 or j >= len(grid[0])): return - if grid[i][j] != '1': + if grid[i][j] != 1: return - grid[i][j] = '0' + grid[i][j] = 0 dfs(grid, i+1, j) dfs(grid, i-1, j) dfs(grid, i, j+1) diff --git a/algorithms/dfs/maze_search.py b/algorithms/dfs/maze_search.py new file mode 100644 index 000000000..f644fd5a6 --- /dev/null +++ b/algorithms/dfs/maze_search.py @@ -0,0 +1,55 @@ +''' +Find shortest path from top left column to the right lowest column using DFS. +only step on the columns whose value is 1 +if there is no path, it returns -1 +(The first column(top left column) is not included in the answer.) + +Ex 1) +If maze is +[[1,0,1,1,1,1], + [1,0,1,0,1,0], + [1,0,1,0,1,1], + [1,1,1,0,1,1]], +the answer is: 14 + +Ex 2) +If maze is +[[1,0,0], + [0,1,1], + [0,1,1]], +the answer is: -1 +''' + + +def find_path(maze): + cnt = dfs(maze, 0, 0, 0, -1) + return cnt + + +def dfs(maze, i, j, depth, cnt): + 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 nx_i >= 0 and nx_i < row and nx_j >= 0 and nx_j < col: + if maze[nx_i][nx_j] == 1: + cnt = dfs(maze, nx_i, nx_j, depth + 1, cnt) + + maze[i][j] = 1 + + return cnt diff --git a/dfs/pacific_atlantic.py b/algorithms/dfs/pacific_atlantic.py similarity index 100% rename from dfs/pacific_atlantic.py rename to algorithms/dfs/pacific_atlantic.py diff --git a/dfs/sudoku_solver.py b/algorithms/dfs/sudoku_solver.py similarity index 90% rename from dfs/sudoku_solver.py rename to algorithms/dfs/sudoku_solver.py index 4f3c27530..e7ba9aa02 100644 --- a/dfs/sudoku_solver.py +++ b/algorithms/dfs/sudoku_solver.py @@ -20,9 +20,6 @@ fine for now. (it would look less lengthy if we are allowed to use numpy array for the board lol). """ - -import unittest - class Sudoku: def __init__ (self, board, row, col): self.board = board @@ -96,15 +93,3 @@ def __str__(self): resp += " {0} ".format(self.board[i][j]) resp += "\n" return resp - - -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) - - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/algorithms/dfs/walls_and_gates.py b/algorithms/dfs/walls_and_gates.py new file mode 100644 index 000000000..0369203d9 --- /dev/null +++ b/algorithms/dfs/walls_and_gates.py @@ -0,0 +1,38 @@ +""" +You are given a m x n 2D grid initialized with these three possible values: + -1: A wall or an obstacle. + 0: A gate. + INF: Infinity means an empty room. We use the value 2^31 - 1 = 2147483647 to represent INF + as you may assume that the distance to a gate is less than 2147483647. +Fill the empty room with distance to its nearest gate. +If it is impossible to reach a gate, it should be filled with INF. + +For example, given the 2D grid: + INF -1 0 INF + INF INF INF -1 + INF -1 INF -1 + 0 -1 INF INF +After running your function, the 2D grid should be: + 3 -1 0 1 + 2 2 1 -1 + 1 -1 2 -1 + 0 -1 3 4 +""" + +def walls_and_gates(rooms): + 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, i, j, depth): + if (i < 0 or i >= len(rooms)) or (j < 0 or j >= len(rooms[0])): + return # out of bounds + if rooms[i][j] < depth: + return # crossed + 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) diff --git a/graph/__init__.py b/algorithms/distribution/__init__.py similarity index 100% rename from graph/__init__.py rename to algorithms/distribution/__init__.py diff --git a/algorithms/distribution/histogram.py b/algorithms/distribution/histogram.py new file mode 100644 index 000000000..7bcdc7f47 --- /dev/null +++ b/algorithms/distribution/histogram.py @@ -0,0 +1,28 @@ +""" +Histogram function. + +Histogram is an accurate representation of the distribution of numerical data. +It is an estimate of the probability distribution of a continuous variable. +https://en.wikipedia.org/wiki/Histogram + +Example: + list_1 = [3, 3, 2, 1] + :return {1: 1, 2: 1, 3: 2} + + list_2 = [2, 3, 5, 5, 5, 6, 4, 3, 7] + :return {2: 1, 3: 2, 4: 1, 5: 3, 6: 1, 7: 1} +""" + + +def get_histogram(input_list: list) -> dict: + """ + Get histogram representation + :param input_list: list with different and unordered values + :return histogram: dict with histogram of input_list + """ + # Create dict to store histogram + histogram = {} + # For each list value, add one to the respective histogram dict position + for i in input_list: + histogram[i] = histogram.get(i, 0) + 1 + return histogram diff --git a/algorithms/dp/__init__.py b/algorithms/dp/__init__.py new file mode 100644 index 000000000..442120282 --- /dev/null +++ b/algorithms/dp/__init__.py @@ -0,0 +1,23 @@ +from .buy_sell_stock import * +from .climbing_stairs import * +from .coin_change import * +from .combination_sum import * +from .edit_distance import * +from .egg_drop import * +from .fib import * +from .hosoya_triangle import * +from .house_robber import * +from .job_scheduling import * +from .knapsack import * +from .longest_increasing import * +from .matrix_chain_order import * +from .max_product_subarray import * +from .max_subarray import * +from .min_cost_path import * +from .num_decodings import * +from .regex_matching import * +from .rod_cut import * +from .word_break import * +from .int_divide import * +from .k_factor import * +from .planting_trees import * diff --git a/dp/buy_sell_stock.py b/algorithms/dp/buy_sell_stock.py similarity index 100% rename from dp/buy_sell_stock.py rename to algorithms/dp/buy_sell_stock.py diff --git a/algorithms/dp/climbing_stairs.py b/algorithms/dp/climbing_stairs.py new file mode 100644 index 000000000..9b90ae15c --- /dev/null +++ b/algorithms/dp/climbing_stairs.py @@ -0,0 +1,36 @@ +""" +You are climbing a stair case. +It takes `steps` number of steps to reach to the top. + +Each time you can either climb 1 or 2 steps. +In how many distinct ways can you climb to the top? + +Note: Given argument `steps` will be a positive integer. +""" + + +# O(n) space + +def climb_stairs(steps): + """ + :type steps: int + :rtype: int + """ + arr = [1, 1] + for _ in range(1, steps): + arr.append(arr[-1] + arr[-2]) + return arr[-1] + + +# the above function can be optimized as: +# O(1) space + +def climb_stairs_optimized(steps): + """ + :type steps: int + :rtype: int + """ + a_steps = b_steps = 1 + for _ in range(steps): + a_steps, b_steps = b_steps, a_steps + b_steps + return a_steps diff --git a/algorithms/dp/coin_change.py b/algorithms/dp/coin_change.py new file mode 100644 index 000000000..379753b06 --- /dev/null +++ b/algorithms/dp/coin_change.py @@ -0,0 +1,34 @@ +""" +Problem +Given a value `value`, if we want to make change for `value` cents, and we have infinite +supply of each of coins = {S1, S2, .. , Sm} valued `coins`, how many ways can we make the change? +The order of `coins` doesn't matter. +For example, for `value` = 4 and `coins` = [1, 2, 3], there are four solutions: +[1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]. +So output should be 4. + +For `value` = 10 and `coins` = [2, 5, 3, 6], there are five solutions: + +[2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5]. +So the output should be 5. + +Time complexity: O(n * m) where n is the `value` and m is the number of `coins` +Space complexity: O(n) +""" + +def count(coins, value): + """ Find number of combination of `coins` that adds upp to `value` + + Keyword arguments: + coins -- int[] + value -- int + """ + # initialize dp array and set base case as 1 + dp_array = [1] + [0] * value + + # fill dp in a bottom up manner + for coin in coins: + for i in range(coin, value+1): + dp_array[i] += dp_array[i-coin] + + return dp_array[value] diff --git a/algorithms/dp/combination_sum.py b/algorithms/dp/combination_sum.py new file mode 100644 index 000000000..aaf0749e8 --- /dev/null +++ b/algorithms/dp/combination_sum.py @@ -0,0 +1,74 @@ +""" +Given an integer array with all positive numbers and no duplicates, +find the number of possible combinations that +add up to a positive integer target. + +Example: + +nums = [1, 2, 3] +target = 4 + +The possible combination ways are: +(1, 1, 1, 1) +(1, 1, 2) +(1, 2, 1) +(1, 3) +(2, 1, 1) +(2, 2) +(3, 1) + +Note that different sequences are counted as different combinations. + +Therefore the output is 7. +Follow up: +What if negative numbers are allowed in the given array? +How does it change the problem? +What limitation we need to add to the question to allow negative numbers? + +""" + +DP = None + +def helper_topdown(nums, target): + """Generates DP and finds result. + + Keyword arguments: + nums -- positive integer array without duplicates + target -- integer describing what a valid combination should add to + """ + if DP[target] != -1: + return DP[target] + res = 0 + for num in nums: + if target >= num: + res += helper_topdown(nums, target - num) + DP[target] = res + return res + + +def combination_sum_topdown(nums, target): + """Find number of possible combinations in nums that add up to target, in top-down manner. + + Keyword arguments: + nums -- positive integer array without duplicates + target -- integer describing what a valid combination should add to + """ + global DP + DP = [-1] * (target + 1) + DP[0] = 1 + return helper_topdown(nums, target) + +def combination_sum_bottom_up(nums, target): + """Find number of possible combinations in nums that add up to target, in bottom-up manner. + + Keyword arguments: + nums -- positive integer array without duplicates + target -- integer describing what a valid combination should add to + """ + 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] diff --git a/algorithms/dp/edit_distance.py b/algorithms/dp/edit_distance.py new file mode 100644 index 000000000..caf56eedc --- /dev/null +++ b/algorithms/dp/edit_distance.py @@ -0,0 +1,69 @@ +"""The edit distance between two words is the minimum number +of letter insertions, letter deletions, and letter substitutions +required to transform one word into another. + +For example, the edit distance between FOOD and MONEY is at +most four: + +FOOD -> MOOD -> MOND -> MONED -> MONEY + +Given two words A and B, find the minimum number of operations +required to transform one string into the other. +In other words, find the edit distance between A and B. + +Thought process: + +Let edit(i, j) denote the edit distance between +the prefixes A[1..i] and B[1..j]. + +Then, the function satifies the following recurrence: + +edit(i, j) = i if j = 0 + j if i = 0 + min(edit(i-1, j) + 1, + edit(i, j-1), + 1, + edit(i-1, j-1) + cost) otherwise + +There are two base cases, both of which occur when one string is empty +and the other is not. +1. To convert an empty string A into a string B of length n, +perform n insertions. +2. To convert a string A of length m into an empty string B, +perform m deletions. + +Here, the cost is 1 if a substitution is required, +or 0 if both chars in words A and B are the same at +indexes i and j, respectively. + +To find the edit distance between two words A and B, +we need to find edit(length_a, length_b). + +Time: O(length_a*length_b) +Space: O(length_a*length_b) +""" + + +def edit_distance(word_a, word_b): + """Finds edit distance between word_a and word_b + + Kwyword arguments: + word_a -- string + word_b -- string + """ + + 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] # this is the same as edit[length_a][length_b] diff --git a/algorithms/dp/egg_drop.py b/algorithms/dp/egg_drop.py new file mode 100644 index 000000000..dfa232019 --- /dev/null +++ b/algorithms/dp/egg_drop.py @@ -0,0 +1,58 @@ +""" +You are given K eggs, and you have access to a building with N floors +from 1 to N. Each egg is identical in function, and if an egg breaks, +you cannot drop it again. You know that there exists a floor F with +0 <= F <= N such that any egg dropped at a floor higher than F will +break, and any egg dropped at or below floor F will not break. +Each move, you may take an egg (if you have an unbroken one) and drop +it from any floor X (with 1 <= X <= N). Your goal is to know with +certainty what the value of F is. What is the minimum number of moves +that you need to know with certainty what F is, regardless of the +initial value of F? + +Example: +Input: K = 1, N = 2 +Output: 2 +Explanation: +Drop the egg from floor 1. If it breaks, we know with certainty that F = 0. +Otherwise, drop the egg from floor 2. If it breaks, we know with +certainty that F = 1. +If it didn't break, then we know with certainty F = 2. +Hence, we needed 2 moves in the worst case to know what F is with certainty. +""" + +# A Dynamic Programming based Python Program for the Egg Dropping Puzzle +INT_MAX = 32767 + + +def egg_drop(n, k): + """ + Keyword arguments: + n -- number of floors + k -- number of eggs + """ + # A 2D table where entery eggFloor[i][j] will represent minimum + # number of trials needed for i eggs and j floors. + egg_floor = [[0 for _ in range(k + 1)] for _ in range(n + 1)] + + # We need one trial for one floor and 0 trials for 0 floors + for i in range(1, n+1): + egg_floor[i][1] = 1 + egg_floor[i][0] = 0 + + # We always need j trials for one egg and j floors. + for j in range(1, k+1): + egg_floor[1][j] = j + + # Fill rest of the entries in table using optimal substructure + # property + 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 + + # eggFloor[n][k] holds the result + return egg_floor[n][k] diff --git a/dp/fib.py b/algorithms/dp/fib.py similarity index 63% rename from dp/fib.py rename to algorithms/dp/fib.py index 3ba4c5e3a..08fa8ea02 100644 --- a/dp/fib.py +++ b/algorithms/dp/fib.py @@ -1,3 +1,22 @@ +''' +In mathematics, the Fibonacci numbers, commonly denoted Fn, +form a sequence, called the Fibonacci sequence, +such that each number is the sum of the two preceding ones, +starting from 0 and 1. +That is, + F0=0 , F1=1 +and + Fn= F(n-1) + F(n-2) +The Fibonacci numbers are the numbers in the following integer sequence. + 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ……. + +In mathematical terms, the sequence Fn of Fibonacci numbers is +defined by the recurrence relation + +Here, given a number n, print n-th Fibonacci Number. +''' + + def fib_recursive(n): """[summary] Computes the n-th fibonacci number recursive. @@ -6,7 +25,7 @@ def fib_recursive(n): Arguments: n {[int]} -- [description] - + Returns: [int] -- [description] """ @@ -16,20 +35,20 @@ def fib_recursive(n): if n <= 1: return n - else: - return fib_recursive(n-1) + fib_recursive(n-2) + return fib_recursive(n-1) + fib_recursive(n-2) # print(fib_recursive(35)) # => 9227465 (slow) + def fib_list(n): """[summary] This algorithm computes the n-th fibbonacci number very quick. approximate O(n) The algorithm use dynamic programming. - + Arguments: n {[int]} -- [description] - + Returns: [int] -- [description] """ @@ -44,13 +63,14 @@ def fib_list(n): # print(fib_list(100)) # => 354224848179261915075 + def fib_iter(n): """[summary] Works iterative approximate O(n) Arguments: n {[int]} -- [description] - + Returns: [int] -- [description] """ @@ -60,14 +80,13 @@ def fib_iter(n): fib_1 = 0 fib_2 = 1 - sum = 0 + res = 0 if n <= 1: return n - for i in range(n-1): - sum = fib_1 + fib_2 + for _ in range(n-1): + res = fib_1 + fib_2 fib_1 = fib_2 - fib_2 = sum - return sum + fib_2 = res + return res -# => 354224848179261915075 -# print(fib_iter(100)) \ No newline at end of file +# print(fib_iter(100)) # => 354224848179261915075 diff --git a/algorithms/dp/hosoya_triangle.py b/algorithms/dp/hosoya_triangle.py new file mode 100644 index 000000000..a63deaf48 --- /dev/null +++ b/algorithms/dp/hosoya_triangle.py @@ -0,0 +1,56 @@ +""" +Hosoya triangle (originally Fibonacci triangle) is a triangular arrangement +of numbers, where if you take any number it is the sum of 2 numbers above. +First line is always 1, and second line is always {1 1}. + +This printHosoya function takes argument n which is the height of the triangle +(number of lines). + +For example: +printHosoya( 6 ) would return: +1 +1 1 +2 1 2 +3 2 2 3 +5 3 4 3 5 +8 5 6 6 5 8 + +The complexity is O(n^3). + +""" + +def hosoya(height, width): + """ Calculates the hosoya triangle + + height -- height of the triangle + """ + 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 print_hosoya(height): + """Prints the hosoya triangle + + height -- height of the triangle + """ + for i in range(height): + for j in range(i + 1): + print(hosoya(i, j) , end = " ") + print ("\n", end = "") + +def hosoya_testing(height): + """Test hosoya function + + height -- height of the triangle + """ + res = [] + for i in range(height): + for j in range(i + 1): + res.append(hosoya(i, j)) + return res diff --git a/dp/house_robber.py b/algorithms/dp/house_robber.py similarity index 81% rename from dp/house_robber.py rename to algorithms/dp/house_robber.py index 88baaa9c4..5a359f93d 100644 --- a/dp/house_robber.py +++ b/algorithms/dp/house_robber.py @@ -15,11 +15,5 @@ def house_robber(houses): last, now = 0, 0 for house in houses: - tmp = now - now = max(last + house, now) - last = tmp + last, now = now, max(last + house, now) return now - -houses = [1, 2, 16, 3, 15, 3, 12, 1] - -print(house_robber(houses)) diff --git a/algorithms/dp/int_divide.py b/algorithms/dp/int_divide.py new file mode 100644 index 000000000..37c9a44e1 --- /dev/null +++ b/algorithms/dp/int_divide.py @@ -0,0 +1,55 @@ +""" +Given positive integer decompose, find an algorithm to find the number of +non-negative number division, or decomposition. + +The complexity is O(n^2). + +Example 1: +Input: 4 +Output: 5 +Explaination: +4=4 +4=3+1 +4=2+2 +4=2+1+1 +4=1+1+1+1 + +Example : +Input: 7 +Output: 15 +Explaination: +7=7 +7=6+1 +7=5+2 +7=5+1+1 +7=4+3 +7=4+2+1 +7=4+1+1+1 +7=3+3+1 +7=3+2+2 +7=3+2+1+1 +7=3+1+1+1+1 +7=2+2+2+1 +7=2+2+1+1+1 +7=2+1+1+1+1+1 +7=1+1+1+1+1+1+1 + +""" + + +def int_divide(decompose): + """Find number of decompositions from `decompose` + + decompose -- integer + """ + 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] diff --git a/algorithms/dp/job_scheduling.py b/algorithms/dp/job_scheduling.py new file mode 100644 index 000000000..b822c032c --- /dev/null +++ b/algorithms/dp/job_scheduling.py @@ -0,0 +1,68 @@ +""" +Python program for weighted job scheduling using Dynamic +Programming and Binary Search +""" + +class Job: + """ + Class to represent a job + """ + def __init__(self, start, finish, profit): + self.start = start + self.finish = finish + self.profit = profit + +def binary_search(job, start_index): + """ + A Binary Search based function to find the latest job + (before current job) that doesn't conflict with current + job. "index" is index of the current job. This function + returns -1 if all jobs before index conflict with it. + The array jobs[] is sorted in increasing order of finish + time. + """ + + left = 0 + right = start_index - 1 + + # Perform binary Search iteratively + 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): + """ + The main function that returns the maximum possible + profit from given array of jobs + """ + + # Sort jobs according to finish time + job = sorted(job, key = lambda j: j.finish) + + # Create an array to store solutions of subproblems. table[i] + # stores the profit for jobs till arr[i] (including arr[i]) + length = len(job) + table = [0 for _ in range(length)] + + table[0] = job[0].profit + + # Fill entries in table[] using recursive property + for i in range(1, length): + + # Find profit including the current job + incl_prof = job[i].profit + pos = binary_search(job, i) + if pos != -1: + incl_prof += table[pos] + + # Store maximum of including and excluding + table[i] = max(incl_prof, table[i - 1]) + + return table[length-1] diff --git a/algorithms/dp/k_factor.py b/algorithms/dp/k_factor.py new file mode 100644 index 000000000..c072f7541 --- /dev/null +++ b/algorithms/dp/k_factor.py @@ -0,0 +1,85 @@ +''' +The K factor of a string is defined as the number of times 'abba' appears as a +substring. Given two numbers `length` and `k_factor`, find the number of +strings of length `length` with 'K factor' = `k_factor`. + +The algorithms is as follows: + +dp[length][k_factor] will be a 4 element array, wherein each element can be the +number of strings of length `length` and 'K factor' = `k_factor` which belong +to the criteria represented by that index: + + - dp[length][k_factor][0] can be the number of strings of length `length` + and K-factor = `k_factor` which end with substring 'a' + + - dp[length][k_factor][1] can be the number of strings of length `length` + and K-factor = `k_factor` which end with substring 'ab' + + - dp[length][k_factor][2] can be the number of strings of length `length` + and K-factor = `k_factor` which end with substring 'abb' + + - dp[length][k_factor][3] can be the number of strings of `length` and + K-factor = `k_factor` which end with anything other than the above + substrings (anything other than 'a' 'ab' 'abb') + +Example inputs + +length=4 k_factor=1 no of strings = 1 +length=7 k_factor=1 no of strings = 70302 +length=10 k_factor=2 no of strings = 74357 + +''' + +def find_k_factor(length, k_factor): + """Find the number of strings of length `length` with K factor = `k_factor`. + + Keyword arguments: + length -- integer + k_factor -- integer + """ + 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 + #base cases + 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: + #adding a at the end + mat[i][j][0]=mat[i-1][j][0]+mat[i-1][j][1]+mat[i-1][j][3] + + #adding b at the end + mat[i][j][1]=mat[i-1][j][0] + mat[i][j][2]=mat[i-1][j][1] + + #adding any other lowercase character + 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 sequence[j]: + counts[i] = max(counts[i], counts[j] + 1) + print(counts) + return max(counts) + + +def longest_increasing_subsequence_optimized(sequence): + """ + Optimized dynamic programming algorithm for + couting the length of the longest increasing subsequence + using segment tree data structure to achieve better complexity + if max element is larger than 10^5 then use + longest_increasing_subsequence_optimied2() instead + type sequence: list[int] + rtype: int + """ + max_seq = max(sequence) + tree = [0] * (max_seq<<2) + + def update(pos, left, right, target, vertex): + 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_seq(tree[pos<<1], tree[(pos<<1)|1]) + + def get_max(pos, left, right, start, end): + if left > end or right < start: + return 0 + if left >= start and right <= end: + return tree[pos] + mid = (left+right)>>1 + return max_seq(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_seq, 0, element-1)+1 + ans = max_seq(ans, cur) + update(1, 0, max_seq, element, cur) + return ans + + +def longest_increasing_subsequence_optimized2(sequence): + """ + Optimized dynamic programming algorithm for + counting the length of the longest increasing subsequence + using segment tree data structure to achieve better complexity + type sequence: list[int] + rtype: int + """ + length = len(sequence) + tree = [0] * (length<<2) + sorted_seq = sorted((x, -i) for i, x in enumerate(sequence)) + def update(pos, left, right, target, vertex): + if left == right: + tree[pos] = vertex + return + mid = (left+right)>>1 + if target <= mid: + vertex(pos<<1, left, mid, target, vertex) + else: + vertex((pos<<1)|1, mid+1, right, target, vertex) + tree[pos] = max(tree[pos<<1], tree[(pos<<1)|1]) + + def get_max(pos, left, right, start, end): + 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 diff --git a/dp/matrix_chain_order.py b/algorithms/dp/matrix_chain_order.py similarity index 51% rename from dp/matrix_chain_order.py rename to algorithms/dp/matrix_chain_order.py index 8c1ecae43..dd2ef7bd9 100644 --- a/dp/matrix_chain_order.py +++ b/algorithms/dp/matrix_chain_order.py @@ -6,12 +6,17 @@ ''' INF = float("inf") + def matrix_chain_order(array): - n=len(array) + """Finds optimal order to multiply matrices + + array -- int[] + """ + 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): + 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 @@ -20,26 +25,37 @@ def matrix_chain_order(array): if cost < matrix[a][b]: matrix[a][b] = cost sol[a][b] = c - return matrix , sol -#Print order of matrix with Ai as matrix + return matrix, sol +# Print order of matrix with Ai as matrix def print_optimal_solution(optimal_solution,i,j): + """Print the solution + + optimal_solution -- int[][] + i -- int[] + j -- int[] + """ if i==j: print("A" + str(i),end = " ") else: - print("(",end = " ") - print_optimal_solution(optimal_solution,i,optimal_solution[i][j]) - print_optimal_solution(optimal_solution,optimal_solution[i][j]+1,j) - print(")",end = " ") + print("(", end=" ") + print_optimal_solution(optimal_solution, i, optimal_solution[i][j]) + print_optimal_solution(optimal_solution, optimal_solution[i][j]+1, j) + print(")", end=" ") + def main(): + """ + Testing for matrix_chain_ordering + """ array=[30,35,15,5,10,20,25] - n=len(array) + length=len(array) #Size of matrix created from above array will be # 30*35 35*15 15*5 5*10 10*20 20*25 - matrix , optimal_solution = matrix_chain_order(array) + matrix, optimal_solution = matrix_chain_order(array) + + print("No. of Operation required: "+str((matrix[1][length-1]))) + print_optimal_solution(optimal_solution,1,length-1) - print("No. of Operation required: "+str((matrix[1][n-1]))) - print_optimal_solution(optimal_solution,1,n-1) if __name__ == '__main__': main() diff --git a/dp/max_product_subarray.py b/algorithms/dp/max_product_subarray.py similarity index 76% rename from dp/max_product_subarray.py rename to algorithms/dp/max_product_subarray.py index 592e98fb1..7a9beac63 100644 --- a/dp/max_product_subarray.py +++ b/algorithms/dp/max_product_subarray.py @@ -14,15 +14,15 @@ def max_product(nums): :rtype: int """ lmin = lmax = gmax = nums[0] - for i in range(len(nums)): - t1 = nums[i] * lmax - t2 = nums[i] * lmin - lmax = max(max(t1, t2), nums[i]) - lmin = min(min(t1, t2), nums[i]) + for num in nums: + 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) -''' +""" Another approach that would print max product and the subarray Examples: @@ -34,18 +34,18 @@ def max_product(nums): #=> max_product_so_far: 24, [-4, -3, -2, -1] subarray_with_max_product([-3,0,1]) #=> max_product_so_far: 1, [1] -''' +""" def subarray_with_max_product(arr): ''' arr is list of positive/negative numbers ''' - l = len(arr) + 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(l): + for i in range(length): max_product_end *= arr[i] if arr[i] > 0: all_negative_flag = False @@ -60,8 +60,7 @@ def subarray_with_max_product(arr): so_far_start_i = max_start_i if all_negative_flag: - print("max_product_so_far: %s, %s" % - (reduce(lambda x, y: x * y, arr), arr)) + print(f"max_product_so_far: {reduce(lambda x, y: x * y, arr)}, {arr}") + else: - print("max_product_so_far: %s, %s" % - (product_so_far, arr[so_far_start_i:so_far_end_i + 1])) + print(f"max_product_so_far: {product_so_far},{arr[so_far_start_i:so_far_end_i + 1]}") diff --git a/dp/max_subarray.py b/algorithms/dp/max_subarray.py similarity index 100% rename from dp/max_subarray.py rename to algorithms/dp/max_subarray.py diff --git a/dp/min_cost_path.py b/algorithms/dp/min_cost_path.py similarity index 56% rename from dp/min_cost_path.py rename to algorithms/dp/min_cost_path.py index 8af947075..7771e1e0f 100644 --- a/dp/min_cost_path.py +++ b/algorithms/dp/min_cost_path.py @@ -1,7 +1,7 @@ """ author @goswami-rahul -To find minimum cost path +To find minimum cost path from station 0 to station N-1, where cost of moving from ith station to jth station is given as: @@ -9,11 +9,11 @@ where Matrix[i][j] denotes the cost of moving from station i --> station j for i < j -NOTE that values where Matrix[i][j] and i > j does not +NOTE that values where Matrix[i][j] and i > j does not mean anything, and hence represented by -1 or INF -For the input below (cost matrix), -Minimum cost is obtained as from { 0 --> 1 --> 3} +For the input below (cost matrix), +Minimum cost is obtained as from { 0 --> 1 --> 3} = cost[0][1] + cost[1][3] = 65 the Output will be: @@ -21,33 +21,38 @@ Time Complexity: O(n^2) Space Complexity: O(n) -""" +""" INF = float("inf") + def min_cost(cost): - - n = len(cost) + """Find minimum cost. + + Keyword arguments: + cost -- matrix containing costs + """ + length = len(cost) # dist[i] stores minimum cost from 0 --> i. - dist = [INF] * n + dist = [INF] * length dist[0] = 0 # cost from 0 --> 0 is zero. - - for i in range(n): - for j in range(i+1,n): + + for i in range(length): + for j in range(i+1,length): dist[j] = min(dist[j], dist[i] + cost[i][j]) - - return dist[n-1] + + return dist[length-1] + if __name__ == '__main__': - - cost = [ [ 0, 15, 80, 90], # cost[i][j] is the cost of + costs = [ [ 0, 15, 80, 90], # cost[i][j] is the cost of [-1, 0, 40, 50], # going from i --> j - [-1, -1, 0, 70], + [-1, -1, 0, 70], [-1, -1, -1, 0] ] # cost[i][j] = -1 for i > j - total_len = len(cost) - - mcost = min_cost(cost) + TOTAL_LEN = len(costs) + + mcost = min_cost(costs) assert mcost == 65 - - print("The Minimum cost to reach station %d is %d" % (total_len, mcost)) + + print(f"The minimum cost to reach station {TOTAL_LEN} is {mcost}") diff --git a/dp/num_decodings.py b/algorithms/dp/num_decodings.py similarity index 52% rename from dp/num_decodings.py rename to algorithms/dp/num_decodings.py index 541db6edf..87cd3e3dc 100644 --- a/dp/num_decodings.py +++ b/algorithms/dp/num_decodings.py @@ -17,33 +17,37 @@ """ -def num_decodings(s): +def num_decodings(enc_mes): """ :type s: str :rtype: int """ - if not s or s[0] == "0": + if not enc_mes or enc_mes[0] == "0": return 0 - wo_last, wo_last_two = 1, 1 - for i in range(1, len(s)): - x = wo_last if s[i] != "0" else 0 - y = wo_last_two if int(s[i-1:i+1]) < 27 and s[i-1] != "0" else 0 - wo_last_two = wo_last - wo_last = x+y - return wo_last + 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(s): - if not s or s.startswith('0'): +def num_decodings2(enc_mes): + """ + :type s: str + :rtype: int + """ + if not enc_mes or enc_mes.startswith('0'): return 0 stack = [1, 1] - for i in range(1, len(s)): - if s[i] == '0': - if s[i-1] == '0' or s[i-1] > '2': + 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': # only '10', '20' is valid return 0 stack.append(stack[-2]) - elif 9 < int(s[i-1:i+1]) < 27: + elif 9 < int(enc_mes[i-1:i+1]) < 27: # '01 - 09' is not allowed stack.append(stack[-2]+stack[-1]) else: diff --git a/algorithms/dp/planting_trees.py b/algorithms/dp/planting_trees.py new file mode 100644 index 000000000..ee8394b8e --- /dev/null +++ b/algorithms/dp/planting_trees.py @@ -0,0 +1,49 @@ +""" +An even number of trees are left along one side of a country road. You've been +assigned the job to plant these trees at an even interval on both sides of the +road. The length and width of the road are variable, and a pair of trees must +be planted at the beginning (at 0) and at the end (at length) of the road. Only +one tree can be moved at a time. The goal is to calculate the lowest amount of +distance that the trees have to be moved before they are all in a valid +position. +""" + +from math import sqrt + +def planting_trees(trees, length, width): + """ + Returns the minimum distance that trees have to be moved before they + are all in a valid state. + + Parameters: + tree (list): A sorted list of integers with all trees' + position along the road. + length (int): An integer with the length of the road. + width (int): An integer with the width of the road. + + Returns: + A float number with the total distance trees have been moved. + """ + 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] diff --git a/algorithms/dp/regex_matching.py b/algorithms/dp/regex_matching.py new file mode 100644 index 000000000..3b65e3594 --- /dev/null +++ b/algorithms/dp/regex_matching.py @@ -0,0 +1,61 @@ +""" +Implement regular expression matching with support for '.' and '*'. + +'.' Matches any single character. +'*' Matches zero or more of the preceding element. + +The matching should cover the entire input string (not partial). + +The function prototype should be: +bool is_match(const char *s, const char *p) + +Some examples: +is_match("aa","a") → false +is_match("aa","aa") → true +is_match("aaa","aa") → false +is_match("aa", "a*") → true +is_match("aa", ".*") → true +is_match("ab", ".*") → true +is_match("aab", "c*a*b") → true +""" + +def is_match(str_a, str_b): + """Finds if `str_a` matches `str_b` + + Keyword arguments: + str_a -- string + str_b -- string + """ + len_a, len_b = len(str_a) + 1, len(str_b) + 1 + matches = [[False] * len_b for _ in range(len_a)] + + # Match empty string with empty pattern + matches[0][0] = True + + # Match empty string with .* + 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 != '*': + # The previous character has matched and the current one + # has to be matched. Two possible matches: the same or . + matches[i][j] = matches[i - 1][j - 1] and \ + char_b in (char_a, '.') + else: + # Horizontal look up [j - 2]. + # Not use the character before *. + matches[i][j] |= matches[i][j - 2] + + # Vertical look up [i - 1]. + # Use at least one character before *. + # p a b * + # s 1 0 0 0 + # a 0 1 0 1 + # b 0 0 1 1 + # b 0 0 0 ? + if char_a == str_b[j - 2] or str_b[j - 2] == '.': + matches[i][j] |= matches[i - 1][j] + + return matches[-1][-1] diff --git a/dp/rod_cut.py b/algorithms/dp/rod_cut.py similarity index 65% rename from dp/rod_cut.py rename to algorithms/dp/rod_cut.py index b4a82245e..d9259e862 100644 --- a/dp/rod_cut.py +++ b/algorithms/dp/rod_cut.py @@ -1,24 +1,28 @@ -# A Dynamic Programming solution for Rod cutting problem +"""A Dynamic Programming solution for Rod cutting problem +""" + INT_MIN = -32767 - -# Returns the best obtainable price for a rod of length n and -# price[] as prices of different pieces + def cut_rod(price): + """ + Returns the best obtainable price for a rod of length n and + price[] as prices of different pieces + """ n = len(price) val = [0]*(n+1) - + # Build the table val[] in bottom up manner and return # the last entry from the table 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]) + max_val = max(max_val, price[j] + val[i-j-1]) val[i] = max_val - + return val[n] - + # Driver program to test above functions arr = [1, 5, 8, 9, 10, 17, 17, 20] print("Maximum Obtainable Value is " + str(cut_rod(arr))) - + # This code is contributed by Bhavya Jain diff --git a/dp/word_break.py b/algorithms/dp/word_break.py similarity index 55% rename from dp/word_break.py rename to algorithms/dp/word_break.py index ea244dc68..f520456b0 100644 --- a/dp/word_break.py +++ b/algorithms/dp/word_break.py @@ -1,44 +1,41 @@ """ Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, -determine if s can be segmented into a space-separated +determine if word can be segmented into a space-separated sequence of one or more dictionary words. You may assume the dictionary does not contain duplicate words. For example, given -s = "leetcode", +word = "leetcode", dict = ["leet", "code"]. Return true because "leetcode" can be segmented as "leet code". -""" - -""" -s = abc word_dict = ["a","bc"] +word = abc word_dict = ["a","bc"] True False False False """ # TC: O(N^2) SC: O(N) -def word_break(s, word_dict): +def word_break(word, word_dict): """ - :type s: str + :type word: str :type word_dict: Set[str] :rtype: bool """ - dp = [False] * (len(s)+1) - dp[0] = True - for i in range(1, len(s)+1): + 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[j] and s[j:i] in word_dict: - dp[i] = True + if dp_array[j] and word[j:i] in word_dict: + dp_array[i] = True break - return dp[-1] + return dp_array[-1] if __name__ == "__main__": - s = "keonkim" + STR = "keonkim" dic = ["keon", "kim"] - print(word_break(s, dic)) + print(word_break(str, dic)) diff --git a/algorithms/graph/__init__.py b/algorithms/graph/__init__.py new file mode 100644 index 000000000..1f52f9bdb --- /dev/null +++ b/algorithms/graph/__init__.py @@ -0,0 +1,12 @@ +""" +Collection of algorithms on graphs. +""" + +from .tarjan import * +from .check_bipartite import * +from .maximum_flow import * +from .maximum_flow_bfs import * +from .maximum_flow_dfs import * +from .all_pairs_shortest_path import * +from .bellman_ford import * +from .prims_minimum_spanning import * diff --git a/algorithms/graph/all_pairs_shortest_path.py b/algorithms/graph/all_pairs_shortest_path.py new file mode 100644 index 000000000..d8e6feca3 --- /dev/null +++ b/algorithms/graph/all_pairs_shortest_path.py @@ -0,0 +1,42 @@ +""" +Given a n*n adjacency array. +it will give you all pairs shortest path length. +use deepcopy to preserve the original information. + +Time complexity : O(E^3) + +example + +a = [[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 + +[[0 , 0.1 , 0.101, 0.142, 0.277], + [0.436, 0 , 0.191, 0.192, 0.343], + [0.245, 0.345, 0 , 0.333, 0.484], + [0.706, 0.27 , 0.461, 0 , 0.151], + [0.555, 0.119, 0.31 , 0.311, 0]] + +""" +import copy + +def all_pairs_shortest_path(adjacency_matrix): + """ + Given a matrix of the edge weights between respective nodes, returns a + matrix containing the shortest distance distance between the two nodes. + """ + + 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 diff --git a/algorithms/graph/bellman_ford.py b/algorithms/graph/bellman_ford.py new file mode 100644 index 000000000..ea1e0465b --- /dev/null +++ b/algorithms/graph/bellman_ford.py @@ -0,0 +1,48 @@ +""" +Determination of single-source shortest-path. +""" + +def bellman_ford(graph, source): + """ + This Bellman-Ford Code is for determination whether we can get + shortest path from given graph or not for single-source shortest-paths problem. + In other words, if given graph has any negative-weight cycle that is reachable + from the source, then it will give answer False for "no solution exits". + For argument graph, it should be a dictionary type + such as + graph = { + 'a': {'b': 6, 'e': 7}, + 'b': {'c': 5, 'd': -4, 'e': 8}, + 'c': {'b': -2}, + 'd': {'a': 2, 'c': 7}, + 'e': {'b': -3} + } + """ + weight = {} + pre_node = {} + + initialize_single_source(graph, source, weight, pre_node) + + for _ in range(1, len(graph)): + for node in graph: + for adjacent in graph[node]: + if weight[adjacent] > weight[node] + graph[node][adjacent]: + weight[adjacent] = weight[node] + graph[node][adjacent] + pre_node[adjacent] = node + + for node in graph: + for adjacent in graph[node]: + if weight[adjacent] > weight[node] + graph[node][adjacent]: + return False + + return True + +def initialize_single_source(graph, source, weight, pre_node): + """ + Initialize data structures for Bellman-Ford algorithm. + """ + for node in graph: + weight[node] = float('inf') + pre_node[node] = None + + weight[source] = 0 diff --git a/algorithms/graph/check_bipartite.py b/algorithms/graph/check_bipartite.py new file mode 100644 index 000000000..51a84ab4c --- /dev/null +++ b/algorithms/graph/check_bipartite.py @@ -0,0 +1,40 @@ +""" +Bipartite graph is a graph whose vertices can be divided into two disjoint and independent sets. +(https://en.wikipedia.org/wiki/Bipartite_graph) +""" + +def check_bipartite(adj_list): + """ + Determine if the given graph is bipartite. + + Time complexity is O(|E|) + Space complexity is O(|V|) + """ + + vertices = len(adj_list) + + # Divide vertexes in the graph into set_type 0 and 1 + # Initialize all set_types as -1 + set_type = [-1 for v in range(vertices)] + set_type[0] = 0 + + queue = [0] + + while queue: + current = queue.pop(0) + + # If there is a self-loop, it cannot be bipartite + 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 of u opposite of v + set_type[adjacent] = 1 - set_type[current] + queue.append(adjacent) + + return True diff --git a/algorithms/graph/check_digraph_strongly_connected.py b/algorithms/graph/check_digraph_strongly_connected.py new file mode 100644 index 000000000..03dd6ab79 --- /dev/null +++ b/algorithms/graph/check_digraph_strongly_connected.py @@ -0,0 +1,69 @@ +""" +In a directed graph, a strongly connected component is a set of vertices such +that for any pairs of vertices u and v there exists a path (u-...-v) that +connects them. A graph is strongly connected if it is a single strongly +connected component. +""" + +from collections import defaultdict + +class Graph: + """ + A directed graph where edges are one-way (a two-way edge can be represented by using two edges). + """ + + def __init__(self,vertex_count): + """ + Create a new graph with vertex_count vertices. + """ + + self.vertex_count = vertex_count + self.graph = defaultdict(list) + + def add_edge(self,source,target): + """ + Add an edge going from source to target + """ + self.graph[source].append(target) + + def dfs(self): + """ + Determine if all nodes are reachable from node 0 + """ + visited = [False] * self.vertex_count + self.dfs_util(0,visited) + if visited == [True]*self.vertex_count: + return True + return False + + def dfs_util(self,source,visited): + """ + Determine if all nodes are reachable from the given node + """ + visited[source] = True + for adjacent in self.graph[source]: + if not visited[adjacent]: + self.dfs_util(adjacent,visited) + + def reverse_graph(self): + """ + Create a new graph where every edge a->b is replaced with an edge b->a + """ + reverse_graph = Graph(self.vertex_count) + for source, adjacent in self.graph.items(): + for target in adjacent: + # Note: we reverse the order of arguments + # pylint: disable=arguments-out-of-order + reverse_graph.add_edge(target,source) + return reverse_graph + + + def is_strongly_connected(self): + """ + Determine if the graph is strongly connected. + """ + if self.dfs(): + reversed_graph = self.reverse_graph() + if reversed_graph.dfs(): + return True + return False diff --git a/graph/clone_graph.py b/algorithms/graph/clone_graph.py similarity index 53% rename from graph/clone_graph.py rename to algorithms/graph/clone_graph.py index 0fbae1d43..84d0324cf 100644 --- a/graph/clone_graph.py +++ b/algorithms/graph/clone_graph.py @@ -1,4 +1,4 @@ -""" +r""" Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. @@ -29,69 +29,95 @@ import collections -# Definition for a undirected graph node class UndirectedGraphNode: - def __init__(self, x): - self.label = x + """ + A node in an undirected graph. Contains a label and a list of neighbouring + nodes (initially empty). + """ + + def __init__(self, label): + self.label = label self.neighbors = [] + def shallow_copy(self): + """ + Return a shallow copy of this node (ignoring any neighbors) + """ + return UndirectedGraphNode(self.label) + + def add_neighbor(self, node): + """ + Adds a new neighbor + """ + self.neighbors.append(node) + -# BFS def clone_graph1(node): + """ + Returns a new graph as seen from the given node using a breadth first search (BFS). + """ if not node: - return - node_copy = UndirectedGraphNode(node.label) + return None + node_copy = node.shallow_copy() dic = {node: node_copy} queue = collections.deque([node]) while queue: node = queue.popleft() for neighbor in node.neighbors: if neighbor not in dic: # neighbor is not visited - neighbor_copy = UndirectedGraphNode(neighbor.label) + neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy - dic[node].neighbors.append(neighbor_copy) + dic[node].add_neighbor(neighbor_copy) queue.append(neighbor) else: - dic[node].neighbors.append(dic[neighbor]) + dic[node].add_neighbor(dic[neighbor]) return node_copy -# DFS iteratively def clone_graph2(node): + """ + Returns a new graph as seen from the given node using an iterative depth first search (DFS). + """ if not node: - return - node_copy = UndirectedGraphNode(node.label) + return None + node_copy = node.shallow_copy() dic = {node: node_copy} stack = [node] while stack: node = stack.pop() for neighbor in node.neighbors: if neighbor not in dic: - neighbor_copy = UndirectedGraphNode(neighbor.label) + neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy - dic[node].neighbors.append(neighbor_copy) + dic[node].add_neighbor(neighbor_copy) stack.append(neighbor) else: - dic[node].neighbors.append(dic[neighbor]) + dic[node].add_neighbor(dic[neighbor]) return node_copy -# DFS recursively def clone_graph(node): + """ + Returns a new graph as seen from the given node using a recursive depth first search (DFS). + """ if not node: - return - node_copy = UndirectedGraphNode(node.label) + return None + node_copy = node.shallow_copy() dic = {node: node_copy} dfs(node, dic) return node_copy def dfs(node, dic): + """ + Clones a graph using a recursive depth first search. Stores the clones in + the dictionary, keyed by the original nodes. + """ for neighbor in node.neighbors: if neighbor not in dic: - neighbor_copy = UndirectedGraphNode(neighbor.label) + neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy - dic[node].neighbors.append(neighbor_copy) + dic[node].add_neighbor(neighbor_copy) dfs(neighbor, dic) else: - dic[node].neighbors.append(dic[neighbor]) + dic[node].add_neighbor(dic[neighbor]) diff --git a/algorithms/graph/count_connected_number_of_component.py b/algorithms/graph/count_connected_number_of_component.py new file mode 100644 index 000000000..a58eda6c3 --- /dev/null +++ b/algorithms/graph/count_connected_number_of_component.py @@ -0,0 +1,59 @@ +#count connected no of component using DFS +''' +In graph theory, a component, sometimes called a connected component, +of an undirected graph is a subgraph in which any +two vertices are connected to each other by paths. + +Example: + + + 1 3------------7 + | + | + 2--------4 + | | + | | output = 2 + 6--------5 + +''' + +# Code is Here + +def dfs(source,visited,adjacency_list): + ''' Function that performs DFS ''' + + visited[source] = True + for child in adjacency_list[source]: + if not visited[child]: + dfs(child,visited,adjacency_list) + +def count_components(adjacency_list,size): + ''' + Function that counts the Connected components on bases of DFS. + return type : int + ''' + + 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 main(): + """ + Example application + """ + node_count,edge_count = map(int, input("Enter the Number of Nodes and Edges \n").split(' ')) + adjacency = [[] for _ in range(node_count+1)] + for _ in range(edge_count): + print("Enter the edge's Nodes in form of `source target`\n") + source,target = map(int,input().split(' ')) + adjacency[source].append(target) + adjacency[target].append(source) + print("Total number of Connected Components are : ", count_components(adjacency,node_count)) + +# Driver code +if __name__ == '__main__': + main() diff --git a/graph/cycle_detection.py b/algorithms/graph/cycle_detection.py similarity index 61% rename from graph/cycle_detection.py rename to algorithms/graph/cycle_detection.py index 61cf61c1f..0821b1c0a 100644 --- a/graph/cycle_detection.py +++ b/algorithms/graph/cycle_detection.py @@ -9,27 +9,22 @@ class TraversalState(Enum): + """ + For a given node: + - WHITE: has not been visited yet + - GRAY: is currently being investigated for a cycle + - BLACK: is not part of a cycle + """ WHITE = 0 GRAY = 1 BLACK = 2 - -example_graph_with_cycle = {'A': ['B', 'C'], - 'B': ['D'], - 'C': ['F'], - 'D': ['E', 'F'], - 'E': ['B'], - 'F': []} - -example_graph_without_cycle = {'A': ['B', 'C'], - 'B': ['D', 'E'], - 'C': ['F'], - 'D': ['E'], - 'E': [], - 'F': []} - - def is_in_cycle(graph, traversal_states, vertex): + """ + Determines if the given vertex is in a cycle. + + :param: traversal_states: for each vertex, the state it is in + """ if traversal_states[vertex] == TraversalState.GRAY: return True traversal_states[vertex] = TraversalState.GRAY @@ -41,12 +36,20 @@ def is_in_cycle(graph, traversal_states, vertex): def contains_cycle(graph): + """ + Determines if there is a cycle in the given graph. + The graph should be given as a dictionary: + + graph = {'A': ['B', 'C'], + 'B': ['D'], + 'C': ['F'], + 'D': ['E', 'F'], + 'E': ['B'], + 'F': []} + """ 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 - -print(contains_cycle(example_graph_with_cycle)) -print(contains_cycle(example_graph_without_cycle)) diff --git a/algorithms/graph/dijkstra.py b/algorithms/graph/dijkstra.py new file mode 100644 index 000000000..e5022e3af --- /dev/null +++ b/algorithms/graph/dijkstra.py @@ -0,0 +1,49 @@ +""" +Dijkstra's single-source shortest-path algorithm +""" + +class Dijkstra(): + """ + A fully connected directed graph with edge weights + """ + + def __init__(self, vertex_count): + self.vertex_count = vertex_count + self.graph = [[0 for _ in range(vertex_count)] for _ in range(vertex_count)] + + def min_distance(self, dist, min_dist_set): + """ + Find the vertex that is closest to the visited set + """ + min_dist = float("inf") + 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): + """ + Given a node, returns the shortest distance to every other node + """ + dist = [float("inf")] * self.vertex_count + dist[src] = 0 + min_dist_set = [False] * self.vertex_count + + for _ in range(self.vertex_count): + #minimum distance vertex that is not processed + source = self.min_distance(dist, min_dist_set) + + #put minimum distance vertex in shortest tree + min_dist_set[source] = True + + #Update dist value of the adjacent vertices + 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 diff --git a/graph/find_all_cliques.py b/algorithms/graph/find_all_cliques.py similarity index 62% rename from graph/find_all_cliques.py rename to algorithms/graph/find_all_cliques.py index 10f7a24eb..f1db16ed5 100644 --- a/graph/find_all_cliques.py +++ b/algorithms/graph/find_all_cliques.py @@ -1,12 +1,19 @@ -# takes dict of sets -# each key is a vertex -# value is set of all edges connected to vertex -# returns list of lists (each sub list is a maximal clique) -# implementation of the basic algorithm described in: -# Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph", - +""" +Finds all cliques in an undirected graph. A clique is a set of vertices in the +graph such that the subgraph is fully connected (ie. for any pair of nodes in +the subgraph there is an edge between them). +""" def find_all_cliques(edges): + """ + takes dict of sets + each key is a vertex + value is set of all edges connected to vertex + returns list of lists (each sub list is a maximal clique) + implementation of the basic algorithm described in: + Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph", + """ + def expand_clique(candidates, nays): nonlocal compsub if not candidates and not nays: diff --git a/graph/find_path.py b/algorithms/graph/find_path.py similarity index 68% rename from graph/find_path.py rename to algorithms/graph/find_path.py index 5008da05a..253e43ca3 100644 --- a/graph/find_path.py +++ b/algorithms/graph/find_path.py @@ -1,14 +1,14 @@ -myGraph = {'A': ['B', 'C'], - 'B': ['C', 'D'], - 'C': ['D', 'F'], - 'D': ['C'], - 'E': ['F'], - 'F': ['C']} +""" +Functions for finding paths in graphs. +""" -# find path from start to end using recursion with backtracking +# pylint: disable=dangerous-default-value def find_path(graph, start, end, path=[]): + """ + Find a path between two nodes using recursion and backtracking. + """ path = path + [start] - if (start == end): + if start == end: return path if not start in graph: return None @@ -18,14 +18,16 @@ def find_path(graph, start, end, path=[]): return newpath return None -# find all path +# pylint: disable=dangerous-default-value def find_all_path(graph, start, end, path=[]): + """ + Find all paths between two nodes using recursion and backtracking + """ path = path + [start] - print(path) - if (start == end): + if start == end: return [path] if not start in graph: - return None + return [] paths = [] for node in graph[start]: if node not in path: @@ -35,6 +37,9 @@ def find_all_path(graph, start, end, path=[]): return paths def find_shortest_path(graph, start, end, path=[]): + """ + find the shortest path between two nodes + """ path = path + [start] if start == end: return path @@ -43,11 +48,8 @@ def find_shortest_path(graph, start, end, path=[]): shortest = None for node in graph[start]: if node not in path: - newpath = find_shortest_path(graph, start, end, path) + newpath = find_shortest_path(graph, node, end, path) if newpath: if not shortest or len(newpath) < len(shortest): shortest = newpath return shortest - -print(find_all_path(myGraph, 'A', 'F')) -# print(find_shortest_path(myGraph, 'A', 'D')) diff --git a/graph/graph.py b/algorithms/graph/graph.py similarity index 57% rename from graph/graph.py rename to algorithms/graph/graph.py index 31e564599..5d4305933 100644 --- a/graph/graph.py +++ b/algorithms/graph/graph.py @@ -3,24 +3,31 @@ It can be shared across graph algorithms. """ -class Node(object): +class Node: + """ + A node/vertex in a graph. + """ + def __init__(self, name): self.name = name @staticmethod def get_name(obj): + """ + Return the name of the node + """ if isinstance(obj, Node): return obj.name - elif isinstance(obj, str): + if isinstance(obj, str): return obj return'' - + def __eq__(self, obj): return self.name == self.get_name(obj) def __repr__(self): return self.name - + def __hash__(self): return hash(self.name) @@ -42,72 +49,63 @@ def __ge__(self, obj): def __bool__(self): return self.name -class DirectedEdge(object): +class DirectedEdge: + """ + A directed edge in a directed graph. + Stores the source and target node of the edge. + """ + def __init__(self, node_from, node_to): - self.nf = node_from - self.nt = node_to + self.source = node_from + self.target = node_to def __eq__(self, obj): if isinstance(obj, DirectedEdge): - return obj.nf == self.nf and obj.nt == self.nt + return obj.source == self.source and obj.target == self.target return False - + def __repr__(self): - return '({0} -> {1})'.format(self.nf, self.nt) + return f"({self.source} -> {self.target})" + +class DirectedGraph: + """ + A directed graph. + Stores a set of nodes, edges and adjacency matrix. + """ -class DirectedGraph(object): + # pylint: disable=dangerous-default-value def __init__(self, load_dict={}): self.nodes = [] self.edges = [] - self.adjmt = {} + self.adjacency_list = {} - if load_dict and type(load_dict) == dict: - for v in load_dict: - node_from = self.add_node(v) - self.adjmt[node_from] = [] - for w in load_dict[v]: - node_to = self.add_node(w) - self.adjmt[node_from].append(node_to) - self.add_edge(v, w) + 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): + """ + Add a new named node to the graph. + """ 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, node_name_to): + """ + Add a new edge to the graph between two nodes. + """ 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 - -class Graph: - def __init__(self, vertices): - # No. of vertices - self.V = vertices - - # default dictionary to store graph - self.graph = {} - - # To store transitive closure - self.tc = [[0 for j in range(self.V)] for i in range(self.V)] - - # function to add an edge to graph - def add_edge(self, u, v): - if u in self.graph: - self.graph[u].append(v) - else: - self.graph[u] = [v] - -#g = 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) diff --git a/algorithms/graph/markov_chain.py b/algorithms/graph/markov_chain.py new file mode 100644 index 000000000..78c50bd12 --- /dev/null +++ b/algorithms/graph/markov_chain.py @@ -0,0 +1,39 @@ +""" +Implements a markov chain. Chains are described using a dictionary: + + my_chain = { + 'A': {'A': 0.6, + 'E': 0.4}, + 'E': {'A': 0.7, + 'E': 0.3} + } +""" + +import random + +def __choose_state(state_map): + """ + Choose the next state randomly + """ + choice = random.random() + probability_reached = 0 + for state, probability in state_map.items(): + probability_reached += probability + if probability_reached > choice: + return state + return None + +def next_state(chain, current_state): + """ + Given a markov-chain, randomly chooses the next state given the current state. + """ + next_state_map = chain.get(current_state) + return __choose_state(next_state_map) + +def iterating_markov_chain(chain, state): + """ + Yield a sequence of states given a markov chain and the initial state + """ + while True: + state = next_state(chain, state) + yield state diff --git a/algorithms/graph/maximum_flow.py b/algorithms/graph/maximum_flow.py new file mode 100644 index 000000000..b46e70790 --- /dev/null +++ b/algorithms/graph/maximum_flow.py @@ -0,0 +1,149 @@ +""" +Given the capacity, source and sink of a graph, +computes the maximum flow from source to sink. +Input : capacity, source, sink +Output : maximum flow from source to sink +Capacity is a two-dimensional array that is v*v. +capacity[i][j] implies the capacity of the edge from i to j. +If there is no edge from i to j, capacity[i][j] should be zero. +""" + +from queue import Queue + +# pylint: disable=too-many-arguments +def dfs(capacity, flow, visit, vertices, idx, sink, current_flow = 1 << 63): + """ + Depth First Search implementation for Ford-Fulkerson algorithm. + """ + + # DFS function for ford_fulkerson algorithm. + 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, source, sink): + """ + Computes maximum flow from source to sink using DFS. + Time Complexity : O(Ef) + E is the number of edges and f is the maximum flow in the graph. + """ + 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, source, sink): + """ + Computes maximum flow from source to sink using BFS. + Time complexity : O(V*E^2) + V is the number of vertices and E is the number of edges. + """ + vertices = len(capacity) + ret = 0 + flow = [[0]*vertices for _ in range(vertices)] + while True: + tmp = 0 + queue = Queue() + visit = [False for _ in range(vertices)] + par = [-1 for _ in range(vertices)] + visit[source] = True + queue.put((source, 1 << 63)) + # Finds new flow using BFS. + 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 + # Update flow array following parent starting from sink. + while parent != -1: + flow[parent][idx] += tmp + flow[idx][parent] -= tmp + idx = parent + parent = par[parent] + return ret + +def dinic_bfs(capacity, flow, level, source, sink): + """ + BFS function for Dinic algorithm. + Check whether sink is reachable only using edges that is not full. + """ + vertices = len(capacity) + queue = 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, flow, level, idx, sink, work, current_flow = 1 << 63): + """ + DFS function for Dinic algorithm. + Finds new flow using edges that is not full. + """ + 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, source, sink): + """ + Computes maximum flow from source to sink using Dinic algorithm. + Time complexity : O(V^2*E) + V is the number of vertices and E is the number of edges. + """ + vertices = len(capacity) + flow = [[0]*vertices for i in range(vertices)] + ret = 0 + while True: + level = [-1 for i in range(vertices)] + work = [0 for i 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 diff --git a/algorithms/graph/maximum_flow_bfs.py b/algorithms/graph/maximum_flow_bfs.py new file mode 100644 index 000000000..bd056c8d3 --- /dev/null +++ b/algorithms/graph/maximum_flow_bfs.py @@ -0,0 +1,87 @@ +""" +Given a n*n adjacency array. +it will give you a maximum flow. +This version use BFS to search path. + +Assume the first is the source and the last is the sink. + +Time complexity - O(Ef) + +example + +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]] + +answer should be + +23 + +""" +import copy +import queue +import math + +def maximum_flow_bfs(adjacency_matrix): + """ + Get the maximum flow through a graph using a breadth first search + """ + #initial setting + new_array = copy.deepcopy(adjacency_matrix) + total = 0 + + while True: + #setting min to max_value + min_flow = math.inf + #save visited nodes + visited = [0]*len(new_array) + #save parent nodes + path = [0]*len(new_array) + + #initialize queue for BFS + bfs = queue.Queue() + + #initial setting + visited[0] = 1 + bfs.put(0) + + #BFS to find path + while bfs.qsize() > 0: + #pop from queue + src = bfs.get() + for k in range(len(new_array)): + #checking capacity and visit + if(new_array[src][k] > 0 and visited[k] == 0 ): + #if not, put into queue and chage to visit and save path + visited[k] = 1 + bfs.put(k) + path[k] = src + + #if there is no path from src to sink + if visited[len(new_array) - 1] == 0: + break + + #initial setting + tmp = len(new_array) - 1 + + #Get minimum flow + while tmp != 0: + #find minimum flow + if min_flow > new_array[path[tmp]][tmp]: + min_flow = new_array[path[tmp]][tmp] + tmp = path[tmp] + + #initial setting + tmp = len(new_array) - 1 + + #reduce capacity + while tmp != 0: + new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min_flow + tmp = path[tmp] + + total = total + min_flow + + return total diff --git a/algorithms/graph/maximum_flow_dfs.py b/algorithms/graph/maximum_flow_dfs.py new file mode 100644 index 000000000..e27c4e851 --- /dev/null +++ b/algorithms/graph/maximum_flow_dfs.py @@ -0,0 +1,88 @@ +""" +Given a n*n adjacency array. +it will give you a maximum flow. +This version use DFS to search path. + +Assume the first is the source and the last is the sink. + +Time complexity - O(Ef) + +example + +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]] + +answer should be + +23 + +""" +import copy +import math + +def maximum_flow_dfs(adjacency_matrix): + """ + Get the maximum flow through a graph using a depth first search + """ + + #initial setting + new_array = copy.deepcopy(adjacency_matrix) + total = 0 + + while True: + #setting min to max_value + min = math.inf + #save visited nodes + visited = [0]*len(new_array) + #save parent nodes + path = [0]*len(new_array) + + #initialize stack for DFS + stack = [] + + #initial setting + visited[0] = 1 + stack.append(0) + + #DFS to find path + while len(stack) > 0: + #pop from queue + src = stack.pop() + for k in range(len(new_array)): + #checking capacity and visit + if new_array[src][k] > 0 and visited[k] == 0: + #if not, put into queue and chage to visit and save path + visited[k] = 1 + stack.append(k) + path[k] = src + + #if there is no path from src to sink + if visited[len(new_array) - 1] == 0: + break + + #initial setting + tmp = len(new_array) - 1 + + #Get minimum flow + while tmp != 0: + #find minimum flow + if min > new_array[path[tmp]][tmp]: + min = new_array[path[tmp]][tmp] + tmp = path[tmp] + + #initial setting + tmp = len(new_array) - 1 + + #reduce capacity + while tmp != 0: + new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min + tmp = path[tmp] + + total = total + min + + return total + diff --git a/algorithms/graph/minimum_spanning_tree.py b/algorithms/graph/minimum_spanning_tree.py new file mode 100644 index 000000000..d53a957ca --- /dev/null +++ b/algorithms/graph/minimum_spanning_tree.py @@ -0,0 +1,151 @@ +""" +Minimum spanning tree (MST) is going to use an undirected graph +""" + +import sys + +# pylint: disable=too-few-public-methods +class Edge: + """ + An edge of an undirected graph + """ + + def __init__(self, source, target, weight): + self.source = source + self.target = target + self.weight = weight + + +class DisjointSet: + """ + The disjoint set is represented with an list of integers where + is the parent of the node at position . + If = , it's a root, or a head, of a set + """ + + def __init__(self, size): + """ + Args: + n (int): Number of vertices in the graph + """ + + self.parent = [None] * size # Contains wich node is the parent of the node at poisition + self.size = [1] * size # Contains size of node at index , used to optimize merge + for i in range(size): + self.parent[i] = i # Make all nodes his own parent, creating n sets. + + def merge_set(self, node1, node2): + """ + Args: + node1, node2 (int): Indexes of nodes whose sets will be merged. + """ + + # Get the set of nodes at position and + # If and are the roots, this will be constant O(1) + node1 = self.find_set(node1) + node2 = self.find_set(node2) + + # Join the shortest node to the longest, minimizing tree size (faster find) + if self.size[node1] < self.size[node2]: + self.parent[node1] = node2 # Merge set(a) and set(b) + self.size[node2] += self.size[node1] # Add size of old set(a) to set(b) + else: + self.parent[node2] = node1 # Merge set(b) and set(a) + self.size[node1] += self.size[node2] # Add size of old set(b) to set(a) + + def find_set(self, node): + """ + Get the root element of the set containing + """ + if self.parent[node] != node: + # Very important, memoize result of the + # recursion in the list to optimize next + # calls and make this operation practically constant, O(1) + self.parent[node] = self.find_set(self.parent[node]) + + # node it's the set root, so we can return that index + return self.parent[node] + + +def kruskal(vertex_count, edges, forest): + """ + Args: + vertex_count (int): Number of vertices in the graph + edges (list of Edge): Edges of the graph + forest (DisjointSet): DisjointSet of the vertices + Returns: + int: sum of weights of the minnimum spanning tree + + Kruskal algorithm: + This algorithm will find the optimal graph with less edges and less + total weight to connect all vertices (MST), the MST will always contain + n-1 edges because it's the minimum required to connect n vertices. + + Procedure: + Sort the edges (criteria: less weight). + Only take edges of nodes in different sets. + If we take a edge, we need to merge the sets to discard these. + After repeat this until select n-1 edges, we will have the complete MST. + """ + edges.sort(key=lambda edge: edge.weight) + + mst = [] # List of edges taken, minimum spanning tree + + for edge in edges: + set_u = forest.find_set(edge.u) # Set of the node + set_v = forest.find_set(edge.v) # Set of the node + if set_u != set_v: + forest.merge_set(set_u, set_v) + mst.append(edge) + if len(mst) == vertex_count-1: + # If we have selected n-1 edges, all the other + # edges will be discarted, so, we can stop here + break + + return sum([edge.weight for edge in mst]) + + +def main(): + """ + Test. How input works: + Input consists of different weighted, connected, undirected graphs. + line 1: + integers n, m + lines 2..m+2: + edge with the format -> node index u, node index v, integer weight + + Samples of input: + + 5 6 + 1 2 3 + 1 3 8 + 2 4 5 + 3 4 2 + 3 5 4 + 4 5 6 + + 3 3 + 2 1 20 + 3 1 20 + 2 3 100 + + Sum of weights of the optimal paths: + 14, 40 + """ + for size in sys.stdin: + vertex_count, edge_count = map(int, size.split()) + forest = DisjointSet(edge_count) + edges = [None] * edge_count # Create list of size + + # Read edges from input + for i in range(edge_count): + source, target, weight = map(int, input().split()) + source -= 1 # Convert from 1-indexed to 0-indexed + target -= 1 # Convert from 1-indexed to 0-indexed + edges[i] = Edge(source, target, weight) + + # After finish input and graph creation, use Kruskal algorithm for MST: + print("MST weights sum:", kruskal(vertex_count, edges, forest)) + +if __name__ == "__main__": + main() diff --git a/algorithms/graph/path_between_two_vertices_in_digraph.py b/algorithms/graph/path_between_two_vertices_in_digraph.py new file mode 100644 index 000000000..4bf290253 --- /dev/null +++ b/algorithms/graph/path_between_two_vertices_in_digraph.py @@ -0,0 +1,51 @@ +""" +Determine if there is a path between nodes in a graph +""" + +from collections import defaultdict + + +class Graph: + """ + A directed graph + """ + + def __init__(self,vertex_count): + self.vertex_count = vertex_count + self.graph = defaultdict(list) + self.has_path = False + + def add_edge(self,source,target): + """ + Add a new directed edge to the graph + """ + self.graph[source].append(target) + + def dfs(self,source,target): + """ + Determine if there is a path from source to target using a depth first search + """ + visited = [False] * self.vertex_count + self.dfsutil(visited,source,target,) + + def dfsutil(self,visited,source,target): + """ + Determine if there is a path from source to target using a depth first search. + :param: visited should be an array of booleans determining if the + corresponding vertex has been visited already + """ + 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.dfsutil(visited,source,i) + + def is_reachable(self,source,target): + """ + Determine if there is a path from source to target + """ + self.has_path = False + self.dfs(source,target) + return self.has_path diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py new file mode 100644 index 000000000..af7cb4357 --- /dev/null +++ b/algorithms/graph/prims_minimum_spanning.py @@ -0,0 +1,42 @@ +''' +This Prim's Algorithm Code is for finding weight of minimum spanning tree +of a connected graph. +For argument graph, it should be a dictionary type such as: + + graph = { + 'a': [ [3, 'b'], [8,'c'] ], + 'b': [ [3, 'a'], [5, 'd'] ], + 'c': [ [8, 'a'], [2, 'd'], [4, 'e'] ], + 'd': [ [5, 'b'], [2, 'c'], [6, 'e'] ], + 'e': [ [4, 'c'], [6, 'd'] ] + } + +where 'a','b','c','d','e' are nodes (these can be 1,2,3,4,5 as well) +''' + + +import heapq # for priority queue + +def prims_minimum_spanning(graph_used): + """ + Prim's algorithm to find weight of minimum spanning tree + """ + vis=[] + heap=[[0,1]] + prim = 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 diff --git a/graph/satisfiability.py b/algorithms/graph/satisfiability.py similarity index 54% rename from graph/satisfiability.py rename to algorithms/graph/satisfiability.py index bc1d1892e..0cae8ee92 100644 --- a/graph/satisfiability.py +++ b/algorithms/graph/satisfiability.py @@ -1,45 +1,49 @@ -''' +""" Given a formula in conjunctive normal form (2-CNF), finds a way to assign True/False values to all variables to satisfy all clauses, or reports there is no solution. https://en.wikipedia.org/wiki/2-satisfiability -''' -''' Format: +Format: - each clause is a pair of literals - each literal in the form (name, is_neg) where name is an arbitrary identifier, and is_neg is true if the literal is negated -''' -formula = [(('x', False), ('y', False)), - (('y', True), ('y', True)), - (('a', False), ('b', False)), - (('a', True), ('c', True)), - (('c', False), ('b', True))] +""" +def dfs_transposed(vertex, graph, order, visited): + """ + Perform a depth first search traversal of the graph starting at the given vertex. + Stores the order in which nodes were visited to the list, in transposed order. + """ + visited[vertex] = True -def dfs_transposed(v, graph, order, vis): - vis[v] = True + for adjacent in graph[vertex]: + if not visited[adjacent]: + dfs_transposed(adjacent, graph, order, visited) - for u in graph[v]: - if not vis[u]: - dfs_transposed(u, graph, order, vis) + order.append(vertex) - order.append(v) +def dfs(vertex, current_comp, vertex_scc, graph, visited): + """ + Perform a depth first search traversal of the graph starting at the given vertex. + Records all visited nodes as being of a certain strongly connected component. + """ + visited[vertex] = True + vertex_scc[vertex] = current_comp -def dfs(v, current_comp, vertex_scc, graph, vis): - vis[v] = True - vertex_scc[v] = current_comp - - for u in graph[v]: - if not vis[u]: - dfs(u, current_comp, vertex_scc, graph, vis) + for adjacent in graph[vertex]: + if not visited[adjacent]: + dfs(adjacent, current_comp, vertex_scc, graph, visited) def add_edge(graph, vertex_from, vertex_to): + """ + Add a directed edge to the graph. + """ if vertex_from not in graph: graph[vertex_from] = [] @@ -49,26 +53,26 @@ def add_edge(graph, vertex_from, vertex_to): def scc(graph): ''' Computes the strongly connected components of a graph ''' order = [] - vis = {vertex: False for vertex in graph} + visited = {vertex: False for vertex in graph} graph_transposed = {vertex: [] for vertex in graph} - for (v, neighbours) in graph.iteritems(): - for u in neighbours: - add_edge(graph_transposed, u, v) + for (source, neighbours) in graph.iteritems(): + for target in neighbours: + add_edge(graph_transposed, target, source) - for v in graph: - if not vis[v]: - dfs_transposed(v, graph_transposed, order, vis) + for vertex in graph: + if not visited[vertex]: + dfs_transposed(vertex, graph_transposed, order, visited) - vis = {vertex: False for vertex in graph} + visited = {vertex: False for vertex in graph} vertex_scc = {} current_comp = 0 - for v in reversed(order): - if not vis[v]: + for vertex in reversed(order): + if not visited[vertex]: # Each dfs will visit exactly one component - dfs(v, current_comp, vertex_scc, graph, vis) + dfs(vertex, current_comp, vertex_scc, graph, visited) current_comp += 1 return vertex_scc @@ -91,6 +95,9 @@ def build_graph(formula): def solve_sat(formula): + """ + Solves the 2-SAT problem + """ graph = build_graph(formula) vertex_scc = scc(graph) @@ -119,8 +126,20 @@ def solve_sat(formula): return value -if __name__ == '__main__': +def main(): + """ + Entry point for testing + """ + formula = [(('x', False), ('y', False)), + (('y', True), ('y', True)), + (('a', False), ('b', False)), + (('a', True), ('c', True)), + (('c', False), ('b', True))] + result = solve_sat(formula) - for (variable, assign) in result.iteritems(): - print("{}:{}".format(variable, assign)) + for (variable, assign) in result.items(): + print(f"{variable}:{assign}") + +if __name__ == '__main__': + main() diff --git a/algorithms/graph/strongly_connected_components_kosaraju.py b/algorithms/graph/strongly_connected_components_kosaraju.py new file mode 100644 index 000000000..0c82a7af6 --- /dev/null +++ b/algorithms/graph/strongly_connected_components_kosaraju.py @@ -0,0 +1,81 @@ +""" +Implementing strongly connected components in a graph using Kosaraju's algorithm. +https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm +""" + + +class Kosaraju: + """ + Kosaraju's algorithm use depth first search approach to find strongly connected components in a directed graph. + Approach: + 1. Make a DFS call to keep track of finish time of each vertex. + 2. Tranpose the original graph. ie 1->2 transpose is 1<-2 + 3. Make another DFS call to calculate strongly connected components. + """ + + def dfs(self, i, V, adj, visited, stk): + visited[i] = 1 + + for x in adj[i]: + if visited[x] == -1: + self.dfs(x, V, adj, visited, stk) + + stk.append(i) + + def kosaraju(self, V, adj): + + stk, visited = [], [-1]*(V+1) + + for i in range(V): + if visited[i] == -1: + self.dfs(i, V, adj, visited, stk) + + stk.reverse() + res = stk.copy() + + ans, visited1 = 0, [-1]*(V+1) + + adj1 = [[] for x in range(V)] + + 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], V, adj1, visited1, stk) + + return ans + + +def main(): + """ + Let's look at the sample input. + + 6 7 #no of vertex, no of edges + 0 2 #directed edge 0->2 + 1 0 + 2 3 + 3 1 + 3 4 + 4 5 + 5 4 + + calculating no of strongly connected compnenets in a directed graph. + answer should be: 2 + 1st strong component: 0->2->3->1->0 + 2nd strongly connected component: 4->5->4 + """ + V, E = map(int, input().split()) + adj = [[] for x in range(V)] + + for i in range(E): + u, v = map(int, input().split()) + adj[u].append(v) + + print(Kosaraju().kosaraju(V, adj)) + + +if __name__ == '__main__': + main() diff --git a/algorithms/graph/tarjan.py b/algorithms/graph/tarjan.py new file mode 100644 index 000000000..ebc69326c --- /dev/null +++ b/algorithms/graph/tarjan.py @@ -0,0 +1,65 @@ +""" +Implements Tarjan's algorithm for finding strongly connected components +in a graph. +https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm +""" +from algorithms.graph.graph import DirectedGraph + + +# pylint: disable=too-few-public-methods +class Tarjan: + """ + A directed graph used for finding strongly connected components + """ + def __init__(self, dict_graph): + self.graph = DirectedGraph(dict_graph) + self.index = 0 + self.stack = [] + + # Runs Tarjan + # Set all node index to None + for vertex in self.graph.nodes: + vertex.index = None + + self.sccs = [] + for vertex in self.graph.nodes: + if vertex.index is None: + self.strongconnect(vertex, self.sccs) + + def strongconnect(self, vertex, sccs): + """ + Given a vertex, adds all successors of the given vertex to the same connected component + """ + # Set the depth index for v to the smallest unused index + vertex.index = self.index + vertex.lowlink = self.index + self.index += 1 + self.stack.append(vertex) + vertex.on_stack = True + + # Consider successors of v + for adjacent in self.graph.adjacency_list[vertex]: + if adjacent.index is None: + # Successor w has not yet been visited; recurse on it + self.strongconnect(adjacent, sccs) + vertex.lowlink = min(vertex.lowlink, adjacent.lowlink) + elif adjacent.on_stack: + # Successor w is in stack S and hence in the current SCC + # If w is not on stack, then (v, w) is a cross-edge in the DFS + # tree and must be ignored + # Note: The next line may look odd - but is correct. + # It says w.index not w.lowlink; that is deliberate and from the original paper + vertex.lowlink = min(vertex.lowlink, adjacent.index) + + # If v is a root node, pop the stack and generate an SCC + if vertex.lowlink == vertex.index: + # start a new strongly connected component + scc = [] + while True: + adjacent = self.stack.pop() + adjacent.on_stack = False + scc.append(adjacent) + if adjacent == vertex: + break + scc.sort() + sccs.append(scc) diff --git a/algorithms/graph/transitive_closure_dfs.py b/algorithms/graph/transitive_closure_dfs.py new file mode 100644 index 000000000..ca7e43a1b --- /dev/null +++ b/algorithms/graph/transitive_closure_dfs.py @@ -0,0 +1,55 @@ +""" +Finds the transitive closure of a graph. + +reference: https://en.wikipedia.org/wiki/Transitive_closure#In_graph_theory +""" + +class Graph: + """ + This class represents a directed graph using adjacency lists + """ + def __init__(self, vertices): + # No. of vertices + self.vertex_count = vertices + + # default dictionary to store graph + self.graph = {} + + # To store transitive closure + self.closure = [[0 for j in range(vertices)] for i in range(vertices)] + + def add_edge(self, source, target): + """ + Adds a directed edge to the graph + """ + if source in self.graph: + self.graph[source].append(target) + else: + self.graph[source] = [target] + + def dfs_util(self, source, target): + """ + A recursive DFS traversal function that finds + all reachable vertices for source + """ + + # Mark reachability from source to target as true. + self.closure[source][target] = 1 + + # Find all the vertices reachable through target + for adjacent in self.graph[target]: + if self.closure[source][adjacent] == 0: + self.dfs_util(source, adjacent) + + def transitive_closure(self): + """ + The function to find transitive closure. It uses + recursive dfs_util() + """ + + # Call the recursive helper function to print DFS + # traversal starting from all vertices one by one + for i in range(self.vertex_count): + self.dfs_util(i, i) + + return self.closure diff --git a/algorithms/graph/traversal.py b/algorithms/graph/traversal.py new file mode 100644 index 000000000..19ae14154 --- /dev/null +++ b/algorithms/graph/traversal.py @@ -0,0 +1,48 @@ +""" +Different ways to traverse a graph +""" + +# dfs and bfs are the ultimately same except that they are visiting nodes in +# different order. To simulate this ordering we would use stack for dfs and +# queue for bfs. +# + +def dfs_traverse(graph, start): + """ + Traversal by depth first search. + """ + visited, stack = set(), [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, start): + """ + Traversal by breadth first search. + """ + visited, queue = set(), [start] + while queue: + node = queue.pop(0) + 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, start, visited=None): + """ + Traversal by recursive depth first search. + """ + 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 diff --git a/algorithms/greedy/__init__.py b/algorithms/greedy/__init__.py new file mode 100644 index 000000000..66184782e --- /dev/null +++ b/algorithms/greedy/__init__.py @@ -0,0 +1 @@ +from .max_contiguous_subsequence_sum import * diff --git a/algorithms/greedy/max_contiguous_subsequence_sum.py b/algorithms/greedy/max_contiguous_subsequence_sum.py new file mode 100644 index 000000000..c5eae3d6e --- /dev/null +++ b/algorithms/greedy/max_contiguous_subsequence_sum.py @@ -0,0 +1,42 @@ +''' +Algorithm used => Kadane's Algorithm + +kadane's algorithm is used for finding the maximum sum of contiguous subsequence in a sequence. +It is considered a greedy/dp algorithm but I think they more greedy than dp +here are some of the examples to understand the use case more clearly +Example1 => [-2, 3, 8, -1, 4] +result => {3, 8, -1, 4} => 14 +Example2 => [-1, 1, 0] +result => {1} => 1 +Example3 => [-1, -3, -4] +result => -1 +Example1 => [-2, 3, 8, -12, 8, 4] +result => {8, 4} => 12 +Basic Algorithm Idea + If the sum of the current contiguous subsequence after adding the value at the current position is less than the value + at the current position then we know that it will be better if we start the current contiguous subsequence from this position. + Else we add the value at the current position to the current contiguous subsequence. +Note + In the implementation, the contiguous subsequence has at least one element. + If it can have 0 elements then the result will be max(max_till_now, 0) +''' + + +def max_contiguous_subsequence_sum(arr) -> int: + arr_size = len(arr) + + if arr_size == 0: + return 0 + + max_till_now = arr[0] + curr_sub_sum = 0 + + for i in range(0, arr_size): + if curr_sub_sum + arr[i] < arr[i]: + curr_sub_sum = arr[i] + else: + curr_sub_sum += arr[i] + + max_till_now = max(max_till_now, curr_sub_sum) + + return max_till_now diff --git a/algorithms/heap/__init__.py b/algorithms/heap/__init__.py new file mode 100644 index 000000000..9ea682d09 --- /dev/null +++ b/algorithms/heap/__init__.py @@ -0,0 +1,5 @@ +from .binary_heap import * +from .skyline import * +from .sliding_window_max import * +from .merge_sorted_k_lists import * +from .k_closest_points import * diff --git a/heap/binary_heap.py b/algorithms/heap/binary_heap.py similarity index 63% rename from heap/binary_heap.py rename to algorithms/heap/binary_heap.py index 5119a0309..776e315f6 100644 --- a/heap/binary_heap.py +++ b/algorithms/heap/binary_heap.py @@ -1,7 +1,7 @@ -""" -Binary Heap. A min heap is a complete binary tree where each node is smaller -its childen. The root, therefore, is the minimum element in the tree. The min -heap use array to represent the data and operation. For example a min heap: +r""" +Binary Heap. A min heap is a complete binary tree where each node is smaller than +its children. The root, therefore, is the minimum element in the tree. The min +heap uses an array to represent the data and operation. For example a min heap: 4 / \ @@ -31,73 +31,78 @@ """ from abc import ABCMeta, abstractmethod + class AbstractHeap(metaclass=ABCMeta): """Abstract Class for Binary Heap.""" + def __init__(self): - pass + """Pass.""" + @abstractmethod def perc_up(self, i): - pass + """Pass.""" + @abstractmethod def insert(self, val): - pass + """Pass.""" + @abstractmethod - def perc_down(self,i): - pass + def perc_down(self, i): + """Pass.""" + @abstractmethod - def min_child(self,i): - pass + def min_child(self, i): + """Pass.""" + @abstractmethod - def remove_min(self,i): - pass + def remove_min(self): + """Pass.""" + + class BinaryHeap(AbstractHeap): + """Binary Heap Class""" + def __init__(self): - self.currentSize = 0 + self.current_size = 0 self.heap = [(0)] def perc_up(self, i): while i // 2 > 0: if self.heap[i] < self.heap[i // 2]: # Swap value of child with value of its parent - tmp = self.heap[i] - self.heap[i] = self.heap[i // 2] - self.heap[i // 2] = tmp + self.heap[i], self.heap[i//2] = self.heap[i//2], self.heap[i] i = i // 2 - """ + def insert(self, val): + """ Method insert always start by inserting the element at the bottom. - it inserts rightmost spot so as to maintain the complete tree property - Then, it fix the tree by swapping the new element with its parent, + It inserts rightmost spot so as to maintain the complete tree property. + Then, it fixes the tree by swapping the new element with its parent, until it finds an appropriate spot for the element. It essentially perc_up the minimum element Complexity: O(logN) - """ - def insert(self, val): + """ self.heap.append(val) - self.currentSize = self.currentSize + 1 - self.perc_up(self.currentSize) + self.current_size = self.current_size + 1 + self.perc_up(self.current_size) + + """ + Method min_child returns the index of smaller of 2 children of parent at index i + """ - """ - Method min_child returns index of smaller 2 childs of its parent - """ def min_child(self, i): - if 2 * i + 1 > self.currentSize: # No right child + if 2 * i + 1 > self.current_size: # No right child return 2 * i - else: - # left child > right child - if self.heap[2 * i] > self.heap[2 * i +1]: - return 2 * i + 1 - else: - return 2 * i + if self.heap[2 * i] > self.heap[2 * i + 1]: + return 2 * i + 1 + return 2 * i def perc_down(self, i): - while 2 * i < self.currentSize: + while 2 * i < self.current_size: min_child = self.min_child(i) if self.heap[min_child] < self.heap[i]: # Swap min child with parent - tmp = self.heap[min_child] - self.heap[min_child] = self.heap[i] - self.heap[i] = tmp + self.heap[min_child], self.heap[i] = self.heap[i], self.heap[min_child] i = min_child """ Remove Min method removes the minimum element and swap it with the last @@ -106,10 +111,12 @@ def perc_down(self, i): min heap property is restored Complexity: O(logN) """ + def remove_min(self): ret = self.heap[1] # the smallest value at beginning - self.heap[1] = self.heap[self.currentSize] # Repalce it by the last value - self.currentSize = self.currentSize - 1 + # Replace it by the last value + self.heap[1] = self.heap[self.current_size] + self.current_size = self.current_size - 1 self.heap.pop() self.perc_down(1) return ret diff --git a/algorithms/heap/k_closest_points.py b/algorithms/heap/k_closest_points.py new file mode 100644 index 000000000..6a4e03604 --- /dev/null +++ b/algorithms/heap/k_closest_points.py @@ -0,0 +1,48 @@ +"""Given a list of points, find the k closest to the origin. + +Idea: Maintain a max heap of k elements. +We can iterate through all points. +If a point p has a smaller distance to the origin than the top element of a +heap, we add point p to the heap and remove the top element. +After iterating through all points, our heap contains the k closest points to +the origin. +""" + + +from heapq import heapify, heappushpop + + +def k_closest(points, k, origin=(0, 0)): + # Time: O(k+(n-k)logk) + # Space: O(k) + """Initialize max heap with first k points. + Python does not support a max heap; thus we can use the default min heap + where the keys (distance) are negated. + """ + heap = [(-distance(p, origin), p) for p in points[:k]] + heapify(heap) + + """ + For every point p in points[k:], + check if p is smaller than the root of the max heap; + if it is, add p to heap and remove root. Reheapify. + """ + for point in points[k:]: + dist = distance(point, origin) + + heappushpop(heap, (-dist, point)) # heappushpop does conditional check + """Same as: + if d < -heap[0][0]: + heappush(heap, (-d,p)) + heappop(heap) + + Note: heappushpop is more efficient than separate push and pop calls. + Each heappushpop call takes O(logk) time. + """ + + return [point for nd, point in heap] # return points in heap + + +def distance(point, origin=(0, 0)): + """ Calculates the distance for a point from origo""" + return (point[0] - origin[0])**2 + (point[1] - origin[1])**2 diff --git a/heap/merge_sorted_k_lists.py b/algorithms/heap/merge_sorted_k_lists.py similarity index 79% rename from heap/merge_sorted_k_lists.py rename to algorithms/heap/merge_sorted_k_lists.py index 2fbfe1df2..f3600c447 100644 --- a/heap/merge_sorted_k_lists.py +++ b/algorithms/heap/merge_sorted_k_lists.py @@ -9,28 +9,32 @@ # Definition for singly-linked list. class ListNode(object): - def __init__(self, x): - self.val = x + """ ListNode Class""" + + def __init__(self, val): + self.val = val self.next = None def merge_k_lists(lists): + """ Merge Lists """ dummy = node = ListNode(0) - h = [(n.val, n) for n in lists if n] - heapify(h) - while h: - v, n = h[0] - if n.next is None: - heappop(h) # only change heap size when necessary + list_h = [(n.val, n) for n in lists if n] + heapify(list_h) + while list_h: + _, n_val = list_h[0] + if n_val.next is None: + heappop(list_h) # only change heap size when necessary else: - heapreplace(h, (n.next.val, n.next)) - node.next = n + heapreplace(list_h, (n_val.next.val, n_val.next)) + node.next = n_val node = node.next return dummy.next def merge_k_lists(lists): + """ Merge List """ dummy = ListNode(None) curr = dummy q = PriorityQueue() diff --git a/heap/skyline.py b/algorithms/heap/skyline.py similarity index 100% rename from heap/skyline.py rename to algorithms/heap/skyline.py diff --git a/heap/sliding_window_max.py b/algorithms/heap/sliding_window_max.py similarity index 100% rename from heap/sliding_window_max.py rename to algorithms/heap/sliding_window_max.py diff --git a/algorithms/linkedlist/__init__.py b/algorithms/linkedlist/__init__.py new file mode 100644 index 000000000..1c9c3b9fc --- /dev/null +++ b/algorithms/linkedlist/__init__.py @@ -0,0 +1,9 @@ +from .reverse import * +from .is_sorted import * +from .remove_range import * +from .swap_in_pairs import * +from .rotate_list import * +from .is_cyclic import * +from .merge_two_list import * +from .is_palindrome import * +from .copy_random_pointer import * diff --git a/linkedlist/add_two_numbers.py b/algorithms/linkedlist/add_two_numbers.py similarity index 100% rename from linkedlist/add_two_numbers.py rename to algorithms/linkedlist/add_two_numbers.py diff --git a/algorithms/linkedlist/copy_random_pointer.py b/algorithms/linkedlist/copy_random_pointer.py new file mode 100644 index 000000000..b3ef4628d --- /dev/null +++ b/algorithms/linkedlist/copy_random_pointer.py @@ -0,0 +1,48 @@ +""" +A linked list is given such that each node contains an additional random +pointer which could point to any node in the list or null. + +Return a deep copy of the list. +""" +from collections import defaultdict + + +class RandomListNode(object): + def __init__(self, label): + self.label = label + self.next = None + self.random = None + + +def copy_random_pointer_v1(head): + """ + :type head: RandomListNode + :rtype: RandomListNode + """ + dic = dict() + m = n = head + while m: + dic[m] = RandomListNode(m.label) + m = m.next + while n: + dic[n].next = dic.get(n.next) + dic[n].random = dic.get(n.random) + n = n.next + return dic.get(head) + + +# O(n) +def copy_random_pointer_v2(head): + """ + :type head: RandomListNode + :rtype: RandomListNode + """ + copy = 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] diff --git a/linkedlist/delete_node.py b/algorithms/linkedlist/delete_node.py similarity index 100% rename from linkedlist/delete_node.py rename to algorithms/linkedlist/delete_node.py diff --git a/linkedlist/first_cyclic_node.py b/algorithms/linkedlist/first_cyclic_node.py similarity index 100% rename from linkedlist/first_cyclic_node.py rename to algorithms/linkedlist/first_cyclic_node.py diff --git a/linkedlist/intersection.py b/algorithms/linkedlist/intersection.py similarity index 100% rename from linkedlist/intersection.py rename to algorithms/linkedlist/intersection.py diff --git a/linkedlist/is_cyclic.py b/algorithms/linkedlist/is_cyclic.py similarity index 100% rename from linkedlist/is_cyclic.py rename to algorithms/linkedlist/is_cyclic.py diff --git a/linkedlist/is_palindrome.py b/algorithms/linkedlist/is_palindrome.py similarity index 100% rename from linkedlist/is_palindrome.py rename to algorithms/linkedlist/is_palindrome.py diff --git a/linkedlist/is_sorted.py b/algorithms/linkedlist/is_sorted.py similarity index 100% rename from linkedlist/is_sorted.py rename to algorithms/linkedlist/is_sorted.py diff --git a/linkedlist/kth_to_last.py b/algorithms/linkedlist/kth_to_last.py similarity index 100% rename from linkedlist/kth_to_last.py rename to algorithms/linkedlist/kth_to_last.py diff --git a/linkedlist/linkedlist.py b/algorithms/linkedlist/linkedlist.py similarity index 93% rename from linkedlist/linkedlist.py rename to algorithms/linkedlist/linkedlist.py index 1b921c926..c7b8e0c4a 100644 --- a/linkedlist/linkedlist.py +++ b/algorithms/linkedlist/linkedlist.py @@ -3,7 +3,7 @@ # in comparison, arrays require O(n) time to do the same thing. # Linked lists can continue to expand without having to specify # their size ahead of time (remember our lectures on Array sizing -# form the Array Sequence section of the course!) +# from the Array Sequence section of the course!) # Cons # To access an element in a linked list, you need to take O(k) time diff --git a/linkedlist/merge_two_list.py b/algorithms/linkedlist/merge_two_list.py similarity index 100% rename from linkedlist/merge_two_list.py rename to algorithms/linkedlist/merge_two_list.py diff --git a/linkedlist/partition.py b/algorithms/linkedlist/partition.py similarity index 100% rename from linkedlist/partition.py rename to algorithms/linkedlist/partition.py diff --git a/linkedlist/remove_duplicates.py b/algorithms/linkedlist/remove_duplicates.py similarity index 100% rename from linkedlist/remove_duplicates.py rename to algorithms/linkedlist/remove_duplicates.py diff --git a/linkedlist/remove_range.py b/algorithms/linkedlist/remove_range.py similarity index 100% rename from linkedlist/remove_range.py rename to algorithms/linkedlist/remove_range.py diff --git a/linkedlist/reverse.py b/algorithms/linkedlist/reverse.py similarity index 100% rename from linkedlist/reverse.py rename to algorithms/linkedlist/reverse.py diff --git a/linkedlist/rotate_list.py b/algorithms/linkedlist/rotate_list.py similarity index 100% rename from linkedlist/rotate_list.py rename to algorithms/linkedlist/rotate_list.py diff --git a/linkedlist/swap_in_pairs.py b/algorithms/linkedlist/swap_in_pairs.py similarity index 100% rename from linkedlist/swap_in_pairs.py rename to algorithms/linkedlist/swap_in_pairs.py diff --git a/algorithms/map/__init__.py b/algorithms/map/__init__.py new file mode 100644 index 000000000..931c0e776 --- /dev/null +++ b/algorithms/map/__init__.py @@ -0,0 +1,6 @@ +from .hashtable import * +from .separate_chaining_hashtable import * +from .word_pattern import * +from .is_isomorphic import * +from .is_anagram import * +from .longest_palindromic_subsequence import * diff --git a/map/hashtable.py b/algorithms/map/hashtable.py similarity index 59% rename from map/hashtable.py rename to algorithms/map/hashtable.py index 1c98ee22c..f579c0b0d 100644 --- a/map/hashtable.py +++ b/algorithms/map/hashtable.py @@ -1,6 +1,3 @@ -from unittest import TestCase -import unittest - class HashTable(object): """ HashMap Data Type @@ -120,93 +117,3 @@ def __resize(self): for key, value in zip(keys, values): if key is not self._empty and key is not self._deleted: self.put(key, value) - - -class TestHashTable(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)) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/algorithms/map/is_anagram.py b/algorithms/map/is_anagram.py new file mode 100644 index 000000000..066d8c20f --- /dev/null +++ b/algorithms/map/is_anagram.py @@ -0,0 +1,29 @@ +""" +Given two strings s and t , write a function to determine if t is an anagram of s. + +Example 1: +Input: s = "anagram", t = "nagaram" +Output: true + +Example 2: +Input: s = "rat", t = "car" +Output: false + +Note: +You may assume the string contains only lowercase alphabets. + +Reference: https://leetcode.com/problems/valid-anagram/description/ +""" +def is_anagram(s, t): + """ + :type s: str + :type t: str + :rtype: bool + """ + maps = {} + mapt = {} + for i in s: + maps[i] = maps.get(i, 0) + 1 + for i in t: + mapt[i] = mapt.get(i, 0) + 1 + return maps == mapt diff --git a/algorithms/map/is_isomorphic.py b/algorithms/map/is_isomorphic.py new file mode 100644 index 000000000..62342e5e4 --- /dev/null +++ b/algorithms/map/is_isomorphic.py @@ -0,0 +1,40 @@ +""" +Given two strings s and t, determine if they are isomorphic. +Two strings are isomorphic if the characters in s can be replaced to get t. +All occurrences of a character must be replaced with another character while +preserving the order of characters. No two characters may map to the same +character but a character may map to itself. + +Example 1: +Input: s = "egg", t = "add" +Output: true + +Example 2: +Input: s = "foo", t = "bar" +Output: false + +Example 3: +Input: s = "paper", t = "title" +Output: true +Reference: https://leetcode.com/problems/isomorphic-strings/description/ +""" +def is_isomorphic(s, t): + """ + :type s: str + :type t: str + :rtype: bool + """ + if len(s) != len(t): + return False + dict = {} + set_value = set() + for i in range(len(s)): + if s[i] not in dict: + if t[i] in set_value: + return False + dict[s[i]] = t[i] + set_value.add(t[i]) + else: + if dict[s[i]] != t[i]: + return False + return True diff --git a/map/longest_common_subsequence.py b/algorithms/map/longest_common_subsequence.py similarity index 100% rename from map/longest_common_subsequence.py rename to algorithms/map/longest_common_subsequence.py diff --git a/algorithms/map/longest_palindromic_subsequence.py b/algorithms/map/longest_palindromic_subsequence.py new file mode 100644 index 000000000..767e78f44 --- /dev/null +++ b/algorithms/map/longest_palindromic_subsequence.py @@ -0,0 +1,29 @@ +def longest_palindromic_subsequence(s): + + k = len(s) + olist = [0] * k # 申请长度为n的列表,并初始化 + nList = [0] * k # 同上 + logestSubStr = "" + logestLen = 0 + + for j in range(0, k): + for i in range(0, j + 1): + if j - i <= 1: + if s[i] == s[j]: + nList[i] = 1 # 当 j 时,第 i 个子串为回文子串 + len_t = j - i + 1 + if logestLen < len_t: # 判断长度 + logestSubStr = s[i:j + 1] + logestLen = len_t + else: + if s[i] == s[j] and olist[i+1]: # 当j-i>1时,判断s[i]是否等于s[j],并判断当j-1时,第i+1个子串是否为回文子串 + nList[i] = 1 # 当 j 时,第 i 个子串为回文子串 + len_t = j - i + 1 + if logestLen < len_t: + logestSubStr = s[i:j + 1] + logestLen = len_t + olist = nList # 覆盖旧的列表 + nList = [0] * k # 新的列表清空 + # ~ from icecream import ic + # ~ ic(s, logestSubStr) + return logestLen#, logestSubStr diff --git a/map/randomized_set.py b/algorithms/map/randomized_set.py similarity index 100% rename from map/randomized_set.py rename to algorithms/map/randomized_set.py diff --git a/map/separate_chaining_hashtable.py b/algorithms/map/separate_chaining_hashtable.py similarity index 58% rename from map/separate_chaining_hashtable.py rename to algorithms/map/separate_chaining_hashtable.py index 5feb50af8..fecb251fa 100644 --- a/map/separate_chaining_hashtable.py +++ b/algorithms/map/separate_chaining_hashtable.py @@ -82,62 +82,3 @@ def __delitem__(self, key): def __setitem__(self, key, value): self.put(key, value) - - -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)) - - -if __name__ == '__main__': - unittest.main() diff --git a/map/valid_sudoku.py b/algorithms/map/valid_sudoku.py similarity index 100% rename from map/valid_sudoku.py rename to algorithms/map/valid_sudoku.py diff --git a/algorithms/map/word_pattern.py b/algorithms/map/word_pattern.py new file mode 100644 index 000000000..bee2f7a91 --- /dev/null +++ b/algorithms/map/word_pattern.py @@ -0,0 +1,40 @@ +""" +Given a pattern and a string str, find if str follows the same pattern. +Here follow means a full match, such that there is a bijection between a +letter in pattern and a non-empty word in str. + +Example 1: +Input: pattern = "abba", str = "dog cat cat dog" +Output: true + +Example 2: +Input:pattern = "abba", str = "dog cat cat fish" +Output: false + +Example 3: +Input: pattern = "aaaa", str = "dog cat cat dog" +Output: false + +Example 4: +Input: pattern = "abba", str = "dog dog dog dog" +Output: false +Notes: +You may assume pattern contains only lowercase letters, and str contains lowercase letters separated by a single space. +Reference: https://leetcode.com/problems/word-pattern/description/ +""" +def word_pattern(pattern, str): + dict = {} + set_value = set() + list_str = str.split() + if len(list_str) != len(pattern): + return False + for i in range(len(pattern)): + if pattern[i] not in dict: + if list_str[i] in set_value: + return False + dict[pattern[i]] = list_str[i] + set_value.add(list_str[i]) + else: + if dict[pattern[i]] != list_str[i]: + return False + return True diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py new file mode 100644 index 000000000..d5499b094 --- /dev/null +++ b/algorithms/maths/__init__.py @@ -0,0 +1,28 @@ +""" +Collection of mathematical algorithms and functions. +""" +from .base_conversion import * +from .decimal_to_binary_ip import * +from .euler_totient import * +from .extended_gcd import * +from .factorial import * +from .gcd import * +from .generate_strobogrammtic import * +from .is_strobogrammatic import * +from .modular_exponential import * +from .next_perfect_square import * +from .prime_check import * +from .primes_sieve_of_eratosthenes import * +from .pythagoras import * +from .rabin_miller import * +from .rsa import * +from .combination import * +from .cosine_similarity import * +from .find_order_simple import * +from .find_primitive_root_simple import * +from .diffie_hellman_key_exchange import * +from .num_digits import * +from .power import * +from .magic_number import * +from .krishnamurthy_number import * +from .num_perfect_squares import * diff --git a/algorithms/maths/base_conversion.py b/algorithms/maths/base_conversion.py new file mode 100644 index 000000000..6badf749e --- /dev/null +++ b/algorithms/maths/base_conversion.py @@ -0,0 +1,49 @@ +""" +Integer base conversion algorithm + +int_to_base(5, 2) return '101'. +base_to_int('F', 16) return 15. + +""" + +import string + +def int_to_base(num, base): + """ + :type num: int + :type base: int + :rtype: str + """ + 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, base): + """ + Note : You can use int() built-in function instead of this. + :type str_to_convert: str + :type base: int + :rtype: int + """ + + 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 diff --git a/algorithms/maths/chinese_remainder_theorem.py b/algorithms/maths/chinese_remainder_theorem.py new file mode 100644 index 000000000..256e60f16 --- /dev/null +++ b/algorithms/maths/chinese_remainder_theorem.py @@ -0,0 +1,46 @@ +""" +Solves system of equations using the chinese remainder theorem if possible. +""" +from typing import List +from algorithms.maths.gcd import gcd + +def solve_chinese_remainder(nums : List[int], rems : List[int]): + """ + Computes the smallest x that satisfies the chinese remainder theorem + for a system of equations. + The system of equations has the form: + x % nums[0] = rems[0] + x % nums[1] = rems[1] + ... + x % nums[k - 1] = rems[k - 1] + Where k is the number of elements in nums and rems, k > 0. + All numbers in nums needs to be pariwise coprime otherwise an exception is raised + returns x: the smallest value for x that satisfies the system of equations + """ + 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]): + 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 diff --git a/algorithms/maths/combination.py b/algorithms/maths/combination.py new file mode 100644 index 000000000..998536056 --- /dev/null +++ b/algorithms/maths/combination.py @@ -0,0 +1,19 @@ +""" +Functions to calculate nCr (ie how many ways to choose r items from n items) +""" +def combination(n, r): + """This function calculates nCr.""" + if n == r or r == 0: + return 1 + return combination(n-1, r-1) + combination(n-1, r) + +def combination_memo(n, r): + """This function calculates nCr using memoization method.""" + memo = {} + def recur(n, r): + 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) diff --git a/algorithms/maths/cosine_similarity.py b/algorithms/maths/cosine_similarity.py new file mode 100644 index 000000000..3ee7fcdd8 --- /dev/null +++ b/algorithms/maths/cosine_similarity.py @@ -0,0 +1,43 @@ +""" +Calculate cosine similarity between given two 1d list. +Two list must have the same length. + +Example: +cosine_similarity([1, 1, 1], [1, 2, -1]) # output : 0.47140452079103173 +""" +import math + + +def _l2_distance(vec): + """ + Calculate l2 distance from two given vectors. + """ + norm = 0. + for element in vec: + norm += element * element + norm = math.sqrt(norm) + return norm + + +def cosine_similarity(vec1, vec2): + """ + Calculate cosine similarity between given two vectors + :type vec1: list + :type vec2: list + """ + 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. + + # Calculate the dot product of two vectors + for vec1_element, vec2_element in zip(vec1, vec2): + similarity += vec1_element * vec2_element + + similarity /= (norm_a * norm_b) + + return similarity diff --git a/algorithms/maths/decimal_to_binary_ip.py b/algorithms/maths/decimal_to_binary_ip.py new file mode 100644 index 000000000..04e65e83c --- /dev/null +++ b/algorithms/maths/decimal_to_binary_ip.py @@ -0,0 +1,35 @@ +""" +Given an ip address in dotted-decimal representation, determine the +binary representation. For example, +decimal_to_binary(255.0.0.5) returns 11111111.00000000.00000000.00000101 +accepts string +returns string +""" + +def decimal_to_binary_util(val): + """ + Convert 8-bit decimal number to binary representation + :type val: str + :rtype: str + """ + bits = [128, 64, 32, 16, 8, 4, 2, 1] + val = int(val) + binary_rep = '' + for bit in bits: + if val >= bit: + binary_rep += str(1) + val -= bit + else: + binary_rep += str(0) + + return binary_rep + +def decimal_to_binary_ip(ip): + """ + Convert dotted-decimal ip address to binary representation with help of decimal_to_binary_util + """ + values = ip.split('.') + binary_list = [] + for val in values: + binary_list.append(decimal_to_binary_util(val)) + return '.'.join(binary_list) diff --git a/algorithms/maths/diffie_hellman_key_exchange.py b/algorithms/maths/diffie_hellman_key_exchange.py new file mode 100644 index 000000000..b70b000af --- /dev/null +++ b/algorithms/maths/diffie_hellman_key_exchange.py @@ -0,0 +1,184 @@ +""" +Algorithms for performing diffie-hellman key exchange. +""" +import math +from random import randint + + +""" +Code from /algorithms/maths/prime_check.py, +written by 'goswami-rahul' and 'Hai Honag Dang' +""" +def prime_check(num): + """Return True if num is a prime number + Else return False. + """ + + 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 + + +""" +For positive integer n and given integer a that satisfies gcd(a, n) = 1, +the order of a modulo n is the smallest positive integer k that satisfies +pow (a, k) % n = 1. In other words, (a^k) ≡ 1 (mod n). +Order of certain number may or may not exist. If not, return -1. +""" +def find_order(a, n): + if (a == 1) & (n == 1): + # Exception Handeling : 1 is the order of of 1 + return 1 + if math.gcd(a, n) != 1: + print ("a and n should be relative prime!") + return -1 + for i in range(1, n): + if pow(a, i) % n == 1: + return i + return -1 + + +""" +Euler's totient function, also known as phi-function ϕ(n), +counts the number of integers between 1 and n inclusive, +which are coprime to n. +(Two numbers are coprime if their greatest common divisor (GCD) equals 1). +Code from /algorithms/maths/euler_totient.py, written by 'goswami-rahul' +""" +def euler_totient(n): + """Euler's totient function or Phi function. + Time Complexity: O(sqrt(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 + +""" +For positive integer n and given integer a that satisfies gcd(a, n) = 1, +a is the primitive root of n, if a's order k for n satisfies k = ϕ(n). +Primitive roots of certain number may or may not be exist. +If so, return empty list. +""" + +def find_primitive_root(n): + """ Returns all primitive roots of n. """ + if n == 1: + # Exception Handeling : 0 is the only primitive root of 1 + return [0] + phi = euler_totient(n) + p_root_list = [] + for i in range (1, n): + if math.gcd(i, n) != 1: + # To have order, a and n must be relative prime with each other. + continue + order = find_order(i, n) + if order == phi: + p_root_list.append(i) + return p_root_list + + +""" +Diffie-Hellman key exchange is the method that enables +two entities (in here, Alice and Bob), not knowing each other, +to share common secret key through not-encrypted communication network. +This method use the property of one-way function (discrete logarithm) +For example, given a, b and n, it is easy to calculate x +that satisfies (a^b) ≡ x (mod n). +However, it is very hard to calculate x that satisfies (a^x) ≡ b (mod n). +For using this method, large prime number p and its primitive root a +must be given. +""" + +def alice_private_key(p): + """Alice determine her private key + in the range of 1 ~ p-1. + This must be kept in secret""" + return randint(1, p-1) + + +def alice_public_key(a_pr_k, a, p): + """Alice calculate her public key + with her private key. + This is open to public""" + return pow(a, a_pr_k) % p + + +def bob_private_key(p): + """Bob determine his private key + in the range of 1 ~ p-1. + This must be kept in secret""" + return randint(1, p-1) + + +def bob_public_key(b_pr_k, a, p): + """Bob calculate his public key + with his private key. + This is open to public""" + return pow(a, b_pr_k) % p + + +def alice_shared_key(b_pu_k, a_pr_k, p): + """ Alice calculate secret key shared with Bob, + with her private key and Bob's public key. + This must be kept in secret""" + return pow(b_pu_k, a_pr_k) % p + + +def bob_shared_key(a_pu_k, b_pr_k, p): + """ Bob calculate secret key shared with Alice, + with his private key and Alice's public key. + This must be kept in secret""" + return pow(a_pu_k, b_pr_k) % p + + +def diffie_hellman_key_exchange(a, p, option = None): + """ Perform diffie-helmman key exchange. """ + if option is not None: + # Print explanation of process when option parameter is given + option = 1 + if prime_check(p) is False: + print(f"{p} is not a prime number") + # p must be large prime number + return False + try: + p_root_list = find_primitive_root(p) + p_root_list.index(a) + except ValueError: + print(f"{a} is not a primitive root of {p}") + # a must be primitive root of p + 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) + + if option == 1: + print(f"Alice's private key: {a_pr_k}") + print(f"Alice's public key: {a_pu_k}") + print(f"Bob's private key: {b_pr_k}") + print(f"Bob's public key: {b_pu_k}") + + # In here, Alice send her public key to Bob, and Bob also send his public key to Alice. + + 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) + print (f"Shared key calculated by Alice = {a_sh_k}") + print (f"Shared key calculated by Bob = {b_sh_k}") + + return a_sh_k == b_sh_k diff --git a/algorithms/maths/euler_totient.py b/algorithms/maths/euler_totient.py new file mode 100644 index 000000000..f29d382ff --- /dev/null +++ b/algorithms/maths/euler_totient.py @@ -0,0 +1,18 @@ +""" +Euler's totient function, also known as phi-function ϕ(n), +counts the number of integers between 1 and n inclusive, +which are coprime to n. +(Two numbers are coprime if their greatest common divisor (GCD) equals 1). +""" +def euler_totient(n): + """Euler's totient function or Phi function. + Time Complexity: O(sqrt(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 diff --git a/algorithms/maths/extended_gcd.py b/algorithms/maths/extended_gcd.py new file mode 100644 index 000000000..a0e9cbc12 --- /dev/null +++ b/algorithms/maths/extended_gcd.py @@ -0,0 +1,24 @@ +""" +Provides extended GCD functionality for finding co-prime numbers s and t such that: +num1 * s + num2 * t = GCD(num1, num2). +Ie the coefficients of Bézout's identity. +""" +def extended_gcd(num1, num2): + """Extended GCD algorithm. + Return s, t, g + such that num1 * s + num2 * t = GCD(num1, num2) + and s and t are co-prime. + """ + + 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 diff --git a/algorithms/maths/factorial.py b/algorithms/maths/factorial.py new file mode 100644 index 000000000..f5e3ea5a2 --- /dev/null +++ b/algorithms/maths/factorial.py @@ -0,0 +1,35 @@ +""" +Calculates the factorial with the added functionality of calculating it modulo mod. +""" +def factorial(n, mod=None): + """Calculates factorial iteratively. + If mod is not None, then return (n! % mod) + Time Complexity - O(n)""" + 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, mod=None): + """Calculates factorial recursively. + If mod is not None, then return (n! % mod) + Time Complexity - O(n)""" + 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 diff --git a/algorithms/maths/fft.py b/algorithms/maths/fft.py new file mode 100644 index 000000000..6f6697421 --- /dev/null +++ b/algorithms/maths/fft.py @@ -0,0 +1,32 @@ +""" +Implementation of the Cooley-Tukey, which is the most common FFT algorithm. + +Input: an array of complex values which has a size of N, where N is an integer power of 2 +Output: an array of complex values which is the discrete fourier transform of the input + +Example 1 +Input: [2.0+2j, 1.0+3j, 3.0+1j, 2.0+2j] +Output: [8+8j, 2j, 2-2j, -2+0j] + + +Pseudocode: https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm +""" +from cmath import exp, pi + +def fft(x): + """ Recursive implementation of the Cooley-Tukey""" + N = len(x) + if N == 1: + return x + + # get the elements at even/odd indices + even = fft(x[0::2]) + odd = fft(x[1::2]) + + y = [0 for i 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 diff --git a/algorithms/maths/find_order_simple.py b/algorithms/maths/find_order_simple.py new file mode 100644 index 000000000..8f69773c7 --- /dev/null +++ b/algorithms/maths/find_order_simple.py @@ -0,0 +1,27 @@ +""" +For positive integer n and given integer a that satisfies gcd(a, n) = 1, +the order of a modulo n is the smallest positive integer k that satisfies +pow (a, k) % n = 1. In other words, (a^k) ≡ 1 (mod n). +Order of a certain number may or may not be exist. If not, return -1. + +Total time complexity O(nlog(n)): +O(n) for iteration loop, +O(log(n)) for built-in power function +""" + +import math + +def find_order(a, n): + """ + Find order for positive integer n and given integer a that satisfies gcd(a, n) = 1. + """ + if (a == 1) & (n == 1): + # Exception Handeling : 1 is the order of of 1 + return 1 + if math.gcd(a, n) != 1: + print ("a and n should be relative prime!") + return -1 + for i in range(1, n): + if pow(a, i) % n == 1: + return i + return -1 diff --git a/algorithms/maths/find_primitive_root_simple.py b/algorithms/maths/find_primitive_root_simple.py new file mode 100644 index 000000000..366f40191 --- /dev/null +++ b/algorithms/maths/find_primitive_root_simple.py @@ -0,0 +1,67 @@ +""" +Function to find the primitive root of a number. +""" +import math + +""" +For positive integer n and given integer a that satisfies gcd(a, n) = 1, +the order of a modulo n is the smallest positive integer k that satisfies +pow (a, k) % n = 1. In other words, (a^k) ≡ 1 (mod n). +Order of certain number may or may not be exist. If so, return -1. +""" +def find_order(a, n): + """ + Find order for positive integer n and given integer a that satisfies gcd(a, n) = 1. + Time complexity O(nlog(n)) + """ + if (a == 1) & (n == 1): + # Exception Handeling : 1 is the order of of 1 + return 1 + if math.gcd(a, n) != 1: + print ("a and n should be relative prime!") + return -1 + for i in range(1, n): + if pow(a, i) % n == 1: + return i + return -1 + +""" +Euler's totient function, also known as phi-function ϕ(n), +counts the number of integers between 1 and n inclusive, +which are coprime to n. +(Two numbers are coprime if their greatest common divisor (GCD) equals 1). +Code from /algorithms/maths/euler_totient.py, written by 'goswami-rahul' +""" +def euler_totient(n): + """Euler's totient function or Phi function. + Time Complexity: O(sqrt(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 + +""" +For positive integer n and given integer a that satisfies gcd(a, n) = 1, +a is the primitive root of n, if a's order k for n satisfies k = ϕ(n). +Primitive roots of certain number may or may not exist. +If so, return empty list. +""" +def find_primitive_root(n): + if n == 1: + # Exception Handeling : 0 is the only primitive root of 1 + return [0] + phi = euler_totient(n) + p_root_list = [] + """ It will return every primitive roots of n. """ + for i in range (1, n): + #To have order, a and n must be relative prime with each other. + if math.gcd(i, n) == 1: + order = find_order(i, n) + if order == phi: + p_root_list.append(i) + return p_root_list diff --git a/algorithms/maths/gcd.py b/algorithms/maths/gcd.py new file mode 100644 index 000000000..189eae100 --- /dev/null +++ b/algorithms/maths/gcd.py @@ -0,0 +1,63 @@ +""" +Functions for calculating the greatest common divisor of two integers or +their least common multiple. +""" + +def gcd(a, b): + """Computes the greatest common divisor of integers a and b using + Euclid's Algorithm. + gcd{𝑎,𝑏}=gcd{−𝑎,𝑏}=gcd{𝑎,−𝑏}=gcd{−𝑎,−𝑏} + See proof: https://proofwiki.org/wiki/GCD_for_Negative_Integers + """ + 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, b): + """Computes the lowest common multiple of integers a and b.""" + return abs(a) * abs(b) / gcd(a, b) + +""" +Given a positive integer x, computes the number of trailing zero of x. +Example +Input : 34(100010) + ~~~~~^ +Output : 1 + +Input : 40(101000) + ~~~^^^ +Output : 3 +""" +def trailing_zero(x): + count = 0 + while x and not x & 1: + count += 1 + x >>= 1 + return count + +""" +Given two non-negative integer a and b, +computes the greatest common divisor of a and b using bitwise operator. +""" +def gcd_bit(a, b): + """ Similar to gcd but uses bitwise operators and less error handling.""" + 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) diff --git a/maths/generate_strobogrammtic.py b/algorithms/maths/generate_strobogrammtic.py similarity index 91% rename from maths/generate_strobogrammtic.py rename to algorithms/maths/generate_strobogrammtic.py index dd0c400c4..d051865fb 100644 --- a/maths/generate_strobogrammtic.py +++ b/algorithms/maths/generate_strobogrammtic.py @@ -8,15 +8,14 @@ Given n = 2, return ["11","69","88","96"]. """ - def gen_strobogrammatic(n): """ + Given n, generate all strobogrammatic numbers of length n. :type n: int :rtype: List[str] """ return helper(n, n) - def helper(n, length): if n == 0: return [""] @@ -33,7 +32,6 @@ def helper(n, length): result.append("6" + middle + "9") return result - def strobogrammatic_in_range(low, high): """ :type low: str @@ -49,13 +47,11 @@ def strobogrammatic_in_range(low, high): for perm in res: if len(perm) == low_len and int(perm) < int(low): continue - elif len(perm) == high_len and int(perm) > int(high): + if len(perm) == high_len and int(perm) > int(high): continue - else: - count += 1 + count += 1 return count - def helper2(n, length): if n == 0: return [""] diff --git a/algorithms/maths/hailstone.py b/algorithms/maths/hailstone.py new file mode 100644 index 000000000..77261ac5e --- /dev/null +++ b/algorithms/maths/hailstone.py @@ -0,0 +1,21 @@ +""" +Implementation of hailstone function which generates a sequence for some n by following these rules: +* n == 1 : done +* n is even : the next n = n/2 +* n is odd : the next n = 3n + 1 +""" + +def hailstone(n): + """ + Return the 'hailstone sequence' from n to 1 + n: The starting point of the hailstone sequence + """ + + sequence = [n] + while n > 1: + if n%2 != 0: + n = 3*n + 1 + else: + n = int(n/2) + sequence.append(n) + return sequence diff --git a/maths/is_strobogrammatic.py b/algorithms/maths/is_strobogrammatic.py similarity index 92% rename from maths/is_strobogrammatic.py rename to algorithms/maths/is_strobogrammatic.py index 018e6955d..b849ddac4 100644 --- a/maths/is_strobogrammatic.py +++ b/algorithms/maths/is_strobogrammatic.py @@ -18,8 +18,7 @@ def is_strobogrammatic(num): i = 0 j = len(num) - 1 while i <= j: - x = comb.find(num[i]+num[j]) - if x == -1: + if comb.find(num[i]+num[j]) == -1: return False i += 1 j -= 1 diff --git a/algorithms/maths/krishnamurthy_number.py b/algorithms/maths/krishnamurthy_number.py new file mode 100644 index 000000000..1d41721b1 --- /dev/null +++ b/algorithms/maths/krishnamurthy_number.py @@ -0,0 +1,46 @@ +""" +A Krishnamurthy number is a number whose sum total of the factorials of each digit is equal to the +number itself. + +The following are some examples of Krishnamurthy numbers: + +"145" is a Krishnamurthy Number because, +1! + 4! + 5! = 1 + 24 + 120 = 145 + +"40585" is also a Krishnamurthy Number. +4! + 0! + 5! + 8! + 5! = 40585 + +"357" or "25965" is NOT a Krishnamurthy Number +3! + 5! + 7! = 6 + 120 + 5040 != 357 + +The following function will check if a number is a Krishnamurthy Number or not and return a +boolean value. +""" + + +def find_factorial(n): + """ Calculates the factorial of a given number n """ + fact = 1 + while n != 0: + fact *= n + n -= 1 + return fact + + +def krishnamurthy_number(n): + if n == 0: + return False + sum_of_digits = 0 # will hold sum of FACTORIAL of digits + temp = n + + while temp != 0: + + # get the factorial of of the last digit of n and add it to sum_of_digits + sum_of_digits += find_factorial(temp % 10) + + # replace value of temp by temp/10 + # i.e. will remove the last digit from temp + temp //= 10 + + # returns True if number is krishnamurthy + return sum_of_digits == n diff --git a/algorithms/maths/magic_number.py b/algorithms/maths/magic_number.py new file mode 100644 index 000000000..6107753d2 --- /dev/null +++ b/algorithms/maths/magic_number.py @@ -0,0 +1,33 @@ +""" +Magic Number +A number is said to be a magic number, +if summing the digits of the number and then recursively repeating this process for the given sum +untill the number becomes a single digit number equal to 1. + +Example: + Number = 50113 => 5+0+1+1+3=10 => 1+0=1 [This is a Magic Number] + Number = 1234 => 1+2+3+4=10 => 1+0=1 [This is a Magic Number] + Number = 199 => 1+9+9=19 => 1+9=10 => 1+0=1 [This is a Magic Number] + Number = 111 => 1+1+1=3 [This is NOT a Magic Number] + +The following function checks for Magic numbers and returns a Boolean accordingly. +""" + +def magic_number(n): + """ Checks if n is a magic number """ + total_sum = 0 + + # will end when n becomes 0 + # AND + # sum becomes single digit. + while n > 0 or total_sum > 9: + # when n becomes 0 but we have a total_sum, + # we update the value of n with the value of the sum digits + if n == 0: + n = total_sum # only when sum of digits isn't single digit + total_sum = 0 + total_sum += n % 10 + n //= 10 + + # Return true if sum becomes 1 + return total_sum == 1 diff --git a/algorithms/maths/modular_exponential.py b/algorithms/maths/modular_exponential.py new file mode 100644 index 000000000..f0e58de8f --- /dev/null +++ b/algorithms/maths/modular_exponential.py @@ -0,0 +1,18 @@ +def modular_exponential(base, exponent, mod): + """Computes (base ^ exponent) % mod. + Time complexity - O(log n) + Use similar to Python in-built function pow.""" + if exponent < 0: + raise ValueError("Exponent must be positive.") + base %= mod + result = 1 + + while exponent > 0: + # If the last bit is 1, add 2^k. + if exponent & 1: + result = (result * base) % mod + exponent = exponent >> 1 + # Utilize modular multiplication properties to combine the computed mod C values. + base = (base * base) % mod + + return result diff --git a/algorithms/maths/modular_inverse.py b/algorithms/maths/modular_inverse.py new file mode 100644 index 000000000..c6f849d7d --- /dev/null +++ b/algorithms/maths/modular_inverse.py @@ -0,0 +1,34 @@ +# extended_gcd(a, b) modified from +# https://github.com/keon/algorithms/blob/master/algorithms/maths/extended_gcd.py + +def extended_gcd(a: int, b: int) -> [int, int, int]: + """Extended GCD algorithm. + Return s, t, g + such that a * s + b * t = GCD(a, b) + and s and t are co-prime. + """ + + 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: + """ + Returns x such that a * x = 1 (mod m) + a and m must be coprime + """ + + s, _, g = extended_gcd(a, m) + if g != 1: + raise ValueError("a and m must be coprime") + return s % m diff --git a/maths/next_bigger.py b/algorithms/maths/next_bigger.py similarity index 88% rename from maths/next_bigger.py rename to algorithms/maths/next_bigger.py index e14e22cb8..a159da197 100644 --- a/maths/next_bigger.py +++ b/algorithms/maths/next_bigger.py @@ -1,9 +1,9 @@ """ -I just bombed an interview and made pretty much zero -progress on my interview question. +I just bombed an interview and made pretty much zero +progress on my interview question. -Given a number, find the next higher number which has the -exact same set of digits as the original number. +Given a number, find the next higher number which has the +exact same set of digits as the original number. For example: given 38276 return 38627. given 99999 return -1. (no such number exists) diff --git a/maths/next_perfect_square.py b/algorithms/maths/next_perfect_square.py similarity index 61% rename from maths/next_perfect_square.py rename to algorithms/maths/next_perfect_square.py index 7e6e6b918..be2d24a24 100644 --- a/maths/next_perfect_square.py +++ b/algorithms/maths/next_perfect_square.py @@ -1,6 +1,7 @@ """ This program will look for the next perfect square. -Check the argument to see if it is a perfect square itself, if it is not then return -1 otherwise look for the next perfect square +Check the argument to see if it is a perfect square itself, if it is not then return -1 otherwise +look for the next perfect square. for instance if you pass 121 then the script should return the next perfect square which is 144. """ @@ -9,11 +10,8 @@ def find_next_square(sq): if root.is_integer(): return (root + 1)**2 return -1 - - -# Another way: def find_next_square2(sq): - x = sq**0.5 - return -1 if x % 1 else (x+1)**2 - + """ Alternative method, works by evaluating anything non-zero as True (0.000001 --> True) """ + root = sq**0.5 + return -1 if root % 1 else (root+1)**2 diff --git a/algorithms/maths/nth_digit.py b/algorithms/maths/nth_digit.py new file mode 100644 index 000000000..f6454e940 --- /dev/null +++ b/algorithms/maths/nth_digit.py @@ -0,0 +1,17 @@ +def find_nth_digit(n): + """find the nth digit of given number. + 1. find the length of the number where the nth digit is from. + 2. find the actual number where the nth digit is from + 3. find the nth digit and return + """ + 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]) diff --git a/algorithms/maths/num_digits.py b/algorithms/maths/num_digits.py new file mode 100644 index 000000000..4ecd5c7c3 --- /dev/null +++ b/algorithms/maths/num_digits.py @@ -0,0 +1,12 @@ +""" +num_digits() method will return the number of digits of a number in O(1) time using +math.log10() method. +""" + +import math + +def num_digits(n): + n=abs(n) + if n==0: + return 1 + return int(math.log10(n))+1 diff --git a/algorithms/maths/num_perfect_squares.py b/algorithms/maths/num_perfect_squares.py new file mode 100644 index 000000000..e02eeb768 --- /dev/null +++ b/algorithms/maths/num_perfect_squares.py @@ -0,0 +1,47 @@ +""" +Given an integer num_perfect_squares will return the minimum amount of perfect squares are required +to sum to the specified number. Lagrange's four-square theorem gives us that the answer will always +be between 1 and 4 (https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem). + +Some examples: +Number | Perfect Squares representation | Answer +-------|--------------------------------|-------- +9 | 3^2 | 1 +10 | 3^2 + 1^2 | 2 +12 | 2^2 + 2^2 + 2^2 | 3 +31 | 5^2 + 2^2 + 1^2 + 1^2 | 4 +""" + +import math + +def num_perfect_squares(number): + """ + Returns the smallest number of perfect squares that sum to the specified number. + :return: int between 1 - 4 + """ + # If the number is a perfect square then we only need 1 number. + if int(math.sqrt(number))**2 == number: + return 1 + + # We check if https://en.wikipedia.org/wiki/Legendre%27s_three-square_theorem holds and divide + # the number accordingly. Ie. if the number can be written as a sum of 3 squares (where the + # 0^2 is allowed), which is possible for all numbers except those of the form: 4^a(8b + 7). + while number > 0 and number % 4 == 0: + number /= 4 + + # If the number is of the form: 4^a(8b + 7) it can't be expressed as a sum of three (or less + # excluding the 0^2) perfect squares. If the number was of that form, the previous while loop + # divided away the 4^a, so by now it would be of the form: 8b + 7. So check if this is the case + # and return 4 since it neccessarily must be a sum of 4 perfect squares, in accordance + # with https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem. + if number % 8 == 7: + return 4 + + # By now we know that the number wasn't of the form 4^a(8b + 7) so it can be expressed as a sum + # of 3 or less perfect squares. Try first to express it as a sum of 2 perfect squares, and if + # that fails, we know finally that it can be expressed as a sum of 3 perfect squares. + 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 diff --git a/algorithms/maths/polynomial.py b/algorithms/maths/polynomial.py new file mode 100644 index 000000000..81faf2b7e --- /dev/null +++ b/algorithms/maths/polynomial.py @@ -0,0 +1,575 @@ +# from __future__ import annotations + +from fractions import Fraction +from typing import Dict, Union, Set, Iterable +from numbers import Rational +from functools import reduce + + +class Monomial: + """ + A simple Monomial class to + record the details of all variables + that a typical monomial is composed of. + """ + def __init__(self, variables: Dict[int, int], coeff: Union[int, float, Fraction, None]= None) -> None: + ''' + Create a monomial in the given variables: + Examples: + + Monomial({1:1}) = (a_1)^1 + + Monomial({ + 1:3, + 2:2, + 4:1, + 5:0 + }, 12) = 12(a_1)^3(a_2)^2(a_4) + + Monomial({}) = 0 + + Monomial({2:3, 3:-1}, 1.5) = (3/2)(a_2)^3(a_3)^(-1) + + ''' + self.variables = dict() + + if coeff is None: + if len(variables) == 0: + coeff = Fraction(0, 1) + else: + coeff = 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): + ''' + A helper for converting numbers + to Fraction only when possible. + ''' + if isinstance(num, Rational): + res = Fraction(num, 1) + return Fraction(res.numerator, res.denominator) + else: + return num + + # def equal_upto_scalar(self, other: Monomial) -> bool: + def equal_upto_scalar(self, other) -> bool: + """ + Return True if other is a monomial + and is equivalent to self up to a scalar + multiple. + """ + if not isinstance(other, Monomial): + raise ValueError('Can only compare monomials.') + return other.variables == self.variables + + # def __add__(self, other: Union[int, float, Fraction, Monomial]): + def __add__(self, other: Union[int, float, Fraction]): + """ + Define the addition of two + monomials or the addition of + a monomial with an int, float, or a Fraction. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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() + + # If they don't share same variables then by the definition, + # if they are added, the result becomes a polynomial and not a monomial. + # Thus, raise ValueError in that case. + + raise ValueError(f'Cannot add {str(other)} to {self.__str__()} because they don\'t have same variables.') + + # def __eq__(self, other: Monomial) -> bool: + def __eq__(self, other) -> bool: + """ + Return True if two monomials + are equal upto a scalar multiple. + """ + return self.equal_upto_scalar(other) and self.coeff == other.coeff + + # def __mul__(self, other: Union[int, float, Fraction, Monomial]) -> Monomial: + def __mul__(self, other: Union[int, float, Fraction]): + """ + Multiply two monomials and merge the variables + in both of them. + + Examples: + + Monomial({1:1}) * Monomial({1: -3, 2: 1}) = (a_1)^(-2)(a_2) + Monomial({3:2}) * 2.5 = (5/2)(a_3)^2 + + """ + if isinstance(other, float) or isinstance(other, int) or isinstance(other, 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: + def inverse(self): + """ + Compute the inverse of a monomial. + + Examples: + + Monomial({1:1, 2:-1, 3:2}, 2.5).inverse() = Monomial({1:-1, 2:1, 3:-2} ,2/5) + + + """ + 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: Union[int, float, Fraction, Monomial]) -> Monomial: + def __truediv__(self, other: Union[int, float, Fraction]): + """ + Compute the division between two monomials + or a monomial and some other datatype + like int/float/Fraction. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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: Union[int, float, Fraction, Monomial]) -> Monomial: + def __floordiv__(self, other: Union[int, float, Fraction]): + """ + For monomials, + floor div is the same as true div. + """ + return self.__truediv__(other) + + # def clone(self) -> Monomial: + def clone(self): + """ + Clone the monomial. + """ + 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: + def clean(self): + """ + Clean the monomial by dropping any variables that have power 0. + """ + 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: Union[int, float, Fraction, Monomial]) -> Monomial: + def __sub__(self, other: Union[int, float, Fraction]): + """ + Compute the subtraction + of a monomial and a datatype + such as int, float, Fraction, or Monomial. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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: + """ + Define the hash of a monomial + by the underlying variables. + + If hashing is implemented in O(v*log(v)) + where v represents the number of + variables in the monomial, + then search queries for the + purposes of simplification of a + polynomial can be performed in + O(v*log(v)) as well; much better than + the length of the polynomial. + """ + 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 variables + present in the monomial. + """ + return set(sorted(self.variables.keys())) + + def substitute(self, substitutions: Union[int, float, Fraction, Dict[int, Union[int, float, Fraction]]]) -> Fraction: + """ + Substitute the variables in the + monomial for values defined by + the substitutions dictionary. + """ + if isinstance(substitutions, int) or isinstance(substitutions, float) or isinstance(substitutions, 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. + """ + if len(self.variables) == 0: + return str(self.coeff) + + result = str(self.coeff) + result += '(' + for i in self.variables: + temp = 'a_{}'.format(str(i)) + if self.variables[i] > 1: + temp = '(' + temp + ')**{}'.format(self.variables[i]) + elif self.variables[i] < 0: + temp = '(' + temp + ')**(-{})'.format(-self.variables[i]) + elif self.variables[i] == 0: + continue + else: + temp = '(' + temp + ')' + result += temp + return result + ')' + + +class Polynomial: + """ + A simple implementation + of a polynomial class that + records the details about two polynomials + that are potentially comprised of multiple + variables. + """ + def __init__(self, monomials: Iterable[Union[int, float, Fraction, Monomial]]) -> None: + ''' + Create a polynomial in the given variables: + Examples: + + Polynomial([ + Monomial({1:1}, 2), + Monomial({2:3, 1:-1}, -1), + math.pi, + Fraction(-1, 2) + ]) = (a_1)^2 + (-1)(a_2)^3(a_1)^(-1) + 2.6415926536 + + Polynomial([]) = 0 + + ''' + self.monomials = 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): + ''' + A helper for converting numbers + to Fraction only when possible. + ''' + if isinstance(num, Rational): + res = Fraction(num, 1) + return Fraction(res.numerator, res.denominator) + else: + return num + + + # def __add__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __add__(self, other: Union[int, float, Fraction, Monomial]): + """ + Add a given poylnomial to a copy of self. + + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __sub__(self, other: Union[int, float, Fraction, Monomial]): + """ + Subtract the given polynomial + from a copy of self. + + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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.') + return + + # def __mul__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __mul__(self, other: Union[int, float, Fraction, Monomial]): + """ + Multiply a given polynomial + to a copy of self. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + 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, 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: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __floordiv__(self, other: Union[int, float, Fraction, Monomial]): + """ + For Polynomials, floordiv is the same + as truediv. + """ + return self.__truediv__(other) + + # def __truediv__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __truediv__(self, other: Union[int, float, Fraction, Monomial]): + """ + For Polynomial division, no remainder is provided. Must use poly_long_division() to capture remainder + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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): + # Call long division + quotient, remainder = self.poly_long_division(other) + return quotient # Return just the quotient, remainder is ignored here + + raise ValueError('Can only divide a polynomial by an int, float, Fraction, Monomial, or Polynomial.') + + return + + # def clone(self) -> Polynomial: + def clone(self): + """ + Clone the polynomial. + """ + return Polynomial(list({m.clone() for m in self.all_monomials()})) + + def variables(self) -> Set: + """ + Get all the variables present + in this polynomials. + """ + res = set() + for i in self.all_monomials(): + res |= {j for j in i.variables} + res = list(res) + # res.sort() + return set(res) + + def all_monomials(self) -> Iterable[Monomial]: + """ + Get the monomials of this polynomial. + """ + return {m for m in self.monomials if m != Monomial({}, 0)} + + + def __eq__(self, other) -> bool: + """ + Return True if the other polynomial is the same as + this. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, 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: Union[int, float, Fraction, Dict[int, Union[int, float, Fraction]]]) -> Union[int, float, Fraction]: + """ + Get the value after substituting + certain values for the variables + defined in substitutions. + """ + if isinstance(substitutions, int) or isinstance(substitutions, float) or isinstance(substitutions, 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 properly formatted string representation of the polynomial. + """ + 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 + Returns (quotient, remainder) + """ + 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]) # Convert Monomial to Polynomial + + remainder = remainder - ( + Polynomial([lead_quotient]) * other) # Convert Monomial to Polynomial before multiplication + + return quotient, remainder + +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 = Polynomial([ + Monomial({1: 1}, 2), # 2(a_1) + Monomial({}, -1) # -1 +]) + +quotient = dividend / divisor +print("Quotient:", quotient) diff --git a/algorithms/maths/power.py b/algorithms/maths/power.py new file mode 100644 index 000000000..70d8587de --- /dev/null +++ b/algorithms/maths/power.py @@ -0,0 +1,48 @@ +""" +Performs exponentiation, similarly to the built-in pow() or ** functions. +Allows also for calculating the exponentiation modulo. +""" +def power(a: int, n: int, mod: int = None): + """ + Iterative version of binary exponentiation + + Calculate a ^ n + if mod is specified, return the result modulo mod + + Time Complexity : O(log(n)) + Space Complexity : O(1) + """ + 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): + """ + Recursive version of binary exponentiation + + Calculate a ^ n + if mod is specified, return the result modulo mod + + Time Complexity : O(log(n)) + Space Complexity : O(log(n)) + """ + 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 diff --git a/algorithms/maths/prime_check.py b/algorithms/maths/prime_check.py new file mode 100644 index 000000000..60e4427ab --- /dev/null +++ b/algorithms/maths/prime_check.py @@ -0,0 +1,17 @@ +def prime_check(n): + """Return True if n is a prime number + Else return 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 diff --git a/maths/primes_sieve_of_eratosthenes.py b/algorithms/maths/primes_sieve_of_eratosthenes.py similarity index 61% rename from maths/primes_sieve_of_eratosthenes.py rename to algorithms/maths/primes_sieve_of_eratosthenes.py index 1677b2575..b0d1d96c5 100644 --- a/maths/primes_sieve_of_eratosthenes.py +++ b/algorithms/maths/primes_sieve_of_eratosthenes.py @@ -1,11 +1,12 @@ -''' -Using sieve of Eratosthenes, primes(x) returns list of all primes less than x +""" +Return list of all primes less than n, +Using sieve of Eratosthenes. Modification: We don't need to check all even numbers, we can make the sieve excluding even numbers and adding 2 to the primes list by default. -We are going to make an array of: x / 2 - 1 if number is even, else x / 2 +We are going to make an array of: x / 2 - 1 if number is even, else x / 2 (The -1 with even number it's to exclude the number itself) Because we just need numbers [from 3..x if x is odd] @@ -21,20 +22,24 @@ With this, we have reduced the array size to a half, and complexity it's also a half now. -''' +""" -def primes(x): - assert(x >= 0) +def get_primes(n): + """Return list of all primes less than n, + Using sieve of Eratosthenes. + """ + if n <= 0: + raise ValueError("'n' must be a positive integer.") # If x is even, exclude x from list (-1): - sieve_size = (x//2 - 1) if x % 2 == 0 else (x//2) - sieve = [1 for v in range(sieve_size)] # Sieve - primes = [] # List of Primes - if x >= 2: - primes.append(2) # Add 2 by default + sieve_size = (n // 2 - 1) if n % 2 == 0 else (n // 2) + sieve = [True for _ in range(sieve_size)] # Sieve + primes = [] # List of Primes + if n >= 2: + primes.append(2) # 2 is prime by default for i in range(sieve_size): - if sieve[i] == 1: + 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] = 0 + sieve[j] = False return primes diff --git a/algorithms/maths/pythagoras.py b/algorithms/maths/pythagoras.py new file mode 100644 index 000000000..b24b682ac --- /dev/null +++ b/algorithms/maths/pythagoras.py @@ -0,0 +1,20 @@ +""" +Given the lengths of two of the three sides of a right angled triangle, this function returns the +length of the third side. +""" + +def pythagoras(opposite, adjacent, hypotenuse): + """ + Returns length of a third side of a right angled triangle. + Passing "?" will indicate the unknown side. + """ + try: + if opposite == str("?"): + return ("Opposite = " + str(((hypotenuse**2) - (adjacent**2))**0.5)) + if adjacent == str("?"): + return ("Adjacent = " + str(((hypotenuse**2) - (opposite**2))**0.5)) + if hypotenuse == str("?"): + return ("Hypotenuse = " + str(((opposite**2) + (adjacent**2))**0.5)) + return "You already know the answer!" + except: + raise ValueError("invalid argument(s) were given.") diff --git a/maths/rabin_miller.py b/algorithms/maths/rabin_miller.py similarity index 99% rename from maths/rabin_miller.py rename to algorithms/maths/rabin_miller.py index a3aad8ef2..08b12c117 100644 --- a/maths/rabin_miller.py +++ b/algorithms/maths/rabin_miller.py @@ -47,5 +47,5 @@ def valid_witness(a): for _ in range(k): if valid_witness(random.randrange(2, n - 2)): return False - + return True diff --git a/algorithms/maths/recursive_binomial_coefficient.py b/algorithms/maths/recursive_binomial_coefficient.py new file mode 100644 index 000000000..a92420050 --- /dev/null +++ b/algorithms/maths/recursive_binomial_coefficient.py @@ -0,0 +1,26 @@ +def recursive_binomial_coefficient(n,k): + """Calculates the binomial coefficient, C(n,k), with n>=k using recursion + Time complexity is O(k), so can calculate fairly quickly for large values of k. + + >>> recursive_binomial_coefficient(5,0) + 1 + + >>> recursive_binomial_coefficient(8,2) + 28 + + >>> recursive_binomial_coefficient(500,300) + 5054949849935535817667719165973249533761635252733275327088189563256013971725761702359997954491403585396607971745777019273390505201262259748208640 + + """ + + if k>n: + raise ValueError('Invalid Inputs, ensure that n >= k') + #function is only defined for n>=k + if k == 0 or n == k: + #C(n,0) = C(n,n) = 1, so this is our base case. + return 1 + if k > n/2: + #C(n,k) = C(n,n-k), so if n/2 is sufficiently small, we can reduce the problem size. + return recursive_binomial_coefficient(n,n-k) + #else, we know C(n,k) = (n/k)C(n-1,k-1), so we can use this to reduce our problem size. + return int((n/k)*recursive_binomial_coefficient(n-1,k-1)) diff --git a/maths/rsa.py b/algorithms/maths/rsa.py similarity index 91% rename from maths/rsa.py rename to algorithms/maths/rsa.py index e3f29956c..bbf193caa 100644 --- a/maths/rsa.py +++ b/algorithms/maths/rsa.py @@ -21,16 +21,14 @@ (a ** b) % c == pow(a,b,c) """ -import random - +# sample usage: +# n,e,d = generate_key(16) +# data = 20 +# encrypted = pow(data,e,n) +# decrypted = pow(encrypted,d,n) +# assert decrypted == data -def modinv(a, m): - """calculate the inverse of a mod m - that is, find b such that (a * b) % m == 1""" - b = 1 - while not (a * b) % m == 1: - b += 1 - return b +import random def generate_key(k, seed=None): @@ -39,6 +37,14 @@ def generate_key(k, seed=None): k is the number of bits in n """ + def modinv(a, m): + """calculate the inverse of a mod m + that is, find b such that (a * b) % m == 1""" + b = 1 + while not (a * b) % m == 1: + b += 1 + return b + def gen_prime(k, seed=None): """generate a prime with k bits""" @@ -59,23 +65,23 @@ def is_prime(num): # size in bits of p and q need to add up to the size of n p_size = k / 2 q_size = k - p_size - + e = gen_prime(k, seed) # in many cases, e is also chosen to be a small constant - + 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 l = (p - 1) * (q - 1) # calculate totient function d = modinv(e, l) - + return int(n), int(e), int(d) @@ -85,13 +91,3 @@ def encrypt(data, e, n): def decrypt(data, d, n): return pow(int(data), int(d), int(n)) - - - -# sample usage: -# n,e,d = generate_key(16) -# data = 20 -# encrypted = pow(data,e,n) -# decrypted = pow(encrypted,d,n) -# assert decrypted == data - diff --git a/algorithms/maths/sqrt_precision_factor.py b/algorithms/maths/sqrt_precision_factor.py new file mode 100644 index 000000000..bb47c0945 --- /dev/null +++ b/algorithms/maths/sqrt_precision_factor.py @@ -0,0 +1,19 @@ +""" +Given a positive integer N and a precision factor P, +it produces an output +with a maximum error P from the actual square root of N. + +Example: +Given N = 5 and P = 0.001, can produce output x such that +2.235 < x < 2.237. Actual square root of 5 being 2.236. +""" + + +def square_root(n, epsilon=0.001): + """Return square root of n, with maximum absolute error epsilon""" + guess = n / 2 + + while abs(guess * guess - n) > epsilon: + guess = (guess + (n / guess)) / 2 + + return guess diff --git a/maths/summing_digits.py b/algorithms/maths/summing_digits.py similarity index 66% rename from maths/summing_digits.py rename to algorithms/maths/summing_digits.py index 3f4e21a32..ec30ffda8 100644 --- a/maths/summing_digits.py +++ b/algorithms/maths/summing_digits.py @@ -1,29 +1,30 @@ """ Recently, I encountered an interview question whose description was as below: -The number 89 is the first integer with more than one digit whose digits when raised up to consecutive powers give the same -number. For example, 89 = 8**1 + 9**2 gives the number 89. +The number 89 is the first integer with more than one digit whose digits when raised up to +consecutive powers give the same number. For example, 89 = 8**1 + 9**2 gives the number 89. The next number after 89 with this property is 135 = 1**1 + 3**2 + 5**3 = 135. -Write a function that returns a list of numbers with the above property. The function will receive range as parameter. +Write a function that returns a list of numbers with the above property. The function will +receive range as parameter. """ -def sum_dig_pow(a, b): +def sum_dig_pow(low, high): result = [] - - for number in range(a, b + 1): + + for number in range(low, high + 1): exponent = 1 # set to 1 - sum = 0 # set to 1 + summation = 0 # set to 1 number_as_string = str(number) tokens = list(map(int, number_as_string)) # parse the string into individual digits for k in tokens: - sum = sum + (k ** exponent) + summation = summation + (k ** exponent) exponent += 1 - if sum == number: + if summation == number: result.append(number) return result diff --git a/algorithms/maths/symmetry_group_cycle_index.py b/algorithms/maths/symmetry_group_cycle_index.py new file mode 100644 index 000000000..01b3e05ee --- /dev/null +++ b/algorithms/maths/symmetry_group_cycle_index.py @@ -0,0 +1,159 @@ +""" +The significance of the cycle index (polynomial) of symmetry group +is deeply rooted in counting the number of configurations +of an object excluding those that are symmetric (in terms of permutations). + +For example, the following problem can be solved as a direct +application of the cycle index polynomial of the symmetry +group. + +Note: I came across this problem as a Google's foo.bar challenge at Level 5 +and solved it using a purely Group Theoretic approach. :) +----- + +Problem: + +Given positive integers +w, h, and s, +compute the number of distinct 2D +grids of dimensions w x h that contain +entries from {0, 1, ..., s-1}. +Note that two grids are defined +to be equivalent if one can be +obtained from the other by +switching rows and columns +some number of times. + +----- + +Approach: + +Compute the cycle index (polynomials) +of S_w, and S_h, i.e. the Symmetry +group on w and h symbols respectively. + +Compute the product of the two +cycle indices while combining two +monomials in such a way that +for any pair of cycles c1, and c2 +in the elements of S_w X S_h, +the resultant monomial contains +terms of the form: +$$ x_{lcm(|c1|, |c2|)}^{gcd(|c1|, |c2|)} $$ + +Return the specialization of +the product of cycle indices +at x_i = s (for all the valid i). + +----- + +Code: + +def solve(w, h, s): + s1 = get_cycle_index_sym(w) + s2 = get_cycle_index_sym(h) + + result = cycle_product_for_two_polynomials(s1, s2, s) + + return str(result) +""" + +from fractions import Fraction +from typing import Dict, Union +from polynomial import ( Monomial, Polynomial ) +from gcd import lcm + + +def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: + """ + Given two monomials (from the + cycle index of a symmetry group), + compute the resultant monomial + in the cartesian product + corresponding to their merging. + """ + assert isinstance(m1, Monomial) and isinstance(m2, Monomial) + A = m1.variables + B = m2.variables + result_variables = dict() + for i in A: + for j in B: + k = lcm(i, j) + g = (i * j) // k + if k in result_variables: + result_variables[k] += A[i] * B[j] * g + else: + result_variables[k] = A[i] * B[j] * g + + return Monomial(result_variables, Fraction(m1.coeff * m2.coeff, 1)) + + +def cycle_product_for_two_polynomials(p1: Polynomial, p2: Polynomial, q: Union[float, int, Fraction]) -> Union[float, int, Fraction]: + """ + Compute the product of + given cycle indices p1, + and p2 and evaluate it at q. + """ + 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: + """ + A helper for the dp-style evaluation + of the cycle index. + + The recurrence is given in: + https://en.wikipedia.org/wiki/Cycle_index#Symmetric_group_Sn + + """ + 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 S_n, i.e. the symmetry + group of n symbols. + + """ + 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 diff --git a/heap/__init__.py b/algorithms/matrix/__init__.py similarity index 100% rename from heap/__init__.py rename to algorithms/matrix/__init__.py diff --git a/matrix/bomb_enemy.py b/algorithms/matrix/bomb_enemy.py similarity index 86% rename from matrix/bomb_enemy.py rename to algorithms/matrix/bomb_enemy.py index debcf5d81..c8412635d 100644 --- a/matrix/bomb_enemy.py +++ b/algorithms/matrix/bomb_enemy.py @@ -17,8 +17,10 @@ return 3. (Placing a bomb at (1,1) kills 3 enemies) """ + def max_killed_enemies(grid): - if not grid: return 0 + if not grid: + return 0 m, n = len(grid), len(grid[0]) max_killed = 0 row_e, col_e = 0, [0] * n @@ -38,6 +40,7 @@ def max_killed_enemies(grid): return max_killed + # calculate killed enemies for row i from column j def row_kills(grid, i, j): num = 0 @@ -48,6 +51,7 @@ def row_kills(grid, i, j): j += 1 return num + # calculate killed enemies for column j from row i def col_kills(grid, i, j): num = 0 @@ -57,7 +61,6 @@ def col_kills(grid, i, j): num += 1 i += 1 return num - # ----------------- TESTS ------------------------- @@ -66,14 +69,16 @@ def col_kills(grid, i, j): Testsuite for the project """ -import unittest +import unittest + class TestBombEnemy(unittest.TestCase): def test_3x4(self): - grid1 = [["0","E","0","0"], - ["E","0","W","E"], - ["0","E","0","0"]] - self.assertEqual(3,max_killed_enemies(grid1)) + grid1 = [["0", "E", "0", "0"], + ["E", "0", "W", "E"], + ["0", "E", "0", "0"]] + self.assertEqual(3, max_killed_enemies(grid1)) + def test_4x4(self): grid1 = [ ["0", "E", "0", "E"], @@ -85,9 +90,9 @@ def test_4x4(self): ["E", "0", "0", "0"], ["E", "0", "W", "E"], ["0", "E", "0", "0"]] - self.assertEqual(5,max_killed_enemies(grid1)) - self.assertEqual(3,max_killed_enemies(grid2)) + self.assertEqual(5, max_killed_enemies(grid1)) + self.assertEqual(3, max_killed_enemies(grid2)) + if __name__ == "__main__": unittest.main() - diff --git a/algorithms/matrix/cholesky_matrix_decomposition.py b/algorithms/matrix/cholesky_matrix_decomposition.py new file mode 100644 index 000000000..28ef5ea76 --- /dev/null +++ b/algorithms/matrix/cholesky_matrix_decomposition.py @@ -0,0 +1,51 @@ +""" +Cholesky matrix decomposition is used to find the decomposition of a +Hermitian positive-definite matrix A +into matrix V, so that V * V* = A, where V* denotes the conjugate +transpose of L. +The dimensions of the matrix A must match. + +This method is mainly used for numeric solution of linear equations Ax = b. + +example: +Input matrix A: +[[ 4, 12, -16], + [ 12, 37, -43], + [-16, -43, 98]] + +Result: +[[2.0, 0.0, 0.0], +[6.0, 1.0, 0.0], +[-8.0, 5.0, 3.0]] + +Time complexity of this algorithm is O(n^3), specifically about (n^3)/3 + +""" +import math + + +def cholesky_decomposition(A): + """ + :param A: Hermitian positive-definite matrix of type List[List[float]] + :return: matrix of type List[List[float]] if A can be decomposed, + otherwise None + """ + n = len(A) + for ai in A: + if len(ai) != n: + return None + V = [[0.0] * n for _ in range(n)] + for j in range(n): + sum_diagonal_element = 0 + for k in range(j): + sum_diagonal_element = sum_diagonal_element + math.pow(V[j][k], 2) + sum_diagonal_element = A[j][j] - sum_diagonal_element + if sum_diagonal_element <= 0: + return None + V[j][j] = math.pow(sum_diagonal_element, 0.5) + for i in range(j+1, n): + sum_other_element = 0 + for k in range(j): + sum_other_element += V[i][k]*V[j][k] + V[i][j] = (A[i][j] - sum_other_element)/V[j][j] + return V diff --git a/matrix/copy_transform.py b/algorithms/matrix/copy_transform.py similarity index 99% rename from matrix/copy_transform.py rename to algorithms/matrix/copy_transform.py index 772a81c9c..99bfa462d 100644 --- a/matrix/copy_transform.py +++ b/algorithms/matrix/copy_transform.py @@ -9,6 +9,7 @@ def rotate_clockwise(matrix): new[i].append(elem) return new + def rotate_counterclockwise(matrix): new = [] for row in matrix: @@ -20,6 +21,7 @@ def rotate_counterclockwise(matrix): new[i].append(elem) return new + def top_left_invert(matrix): new = [] for row in matrix: @@ -31,6 +33,7 @@ def top_left_invert(matrix): new[i].append(elem) return new + def bottom_left_invert(matrix): new = [] for row in reversed(matrix): @@ -42,6 +45,7 @@ def bottom_left_invert(matrix): new[i].append(elem) return new + if __name__ == '__main__': def print_matrix(matrix, name): print('{}:\n['.format(name)) diff --git a/matrix/count_paths.py b/algorithms/matrix/count_paths.py similarity index 100% rename from matrix/count_paths.py rename to algorithms/matrix/count_paths.py diff --git a/algorithms/matrix/crout_matrix_decomposition.py b/algorithms/matrix/crout_matrix_decomposition.py new file mode 100644 index 000000000..bfb859bb3 --- /dev/null +++ b/algorithms/matrix/crout_matrix_decomposition.py @@ -0,0 +1,48 @@ +""" +Crout matrix decomposition is used to find two matrices that, when multiplied +give our input matrix, so L * U = A. +L stands for lower and L has non-zero elements only on diagonal and below. +U stands for upper and U has non-zero elements only on diagonal and above. + +This can for example be used to solve systems of linear equations. +The last if is used if to avoid dividing by zero. + +Example: +We input the A matrix: +[[1,2,3], +[3,4,5], +[6,7,8]] + +We get: +L = [1.0, 0.0, 0.0] + [3.0, -2.0, 0.0] + [6.0, -5.0, 0.0] +U = [1.0, 2.0, 3.0] + [0.0, 1.0, 2.0] + [0.0, 0.0, 1.0] + +We can check that L * U = A. + +I think the complexity should be O(n^3). +""" + + +def crout_matrix_decomposition(A): + n = len(A) + L = [[0.0] * n for i in range(n)] + U = [[0.0] * n for i in range(n)] + for j in range(n): + U[j][j] = 1.0 + for i in range(j, n): + alpha = float(A[i][j]) + for k in range(j): + alpha -= L[i][k]*U[k][j] + L[i][j] = float(alpha) + for i in range(j+1, n): + tempU = float(A[j][i]) + for k in range(j): + tempU -= float(L[j][k]*U[k][i]) + if int(L[j][j]) == 0: + L[j][j] = float(0.1**40) + U[j][i] = float(tempU/L[j][j]) + return (L, U) diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py new file mode 100644 index 000000000..d9b45ba21 --- /dev/null +++ b/algorithms/matrix/matrix_exponentiation.py @@ -0,0 +1,43 @@ +def multiply(matA: list, matB: list) -> list: + """ + Multiplies two square matrices matA and matB of size n x n + Time Complexity: O(n^3) + """ + n = len(matA) + matC = [[0 for i in range(n)] for j in range(n)] + + for i in range(n): + for j in range(n): + for k in range(n): + matC[i][j] += matA[i][k] * matB[k][j] + + return matC + + +def identity(n: int) -> list: + """ + Returns the Identity matrix of size n x n + Time Complexity: O(n^2) + """ + I = [[0 for i in range(n)] for j in range(n)] + + for i in range(n): + I[i][i] = 1 + + return I + + +def matrix_exponentiation(mat: list, n: int) -> list: + """ + Calculates mat^n by repeated squaring + Time Complexity: O(d^3 log(n)) + d: dimension of the square matrix mat + n: power the matrix is raised to + """ + if n == 0: + return identity(len(mat)) + elif n % 2 == 1: + return multiply(matrix_exponentiation(mat, n - 1), mat) + else: + tmp = matrix_exponentiation(mat, n // 2) + return multiply(tmp, tmp) diff --git a/algorithms/matrix/matrix_inversion.py b/algorithms/matrix/matrix_inversion.py new file mode 100644 index 000000000..8d8102d41 --- /dev/null +++ b/algorithms/matrix/matrix_inversion.py @@ -0,0 +1,124 @@ +""" +Inverts an invertible n x n matrix -- i.e., given an n x n matrix A, returns +an n x n matrix B such that AB = BA = In, the n x n identity matrix. + +For a 2 x 2 matrix, inversion is simple using the cofactor equation. For +larger matrices, this is a four step process: +1. calculate the matrix of minors: create an n x n matrix by considering each +position in the original matrix in turn. Exclude the current row and column +and calculate the determinant of the remaining matrix, then place that value +in the current position's equivalent in the matrix of minors. +2. create the matrix of cofactors: take the matrix of minors and multiply +alternate values by -1 in a checkerboard pattern. +3. adjugate: hold the top left to bottom right diagonal constant, but swap all +other values over it. +4. multiply the adjugated matrix by 1 / the determinant of the original matrix + +This code combines steps 1 and 2 into one method to reduce traversals of the +matrix. + +Possible edge cases: will not work for 0x0 or 1x1 matrix, though these are +trivial to calculate without use of this file. +""" +import fractions + + +def invert_matrix(m): + """invert an n x n matrix""" + # Error conditions + if not array_is_matrix(m): + print("Invalid matrix: array is not a matrix") + return [[-1]] + elif len(m) != len(m[0]): + print("Invalid matrix: matrix is not square") + return [[-2]] + elif len(m) < 2: + print("Invalid matrix: matrix is too small") + return [[-3]] + elif get_determinant(m) == 0: + print("Invalid matrix: matrix is square, but singular (determinant = 0)") + return [[-4]] + + # Calculation + elif len(m) == 2: + # simple case + multiplier = 1 / get_determinant(m) + inverted = [[multiplier] * len(m) for n in range(len(m))] + inverted[0][1] = inverted[0][1] * -1 * m[0][1] + inverted[1][0] = inverted[1][0] * -1 * m[1][0] + inverted[0][0] = multiplier * m[1][1] + inverted[1][1] = multiplier * m[0][0] + return inverted + else: + """some steps combined in helpers to reduce traversals""" + # get matrix of minors w/ "checkerboard" signs + m_of_minors = get_matrix_of_minors(m) + + # calculate determinant (we need to know 1/det) + multiplier = fractions.Fraction(1, get_determinant(m)) + + # adjugate (swap on diagonals) and multiply by 1/det + inverted = transpose_and_multiply(m_of_minors, multiplier) + + return inverted + + +def get_determinant(m): + """recursively calculate the determinant of an n x n matrix, n >= 2""" + if len(m) == 2: + # trivial case + return (m[0][0] * m[1][1]) - (m[0][1] * m[1][0]) + else: + sign = 1 + det = 0 + for i in range(len(m)): + det += sign * m[0][i] * get_determinant(get_minor(m, 0, i)) + sign *= -1 + return det + + +def get_matrix_of_minors(m): + """get the matrix of minors and alternate signs""" + matrix_of_minors = [[0 for i in range(len(m))] for j in range(len(m))] + for row in range(len(m)): + for col in range(len(m[0])): + if (row + col) % 2 == 0: + sign = 1 + else: + sign = -1 + matrix_of_minors[row][col] = sign * get_determinant(get_minor(m, row, col)) + return matrix_of_minors + + +def get_minor(m, row, col): + """ + get the minor of the matrix position m[row][col] + (all values m[r][c] where r != row and c != col) + """ + minors = [] + for i in range(len(m)): + if i != row: + new_row = m[i][:col] + new_row.extend(m[i][col + 1:]) + minors.append(new_row) + return minors + + +def transpose_and_multiply(m, multiplier=1): + """swap values along diagonal, optionally adding multiplier""" + for row in range(len(m)): + for col in range(row + 1): + temp = m[row][col] * multiplier + m[row][col] = m[col][row] * multiplier + m[col][row] = temp + return m + + +def array_is_matrix(m): + if len(m) == 0: + return False + first_col = len(m[0]) + for row in m: + if len(row) != first_col: + return False + return True diff --git a/algorithms/matrix/multiply.py b/algorithms/matrix/multiply.py new file mode 100644 index 000000000..2c1cba8f2 --- /dev/null +++ b/algorithms/matrix/multiply.py @@ -0,0 +1,28 @@ +""" +This algorithm takes two compatible two dimensional matrix +and return their product +Space complexity: O(n^2) +Possible edge case: the number of columns of multiplicand not consistent with +the number of rows of multiplier, will raise exception +""" + + +def multiply(multiplicand: list, multiplier: list) -> list: + """ + :type A: List[List[int]] + :type B: List[List[int]] + :rtype: List[List[int]] + """ + multiplicand_row, multiplicand_col = len( + multiplicand), len(multiplicand[0]) + multiplier_row, multiplier_col = len(multiplier), len(multiplier[0]) + if(multiplicand_col != multiplier_row): + raise Exception( + "Multiplicand matrix not compatible with Multiplier matrix.") + # create a result matrix + result = [[0] * multiplier_col for i in range(multiplicand_row)] + for i in range(multiplicand_row): + for j in range(multiplier_col): + for k in range(len(multiplier)): + result[i][j] += multiplicand[i][k] * multiplier[k][j] + return result diff --git a/matrix/rotate_image.py b/algorithms/matrix/rotate_image.py similarity index 84% rename from matrix/rotate_image.py rename to algorithms/matrix/rotate_image.py index 0a6fb50b8..a8b5e4e77 100644 --- a/matrix/rotate_image.py +++ b/algorithms/matrix/rotate_image.py @@ -14,19 +14,20 @@ # 4 5 6 => 4 5 6 => 8 5 2 # 7 8 9 1 2 3 9 6 3 -def rotate(mat:"List[List[int]]"): +def rotate(mat): 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 if __name__ == "__main__": - mat = [[1,2,3], - [4,5,6], - [7,8,9]] + mat = [[1, 2, 3], + [4, 5, 6], + [7, 8, 9]] print(mat) rotate(mat) print(mat) diff --git a/matrix/search_in_sorted_matrix.py b/algorithms/matrix/search_in_sorted_matrix.py similarity index 83% rename from matrix/search_in_sorted_matrix.py rename to algorithms/matrix/search_in_sorted_matrix.py index ce3711200..54502c33a 100644 --- a/matrix/search_in_sorted_matrix.py +++ b/algorithms/matrix/search_in_sorted_matrix.py @@ -10,13 +10,13 @@ def search_in_a_sorted_matrix(mat, m, n, key): i, j = m-1, 0 while i >= 0 and j < n: if key == mat[i][j]: - print ('Key %s found at row- %s column- %s' % (key, i+1, j+1)) + print('Key %s found at row- %s column- %s' % (key, i+1, j+1)) return if key < mat[i][j]: i -= 1 else: j += 1 - print ('Key %s not found' % (key)) + print('Key %s not found' % (key)) def main(): @@ -27,7 +27,7 @@ def main(): [12, 17, 20] ] key = 13 - print (mat) + print(mat) search_in_a_sorted_matrix(mat, len(mat), len(mat[0]), key) diff --git a/algorithms/matrix/sort_matrix_diagonally.py b/algorithms/matrix/sort_matrix_diagonally.py new file mode 100644 index 000000000..b32c71044 --- /dev/null +++ b/algorithms/matrix/sort_matrix_diagonally.py @@ -0,0 +1,77 @@ +""" +Given a m * n matrix mat of integers, +sort it diagonally in ascending order +from the top-left to the bottom-right +then return the sorted array. + +mat = [ + [3,3,1,1], + [2,2,1,2], + [1,1,1,2] +] + +Should return: +[ + [1,1,1,1], + [1,2,2,2], + [1,2,3,3] +] +""" + +from heapq import heappush, heappop +from typing import List + + +def sort_diagonally(mat: List[List[int]]) -> List[List[int]]: + # If the input is a vector, return the vector + if len(mat) == 1 or len(mat[0]) == 1: + return mat + + # Rows + columns - 1 + # The -1 helps you to not repeat a column + for i in range(len(mat)+len(mat[0])-1): + # Process the rows + if i+1 < len(mat): + # Initialize heap, set row and column + h = [] + row = len(mat)-(i+1) + col = 0 + + # Traverse diagonally, and add the values to the heap + while row < len(mat): + heappush(h, (mat[row][col])) + row += 1 + col += 1 + + # Sort the diagonal + row = len(mat)-(i+1) + col = 0 + while h: + ele = heappop(h) + mat[row][col] = ele + row += 1 + col += 1 + else: + # Process the columns + # Initialize heap, row and column + h = [] + row = 0 + col = i - (len(mat)-1) + + # Traverse Diagonally + while col < len(mat[0]) and row < len(mat): + heappush(h, (mat[row][col])) + row += 1 + col += 1 + + # Sort the diagonal + row = 0 + col = i - (len(mat)-1) + while h: + ele = heappop(h) + mat[row][col] = ele + row += 1 + col += 1 + + # Return the updated matrix + return mat diff --git a/matrix/sparse_dot_vector.py b/algorithms/matrix/sparse_dot_vector.py similarity index 98% rename from matrix/sparse_dot_vector.py rename to algorithms/matrix/sparse_dot_vector.py index 19053ec58..0cba0a575 100644 --- a/matrix/sparse_dot_vector.py +++ b/algorithms/matrix/sparse_dot_vector.py @@ -1,7 +1,8 @@ #! /usr/bin/env python3 """ -Suppose we have very large sparse vectors, which contains a lot of zeros and double . +Suppose we have very large sparse vectors, which contains a lot of +zeros and double . find a data structure to store them get the dot product of them diff --git a/matrix/sparse_mul.py b/algorithms/matrix/sparse_mul.py similarity index 82% rename from matrix/sparse_mul.py rename to algorithms/matrix/sparse_mul.py index c3b7b61e4..eeaae0eff 100644 --- a/matrix/sparse_mul.py +++ b/algorithms/matrix/sparse_mul.py @@ -30,7 +30,8 @@ def multiply(self, a, b): :type B: List[List[int]] :rtype: List[List[int]] """ - if a is None or b is None: return None + if a is None or b is None: + return None m, n, l = len(a), len(b[0]), len(b[0]) if len(b) != n: raise Exception("A's column number must be equal to B's row number.") @@ -39,7 +40,8 @@ def multiply(self, a, b): for k, eleA in enumerate(row): if eleA: for j, eleB in enumerate(b[k]): - if eleB: c[i][j] += eleA * eleB + if eleB: + c[i][j] += eleA * eleB return c @@ -50,7 +52,8 @@ def multiply(self, a, b): :type B: List[List[int]] :rtype: List[List[int]] """ - if a is None or b is None: return None + if a is None or b is None: + return None m, n, l = len(a), len(a[0]), len(b[0]) if len(b) != n: raise Exception("A's column number must be equal to B's row number.") @@ -59,7 +62,8 @@ def multiply(self, a, b): for k, row in enumerate(b): table_b[k] = {} for j, eleB in enumerate(row): - if eleB: table_b[k][j] = eleB + if eleB: + table_b[k][j] = eleB for i, row in enumerate(a): for k, eleA in enumerate(row): if eleA: @@ -67,6 +71,7 @@ def multiply(self, a, b): c[i][j] += eleA * eleB return c + # Python solution with two tables (~196ms): def multiply(self, a, b): """ @@ -74,7 +79,8 @@ def multiply(self, a, b): :type B: List[List[int]] :rtype: List[List[int]] """ - if a is None or b is None: return None + if a is None or b is None: + return None m, n = len(a), len(b[0]) if len(b) != n: raise Exception("A's column number must be equal to B's row number.") @@ -83,17 +89,20 @@ def multiply(self, a, b): for i, row in enumerate(a): for j, ele in enumerate(row): if ele: - if i not in table_a: table_a[i] = {} + if i not in table_a: + table_a[i] = {} table_a[i][j] = ele for i, row in enumerate(b): for j, ele in enumerate(row): if ele: - if i not in table_b: table_b[i] = {} + if i not in table_b: + table_b[i] = {} table_b[i][j] = ele c = [[0 for j in range(l)] for i in range(m)] for i in table_a: for k in table_a[i]: - if k not in table_b: continue + if k not in table_b: + continue for j in table_b[k]: c[i][j] += table_a[i][k] * table_b[k][j] return c diff --git a/matrix/spiral_traversal.py b/algorithms/matrix/spiral_traversal.py similarity index 100% rename from matrix/spiral_traversal.py rename to algorithms/matrix/spiral_traversal.py diff --git a/matrix/sudoku_validator.py b/algorithms/matrix/sudoku_validator.py similarity index 55% rename from matrix/sudoku_validator.py rename to algorithms/matrix/sudoku_validator.py index ad2faebeb..257fcce8e 100644 --- a/matrix/sudoku_validator.py +++ b/algorithms/matrix/sudoku_validator.py @@ -1,12 +1,19 @@ """ -Write a function validSolution/ValidateSolution/valid_solution() that accepts a 2D array representing a Sudoku board, and returns true if it is a valid solution, or false otherwise. The cells of the sudoku board may also contain 0's, which will represent empty cells. Boards containing one or more zeroes are considered to be invalid solutions. -The board is always 9 cells by 9 cells, and every cell only contains integers from 0 to 9. +Write a function validSolution/ValidateSolution/valid_solution() +that accepts a 2D array representing a Sudoku board, and returns true +if it is a valid solution, or false otherwise. The cells of the sudoku +board may also contain 0's, which will represent empty cells. +Boards containing one or more zeroes are considered to be invalid solutions. +The board is always 9 cells by 9 cells, and every cell only contains integers +from 0 to 9. (More info at: http://en.wikipedia.org/wiki/Sudoku) """ # Using dict/hash-table from collections import defaultdict + + def valid_solution_hashtable(board): for i in range(len(board)): dict_row = defaultdict(int) @@ -31,7 +38,7 @@ def valid_solution_hashtable(board): grid_add = 0 for k in range(3): for l in range(3): - grid_add += board[i*3+k][j*3+l] + grid_add += board[i * 3 + k][j * 3 + l] if grid_add != 45: return False return True @@ -65,7 +72,7 @@ def valid_solution(board): # Using set -def valid_solution_set (board): +def valid_solution_set(board): valid = set(range(1, 10)) for row in board: @@ -82,32 +89,3 @@ def valid_solution_set (board): return False return True - -# test cases -# To avoid congestion I'll leave testing all the functions to the reader. Just change the name of the function in the below test cases. -import unittest -class TestSuite(unittest.TestCase): - def test_valid(self): - self.assertTrue(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]])) - - def test_invalid(self): - self.assertFalse(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]])) - -if __name__ == "__main__": - unittest.main() diff --git a/algorithms/matrix/sum_sub_squares.py b/algorithms/matrix/sum_sub_squares.py new file mode 100644 index 000000000..1231547ef --- /dev/null +++ b/algorithms/matrix/sum_sub_squares.py @@ -0,0 +1,23 @@ +# Function to find sum of all +# sub-squares of size k x k in a given +# square matrix of size n x n +def sum_sub_squares(matrix, k): + n = len(matrix) + result = [[0 for i in range(k)] for j in range(k)] + + if k > n: + return + for i in range(n - k + 1): + l = 0 + for j in range(n - k + 1): + sum = 0 + + # Calculate and print sum of current sub-square + for p in range(i, k + i): + for q in range(j, k + j): + sum += matrix[p][q] + + result[i][l] = sum + l += 1 + + return result diff --git a/algorithms/ml/nearest_neighbor.py b/algorithms/ml/nearest_neighbor.py new file mode 100644 index 000000000..d0fabab15 --- /dev/null +++ b/algorithms/ml/nearest_neighbor.py @@ -0,0 +1,41 @@ +import math + +def distance(x,y): + """[summary] + HELPER-FUNCTION + calculates the (eulidean) distance between vector x and y. + + Arguments: + x {[tuple]} -- [vector] + y {[tuple]} -- [vector] + """ + assert len(x) == len(y), "The vector must have same length" + result = () + sum = 0 + for i in range(len(x)): + result += (x[i] -y[i],) + for component in result: + sum += component**2 + return math.sqrt(sum) + + +def nearest_neighbor(x, tSet): + """[summary] + Implements the nearest neighbor algorithm + + Arguments: + x {[tupel]} -- [vector] + tSet {[dict]} -- [training set] + + Returns: + [type] -- [result of the AND-function] + """ + assert isinstance(x, tuple) and isinstance(tSet, dict) + current_key = () + min_d = float('inf') + for key in tSet: + d = distance(x, key) + if d < min_d: + min_d = d + current_key = key + return tSet[current_key] diff --git a/algorithms/queues/__init__.py b/algorithms/queues/__init__.py new file mode 100644 index 000000000..3c4f22ca0 --- /dev/null +++ b/algorithms/queues/__init__.py @@ -0,0 +1,4 @@ +from .queue import * +from .max_sliding_window import * +from .reconstruct_queue import * +from .priority_queue import * diff --git a/queues/max_sliding_window.py b/algorithms/queues/max_sliding_window.py similarity index 99% rename from queues/max_sliding_window.py rename to algorithms/queues/max_sliding_window.py index 7d1a45f11..74db65e11 100644 --- a/queues/max_sliding_window.py +++ b/algorithms/queues/max_sliding_window.py @@ -18,6 +18,8 @@ """ import collections + + def max_sliding_window(arr, k): qi = collections.deque() # queue storing indexes of elements result = [] diff --git a/queues/moving_average.py b/algorithms/queues/moving_average.py similarity index 100% rename from queues/moving_average.py rename to algorithms/queues/moving_average.py diff --git a/algorithms/queues/priority_queue.py b/algorithms/queues/priority_queue.py new file mode 100644 index 000000000..c573ac3cc --- /dev/null +++ b/algorithms/queues/priority_queue.py @@ -0,0 +1,55 @@ +""" +Implementation of priority queue using linear array. +Insertion - O(n) +Extract min/max Node - O(1) +""" +import itertools + + +class PriorityQueueNode: + def __init__(self, data, priority): + self.data = data + self.priority = priority + + def __repr__(self): + return "{}: {}".format(self.data, self.priority) + + +class PriorityQueue: + def __init__(self, items=None, priorities=None): + """Create a priority queue with items (list or iterable). + If items is not passed, create empty priority queue.""" + self.priority_queue_list = [] + if items is None: + return + if priorities is None: + priorities = itertools.repeat(None) + for item, priority in zip(items, priorities): + self.push(item, priority=priority) + + def __repr__(self): + return "PriorityQueue({!r})".format(self.priority_queue_list) + + def size(self): + """Return size of the priority queue. + """ + return len(self.priority_queue_list) + + def push(self, item, priority=None): + """Push the item in the priority queue. + if priority is not given, priority is set to the value of item. + """ + 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 + # when traversed complete queue + self.priority_queue_list.append(node) + + def pop(self): + """Remove and return the item with the lowest priority. + """ + # remove and return the first node from the queue + return self.priority_queue_list.pop().data diff --git a/queues/queue.py b/algorithms/queues/queue.py similarity index 99% rename from queues/queue.py rename to algorithms/queues/queue.py index feab56b62..b3ccb4e7f 100644 --- a/queues/queue.py +++ b/algorithms/queues/queue.py @@ -13,6 +13,8 @@ * peek() returns the front element of the queue. """ from abc import ABCMeta, abstractmethod + + class AbstractQueue(metaclass=ABCMeta): def __init__(self): @@ -96,6 +98,7 @@ def __init__(self, value): self.value = value self.next = None + class LinkedListQueue(AbstractQueue): def __init__(self): diff --git a/queues/reconstruct_queue.py b/algorithms/queues/reconstruct_queue.py similarity index 100% rename from queues/reconstruct_queue.py rename to algorithms/queues/reconstruct_queue.py diff --git a/queues/zigzagiterator.py b/algorithms/queues/zigzagiterator.py similarity index 71% rename from queues/zigzagiterator.py rename to algorithms/queues/zigzagiterator.py index a8b571f94..5b1e50371 100644 --- a/queues/zigzagiterator.py +++ b/algorithms/queues/zigzagiterator.py @@ -5,25 +5,28 @@ def __init__(self, v1, v2): :type v1: List[int] :type v2: List[int] """ - self.queue=[_ for _ in (v1,v2) if _] + self.queue = [_ for _ in (v1, v2) if _] print(self.queue) def next(self): """ :rtype: int """ - v=self.queue.pop(0) - ret=v.pop(0) - if v: self.queue.append(v) + v = self.queue.pop(0) + ret = v.pop(0) + if v: + self.queue.append(v) return ret def has_next(self): """ :rtype: bool """ - if self.queue: return True + if self.queue: + return True return False + l1 = [1, 2] l2 = [3, 4, 5, 6] it = ZigZagIterator(l1, l2) diff --git a/algorithms/search/__init__.py b/algorithms/search/__init__.py new file mode 100644 index 000000000..3f39479bc --- /dev/null +++ b/algorithms/search/__init__.py @@ -0,0 +1,17 @@ +""" +Collection of search algorithms: finding the needle in a haystack. +""" + +from .binary_search import * +from .ternary_search import * +from .first_occurrence import * +from .last_occurrence import * +from .linear_search import * +from .search_insert import * +from .two_sum import * +from .search_range import * +from .find_min_rotate import * +from .search_rotate import * +from .jump_search import * +from .next_greatest_letter import * +from .interpolation_search import * diff --git a/algorithms/search/binary_search.py b/algorithms/search/binary_search.py new file mode 100644 index 000000000..2a7c9bc3e --- /dev/null +++ b/algorithms/search/binary_search.py @@ -0,0 +1,52 @@ +""" +Binary Search + +Find an element in a sorted array (in ascending order). +""" + +# For Binary Search, T(N) = T(N/2) + O(1) // the recurrence relation +# Apply Masters Theorem for computing Run time complexity of recurrence relations: +# T(N) = aT(N/b) + f(N) +# Here, +# a = 1, b = 2 => log (a base b) = 1 +# also, here +# f(N) = n^c log^k(n) // k = 0 & c = log (a base b) +# So, +# T(N) = O(N^c log^(k+1)N) = O(log(N)) + +def binary_search(array, query): + """ + Worst-case Complexity: O(log(n)) + + reference: https://en.wikipedia.org/wiki/Binary_search_algorithm + """ + + low, high = 0, len(array) - 1 + while low <= high: + mid = (high + low) // 2 + val = array[mid] + if val == query: + return mid + + if val < query: + low = mid + 1 + else: + high = mid - 1 + return None + +#In this below function we are passing array, it's first index , last index and value to be searched +def binary_search_recur(array, low, high, val): + """ + Worst-case Complexity: O(log(n)) + + reference: https://en.wikipedia.org/wiki/Binary_search_algorithm + """ +#Here in Logic section first we are checking if low is greater than high which means its an error condition because low index should not move ahead of high index + if low > high: + return -1 + mid = low + (high-low)//2 #This mid will not break integer range + if val < array[mid]: + return binary_search_recur(array, low, mid - 1, val) #Go search in the left subarray + if val > array[mid]: + return binary_search_recur(array, mid + 1, high, val) #Go search in the right subarray + return mid diff --git a/search/find_min_rotate.py b/algorithms/search/find_min_rotate.py similarity index 72% rename from search/find_min_rotate.py rename to algorithms/search/find_min_rotate.py index 1afc4eef2..b47fd4e87 100644 --- a/search/find_min_rotate.py +++ b/algorithms/search/find_min_rotate.py @@ -7,6 +7,9 @@ You may assume no duplicate exists in the array. """ def find_min_rotate(array): + """ + Finds the minimum element in a sorted array that has been rotated. + """ low = 0 high = len(array) - 1 while low < high: @@ -19,10 +22,12 @@ def find_min_rotate(array): return array[low] def find_min_rotate_recur(array, low, high): + """ + Finds the minimum element in a sorted array that has been rotated. + """ mid = (low + high) // 2 if mid == low: return array[low] - elif array[mid] > array[high]: + if array[mid] > array[high]: return find_min_rotate_recur(array, mid + 1, high) - else: - return find_min_rotate_recur(array, low, mid) + return find_min_rotate_recur(array, low, mid) diff --git a/algorithms/search/first_occurrence.py b/algorithms/search/first_occurrence.py new file mode 100644 index 000000000..9d9beaae1 --- /dev/null +++ b/algorithms/search/first_occurrence.py @@ -0,0 +1,23 @@ +""" +Find first occurance of a number in a sorted array (increasing order) +Approach- Binary Search +T(n)- O(log n) +""" +def first_occurrence(array, query): + """ + Returns the index of the first occurance of the given element in an array. + The array has to be sorted in increasing order. + """ + + low, high = 0, len(array) - 1 + while low <= high: + mid = low + (high-low)//2 #Now mid will be ininteger range + #print("lo: ", lo, " hi: ", hi, " mid: ", mid) + if low == high: + break + if array[mid] < query: + low = mid + 1 + else: + high = mid + if array[low] == query: + return low diff --git a/algorithms/search/interpolation_search.py b/algorithms/search/interpolation_search.py new file mode 100644 index 000000000..5b1d00a1a --- /dev/null +++ b/algorithms/search/interpolation_search.py @@ -0,0 +1,61 @@ +""" +Python implementation of the Interpolation Search algorithm. +Given a sorted array in increasing order, interpolation search calculates +the starting point of its search according to the search key. + +FORMULA: start_pos = low + [ (x - arr[low])*(high - low) / (arr[high] - arr[low]) ] + +Doc: https://en.wikipedia.org/wiki/Interpolation_search + +Time Complexity: O(log2(log2 n)) for average cases, O(n) for the worst case. +The algorithm performs best with uniformly distributed arrays. +""" + +from typing import List + + +def interpolation_search(array: List[int], search_key: int) -> int: + """ + :param array: The array to be searched. + :param search_key: The key to be searched in the array. + + :returns: Index of search_key in array if found, else -1. + + 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 + + """ + + # highest and lowest index in array + high = len(array) - 1 + low = 0 + + while (low <= high) and (array[low] <= search_key <= array[high]): + # calculate the search position + pos = low + int(((search_key - array[low]) * + (high - low) / (array[high] - array[low]))) + + # search_key is found + if array[pos] == search_key: + return pos + + # if search_key is larger, search_key is in upper part + if array[pos] < search_key: + low = pos + 1 + + # if search_key is smaller, search_key is in lower part + else: + high = pos - 1 + + return -1 + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/algorithms/search/jump_search.py b/algorithms/search/jump_search.py new file mode 100644 index 000000000..2ec074938 --- /dev/null +++ b/algorithms/search/jump_search.py @@ -0,0 +1,45 @@ +""" +Jump Search + +Find an element in a sorted array. +""" + +import math + +def jump_search(arr,target): + """ + Worst-case Complexity: O(√n) (root(n)) + All items in list must be sorted like binary search + + Find block that contains target value and search it linearly in that block + It returns a first target value in array + + reference: https://en.wikipedia.org/wiki/Jump_search + """ + + length = len(arr) + block_size = int(math.sqrt(length)) + block_prev = 0 + block= block_size + + # return -1 means that array doesn't contain target value + # find block that contains target value + + if arr[length - 1] < target: + return -1 + while block <= length and arr[block - 1] < target: + block_prev = block + block += block_size + + # find target value in block + + while arr[block_prev] < target : + block_prev += 1 + if block_prev == min(block, length) : + return -1 + + # if there is target value in array, return it + + if arr[block_prev] == target : + return block_prev + return -1 diff --git a/algorithms/search/last_occurrence.py b/algorithms/search/last_occurrence.py new file mode 100644 index 000000000..6374625e6 --- /dev/null +++ b/algorithms/search/last_occurrence.py @@ -0,0 +1,20 @@ +""" +Find last occurance of a number in a sorted array (increasing order) +Approach- Binary Search +T(n)- O(log n) +""" +def last_occurrence(array, query): + """ + Returns the index of the last occurance of the given element in an array. + The array has to be sorted in increasing order. + """ + 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 diff --git a/algorithms/search/linear_search.py b/algorithms/search/linear_search.py new file mode 100644 index 000000000..d375d77f2 --- /dev/null +++ b/algorithms/search/linear_search.py @@ -0,0 +1,15 @@ +""" +Linear search works in any array. +T(n): O(n) +""" + +def linear_search(array, query): + """ + Find the index of the given element in the array. + There are no restrictions on the order of the elements in the array. + If the element couldn't be found, returns -1. + """ + for i, value in enumerate(array): + if value == query: + return i + return -1 diff --git a/algorithms/search/next_greatest_letter.py b/algorithms/search/next_greatest_letter.py new file mode 100644 index 000000000..26ec8536d --- /dev/null +++ b/algorithms/search/next_greatest_letter.py @@ -0,0 +1,60 @@ +''' +Given a list of sorted characters letters containing only lowercase letters, +and given a target letter target, find the smallest element in the list that +is larger than the given target. + +Letters also wrap around. For example, if the target is target = 'z' and +letters = ['a', 'b'], the answer is 'a'. + +Input: +letters = ["c", "f", "j"] +target = "a" +Output: "c" + +Input: +letters = ["c", "f", "j"] +target = "c" +Output: "f" + +Input: +letters = ["c", "f", "j"] +target = "d" +Output: "f" + +Reference: https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/ +''' + +import bisect + +def next_greatest_letter(letters, target): + """ + Using bisect libarary + """ + index = bisect.bisect(letters, target) + return letters[index % len(letters)] + +def next_greatest_letter_v1(letters, target): + """ + Using binary search: complexity O(logN) + """ + 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, target): + """ + Brute force: complexity O(N) + """ + for index in letters: + if index > target: + return index + return letters[0] diff --git a/algorithms/search/search_insert.py b/algorithms/search/search_insert.py new file mode 100644 index 000000000..279e64e40 --- /dev/null +++ b/algorithms/search/search_insert.py @@ -0,0 +1,24 @@ +""" +Helper methods for implementing insertion sort. +""" + +def search_insert(array, val): + """ + 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. + + For example: + [1,3,5,6], 5 -> 2 + [1,3,5,6], 2 -> 1 + [1,3,5,6], 7 -> 4 + [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 diff --git a/search/search_range.py b/algorithms/search/search_range.py similarity index 78% rename from search/search_range.py rename to algorithms/search/search_range.py index 116d8f541..f0f2bfba0 100644 --- a/search/search_range.py +++ b/algorithms/search/search_range.py @@ -17,17 +17,17 @@ def search_range(nums, target): """ low = 0 high = len(nums) - 1 - while low <= high: + # breaks at low == high + # both pointing to first occurence of target + while low < high: mid = low + (high - low) // 2 - if target < nums[mid]: - high = mid - 1 - elif target > nums[mid]: - low = mid + 1 + if target <= nums[mid]: + high = mid else: - break + low = mid + 1 for j in range(len(nums) - 1, -1, -1): if nums[j] == target: - return [mid, j] + return [low, j] return [-1, -1] diff --git a/search/search_rotate.py b/algorithms/search/search_rotate.py similarity index 81% rename from search/search_rotate.py rename to algorithms/search/search_rotate.py index ade027082..fe5474538 100644 --- a/search/search_rotate.py +++ b/algorithms/search/search_rotate.py @@ -37,9 +37,10 @@ Recursion helps you understand better the above algorithm explanation """ def search_rotate(array, val): - if not array: - return -1 - + """ + Finds the index of the given value in an array that has been sorted in + ascending order and then rotated at some unknown pivot. + """ low, high = 0, len(array) - 1 while low <= high: mid = (low + high) // 2 @@ -61,6 +62,10 @@ def search_rotate(array, val): # Recursion technique def search_rotate_recur(array, low, high, val): + """ + Finds the index of the given value in an array that has been sorted in + ascending order and then rotated at some unknown pivot. + """ if low >= high: return -1 mid = (low + high) // 2 @@ -69,10 +74,7 @@ def search_rotate_recur(array, low, high, val): if array[low] <= array[mid]: if array[low] <= val <= array[mid]: return search_rotate_recur(array, low, mid - 1, val) # Search left - else: - return search_rotate_recur(array, mid + 1, high, val) # Search right - else: - if array[mid] <= val <= array[high]: - return search_rotate_recur(array, mid + 1, high, val) # Search right - else: - return search_rotate_recur(array, low, mid - 1, val) # Search left + return search_rotate_recur(array, mid + 1, high, val) # Search right + if array[mid] <= val <= array[high]: + return search_rotate_recur(array, mid + 1, high, val) # Search right + return search_rotate_recur(array, low, mid - 1, val) # Search left diff --git a/algorithms/search/ternary_search.py b/algorithms/search/ternary_search.py new file mode 100644 index 000000000..0d0ee1b66 --- /dev/null +++ b/algorithms/search/ternary_search.py @@ -0,0 +1,42 @@ +""" +Ternary search is a divide and conquer algorithm that can be used to find an element in an array. +It is similar to binary search where we divide the array into two parts but in this algorithm, +we divide the given array into three parts and determine which has the key (searched element). +We can divide the array into three parts by taking mid1 and mid2. +Initially, l and r will be equal to 0 and n-1 respectively, where n is the length of the array. +mid1 = l + (r-l)/3 +mid2 = r – (r-l)/3 + +Note: Array needs to be sorted to perform ternary search on it. +T(N) = O(log3(N)) +log3 = log base 3 +""" +def ternary_search(left, right, key, arr): + """ + Find the given value (key) in an array sorted in ascending order. + Returns the index of the value if found, and -1 otherwise. + If the index is not in the range left..right (ie. left <= index < right) returns -1. + """ + + while right >= left: + mid1 = left + (right-left) // 3 + mid2 = right - (right-left) // 3 + + if key == arr[mid1]: + return mid1 + if key == mid2: + return mid2 + + if key < arr[mid1]: + # key lies between l and mid1 + right = mid1 - 1 + elif key > arr[mid2]: + # key lies between mid2 and r + left = mid2 + 1 + else: + # key lies between mid1 and mid2 + left = mid1 + 1 + right = mid2 - 1 + + # key not found + return -1 diff --git a/search/two_sum.py b/algorithms/search/two_sum.py similarity index 52% rename from search/two_sum.py rename to algorithms/search/two_sum.py index 4251e5378..e8400fd1c 100644 --- a/search/two_sum.py +++ b/algorithms/search/two_sum.py @@ -15,37 +15,57 @@ two_sum1: using dictionary as a hash table two_sum2: using two pointers """ -# Using binary search technique + def two_sum(numbers, target): - for i in range(len(numbers)): - second_val = target - numbers[i] + """ + Given a list of numbers sorted in ascending order, find the indices of two + numbers such that their sum is the given target. + + Using binary search. + """ + 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] - elif second_val > numbers[mid]: + + if second_val > numbers[mid]: low = mid + 1 else: high = mid - 1 + return None -# Using dictionary as a hash table def two_sum1(numbers, target): + """ + Given a list of numbers, find the indices of two numbers such that their + sum is the given target. + + Using a hash table. + """ dic = {} for i, num in enumerate(numbers): if target - num in dic: return [dic[target - num] + 1, i + 1] dic[num] = i + return None -# Using two pointers def two_sum2(numbers, target): - p1 = 0 # pointer 1 holds from left of array numbers - p2 = len(numbers) - 1 # pointer 2 holds from right of array numbers - while p1 < p2: - s = numbers[p1] + numbers[p2] - if s == target: - return [p1 + 1, p2 + 1] - elif s > target: - p2 = p2 - 1 + """ + Given a list of numbers sorted in ascending order, find the indices of two + numbers such that their sum is the given target. + + Using a bidirectional linear search. + """ + left = 0 # pointer 1 holds from left of array numbers + right = len(numbers) - 1 # pointer 2 holds from right of array numbers + 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: - p1 = p1 + 1 + left = left + 1 diff --git a/algorithms/set/__init__.py b/algorithms/set/__init__.py new file mode 100644 index 000000000..534e5de48 --- /dev/null +++ b/algorithms/set/__init__.py @@ -0,0 +1 @@ +from .find_keyboard_row import * diff --git a/algorithms/set/find_keyboard_row.py b/algorithms/set/find_keyboard_row.py new file mode 100644 index 000000000..c15dcdadf --- /dev/null +++ b/algorithms/set/find_keyboard_row.py @@ -0,0 +1,27 @@ +""" +Given a List of words, return the words that can be typed using letters of +alphabet on only one row's of American keyboard. + +For example: +Input: ["Hello", "Alaska", "Dad", "Peace"] +Output: ["Alaska", "Dad"] + +Reference: https://leetcode.com/problems/keyboard-row/description/ +""" + +def find_keyboard_row(words): + """ + :type words: List[str] + :rtype: List[str] + """ + keyboard = [ + set('qwertyuiop'), + set('asdfghjkl'), + set('zxcvbnm'), + ] + result = [] + for word in words: + for key in keyboard: + if set(word.lower()).issubset(key): + result.append(word) + return result diff --git a/set/randomized_set.py b/algorithms/set/randomized_set.py similarity index 100% rename from set/randomized_set.py rename to algorithms/set/randomized_set.py diff --git a/set/set_covering.py b/algorithms/set/set_covering.py similarity index 100% rename from set/set_covering.py rename to algorithms/set/set_covering.py diff --git a/algorithms/sort/__init__.py b/algorithms/sort/__init__.py new file mode 100644 index 000000000..fb186c024 --- /dev/null +++ b/algorithms/sort/__init__.py @@ -0,0 +1,21 @@ +from .bitonic_sort import * +from .bogo_sort import * +from .bubble_sort import * +from .comb_sort import * +from .counting_sort import * +from .cycle_sort import * +from .exchange_sort import * +from .heap_sort import * +from .insertion_sort import * +from .merge_sort import * +from .pancake_sort import * +from .pigeonhole_sort import * +from .quick_sort import * +from .selection_sort import * +from .top_sort import * +from .bucket_sort import * +from .shell_sort import * +from .stooge_sort import * +from .radix_sort import * +from .gnome_sort import * +from .cocktail_shaker_sort import * diff --git a/algorithms/sort/bead_sort.py b/algorithms/sort/bead_sort.py new file mode 100644 index 000000000..0d949fa82 --- /dev/null +++ b/algorithms/sort/bead_sort.py @@ -0,0 +1,26 @@ +""" +Bead Sort (also known as Gravity Sort) is a natural sorting algorithm that simulates how beads would settle under gravity on an abacus. It is most useful for sorting positive integers, especially when the range of numbers isn't excessively large. However, it is not a comparison-based sort and is generally impractical for large inputs due to its reliance on physical modeling. +Time Complexity +- Best Case: O(n) if the numbers are already sorted +- Average Case: O(n^2) because each bead needs to be placed and then fall under gravity +- Worst Case: O(n^2) since each bead must "fall" individually +""" + +def bead_sort(arr): + if any(num < 0 for num in arr): + raise ValueError("Bead sort only works with non-negative integers.") + + max_num = max(arr) if arr else 0 + grid = [[0] * len(arr) for _ in range(max_num)] + + # Drop beads (place beads in columns) + for col, num in enumerate(arr): + for row in range(num): + grid[row][col] = 1 + + # Let the beads "fall" (count beads in each row) + for row in grid: + sum_beads = sum(row) + for col in range(len(arr)): + row[col] = 1 if col < sum_beads else 0 + diff --git a/algorithms/sort/bitonic_sort.py b/algorithms/sort/bitonic_sort.py new file mode 100644 index 000000000..f8cad90e1 --- /dev/null +++ b/algorithms/sort/bitonic_sort.py @@ -0,0 +1,43 @@ +def bitonic_sort(arr, reverse=False): + """ + bitonic sort is sorting algorithm to use multiple process, but this code not containing parallel process + It can sort only array that sizes power of 2 + It can sort array in both increasing order and decreasing order by giving argument true(increasing) and false(decreasing) + + Worst-case in parallel: O(log(n)^2) + Worst-case in non-parallel: O(nlog(n)^2) + + reference: https://en.wikipedia.org/wiki/Bitonic_sorter + """ + def compare(arr, reverse): + n = len(arr)//2 + for i in range(n): + if reverse != (arr[i] > arr[i+n]): + arr[i], arr[i+n] = arr[i+n], arr[i] + return arr + + def bitonic_merge(arr, reverse): + n = len(arr) + + if n <= 1: + return arr + + arr = compare(arr, reverse) + left = bitonic_merge(arr[:n // 2], reverse) + right = bitonic_merge(arr[n // 2:], reverse) + return left + right + + #end of function(compare and bitionic_merge) definition + n = len(arr) + if n <= 1: + return arr + # checks if n is power of two + if not (n and (not(n & (n - 1))) ): + raise ValueError("the size of input should be power of two") + + left = bitonic_sort(arr[:n // 2], True) + right = bitonic_sort(arr[n // 2:], False) + + arr = bitonic_merge(left + right, reverse) + + return arr diff --git a/algorithms/sort/bogo_sort.py b/algorithms/sort/bogo_sort.py new file mode 100644 index 000000000..60e1be8f0 --- /dev/null +++ b/algorithms/sort/bogo_sort.py @@ -0,0 +1,32 @@ +import random + +def bogo_sort(arr, simulation=False): + """Bogo Sort + Best Case Complexity: O(n) + Worst Case Complexity: O(∞) + Average Case Complexity: O(n(n-1)!) + """ + + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + + def is_sorted(arr): + #check the array is inorder + i = 0 + arr_len = len(arr) + while i+1 < arr_len: + if arr[i] > arr[i+1]: + return False + i += 1 + + + return True + while not is_sorted(arr): + random.shuffle(arr) + + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + + return arr diff --git a/algorithms/sort/bubble_sort.py b/algorithms/sort/bubble_sort.py new file mode 100644 index 000000000..84f406795 --- /dev/null +++ b/algorithms/sort/bubble_sort.py @@ -0,0 +1,35 @@ +""" + +https://en.wikipedia.org/wiki/Bubble_sort + +Worst-case performance: O(N^2) + +If you call bubble_sort(arr,True), you can see the process of the sort +Default is simulation = False + +""" + + +def bubble_sort(arr, simulation=False): + def swap(i, j): + arr[i], arr[j] = arr[j], arr[i] + + n = len(arr) + swapped = True + + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + x = -1 + while swapped: + swapped = False + x = x + 1 + for i in range(1, n-x): + if arr[i - 1] > arr[i]: + swap(i - 1, i) + swapped = True + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + + return arr diff --git a/algorithms/sort/bucket_sort.py b/algorithms/sort/bucket_sort.py new file mode 100644 index 000000000..d89232ccf --- /dev/null +++ b/algorithms/sort/bucket_sort.py @@ -0,0 +1,28 @@ +def bucket_sort(arr): + ''' Bucket Sort + Complexity: O(n^2) + The complexity is dominated by nextSort + ''' + # The number of buckets and make buckets + num_buckets = len(arr) + buckets = [[] for bucket in range(num_buckets)] + # Assign values into bucket_sort + for value in arr: + index = value * num_buckets // (max(arr) + 1) + buckets[index].append(value) + # Sort + sorted_list = [] + for i in range(num_buckets): + sorted_list.extend(next_sort(buckets[i])) + return sorted_list + +def next_sort(arr): + # We will use insertion sort here. + for i in range(1, len(arr)): + j = i - 1 + key = arr[i] + while arr[j] > key and j >= 0: + arr[j+1] = arr[j] + j = j - 1 + arr[j + 1] = key + return arr diff --git a/algorithms/sort/cocktail_shaker_sort.py b/algorithms/sort/cocktail_shaker_sort.py new file mode 100644 index 000000000..973b824b6 --- /dev/null +++ b/algorithms/sort/cocktail_shaker_sort.py @@ -0,0 +1,30 @@ +def cocktail_shaker_sort(arr): + """ + Cocktail_shaker_sort + Sorting a given array + mutation of bubble sort + + reference: https://en.wikipedia.org/wiki/Cocktail_shaker_sort + + Worst-case performance: O(N^2) + """ + + def swap(i, j): + arr[i], arr[j] = arr[j], arr[i] + + n = len(arr) + swapped = True + while swapped: + swapped = False + for i in range(1, n): + if arr[i - 1] > arr[i]: + swap(i - 1, i) + swapped = True + if swapped == False: + return arr + swapped = False + for i in range(n-1,0,-1): + if arr[i - 1] > arr[i]: + swap(i - 1, i) + swapped = True + return arr diff --git a/sort/comb_sort.py b/algorithms/sort/comb_sort.py similarity index 89% rename from sort/comb_sort.py rename to algorithms/sort/comb_sort.py index 338771d2b..efa58fc5e 100644 --- a/sort/comb_sort.py +++ b/algorithms/sort/comb_sort.py @@ -5,11 +5,9 @@ Worst-case performance: O(N^2) """ -from math import floor def comb_sort(arr): - def swap(i, j): arr[i], arr[j] = arr[j], arr[i] @@ -18,7 +16,7 @@ def swap(i, j): shrink = 1.3 sorted = False while not sorted: - gap = int(floor(gap/shrink)) + gap = int(gap / shrink) if gap > 1: sorted = False else: diff --git a/algorithms/sort/counting_sort.py b/algorithms/sort/counting_sort.py new file mode 100644 index 000000000..46688e218 --- /dev/null +++ b/algorithms/sort/counting_sort.py @@ -0,0 +1,36 @@ +def counting_sort(arr): + """ + Counting_sort + Sorting a array which has no element greater than k + Creating a new temp_arr,where temp_arr[i] contain the number of + element less than or equal to i in the arr + Then placing the number i into a correct position in the result_arr + return the result_arr + Complexity: 0(n) + """ + + m = min(arr) + # in case there are negative elements, change the array to all positive element + different = 0 + if m < 0: + # save the change, so that we can convert the array back to all positive number + different = -m + for i in range(len(arr)): + arr[i] += -m + k = max(arr) + temp_arr = [0] * (k + 1) + for i in range(0, len(arr)): + temp_arr[arr[i]] = temp_arr[arr[i]] + 1 + # temp_array[i] contain the times the number i appear in arr + + for i in range(1, k + 1): + temp_arr[i] = temp_arr[i] + temp_arr[i - 1] + # temp_array[i] contain the number of element less than or equal i in arr + + result_arr = arr.copy() + # creating a result_arr an put the element in a correct positon + for i in range(len(arr) - 1, -1, -1): + result_arr[temp_arr[arr[i]] - 1] = arr[i] - different + temp_arr[arr[i]] = temp_arr[arr[i]] - 1 + + return result_arr diff --git a/algorithms/sort/cycle_sort.py b/algorithms/sort/cycle_sort.py new file mode 100644 index 000000000..6e512c924 --- /dev/null +++ b/algorithms/sort/cycle_sort.py @@ -0,0 +1,46 @@ +def cycle_sort(arr): + """ + cycle_sort + This is based on the idea that the permutations to be sorted + can be decomposed into cycles, + and the results can be individually sorted by cycling. + + reference: https://en.wikipedia.org/wiki/Cycle_sort + + Average time complexity : O(N^2) + Worst case time complexity : O(N^2) + """ + len_arr = len(arr) + # Finding cycle to rotate. + for cur in range(len_arr - 1): + item = arr[cur] + + # Finding an indx to put items in. + index = cur + for i in range(cur + 1, len_arr): + if arr[i] < item: + index += 1 + + # Case of there is not a cycle + if index == cur: + continue + + # Putting the item immediately right after the duplicate item or on the right. + while item == arr[index]: + index += 1 + arr[index], item = item, arr[index] + + # Rotating the remaining cycle. + while index != cur: + + # Finding where to put the item. + index = cur + for i in range(cur + 1, len_arr): + if arr[i] < item: + index += 1 + + # After item is duplicated, put it in place or put it there. + while item == arr[index]: + index += 1 + arr[index], item = item, arr[index] + return arr diff --git a/algorithms/sort/exchange_sort.py b/algorithms/sort/exchange_sort.py new file mode 100644 index 000000000..c2d2e7923 --- /dev/null +++ b/algorithms/sort/exchange_sort.py @@ -0,0 +1,11 @@ +def exchange_sort(arr): + """ + Reference : https://en.wikipedia.org/wiki/Sorting_algorithm#Exchange_sort + Complexity : O(n^2) + """ + arr_len = len(arr) + for i in range(arr_len-1): + for j in range(i+1, arr_len): + if(arr[i] > arr[j]): + arr[i], arr[j] = arr[j], arr[i] + return arr diff --git a/algorithms/sort/gnome_sort.py b/algorithms/sort/gnome_sort.py new file mode 100644 index 000000000..aaf38a278 --- /dev/null +++ b/algorithms/sort/gnome_sort.py @@ -0,0 +1,19 @@ +""" + +Gnome Sort +Best case performance is O(n) +Worst case performance is O(n^2) + +""" + + +def gnome_sort(arr): + n = len(arr) + index = 0 + while index < n: + if index == 0 or arr[index] >= arr[index-1]: + index = index + 1 + else: + arr[index], arr[index-1] = arr[index-1], arr[index] + index = index - 1 + return arr diff --git a/algorithms/sort/heap_sort.py b/algorithms/sort/heap_sort.py new file mode 100644 index 000000000..f07d4bf61 --- /dev/null +++ b/algorithms/sort/heap_sort.py @@ -0,0 +1,92 @@ +def max_heap_sort(arr, simulation=False): + """ Heap Sort that uses a max heap to sort an array in ascending order + Complexity: O(n log(n)) + """ + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + + for i in range(len(arr) - 1, 0, -1): + iteration = max_heapify(arr, i, simulation, iteration) + + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + return arr + + +def max_heapify(arr, end, simulation, iteration): + """ Max heapify helper for max_heap_sort + """ + last_parent = (end - 1) // 2 + + # Iterate from last parent to first + for parent in range(last_parent, -1, -1): + current_parent = parent + + # Iterate from current_parent to last_parent + while current_parent <= last_parent: + # Find greatest child of current_parent + child = 2 * current_parent + 1 + if child + 1 <= end and arr[child] < arr[child + 1]: + child = child + 1 + + # Swap if child is greater than parent + if arr[child] > arr[current_parent]: + arr[current_parent], arr[child] = arr[child], arr[current_parent] + current_parent = child + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + # If no swap occurred, no need to keep iterating + else: + break + arr[0], arr[end] = arr[end], arr[0] + return iteration + +def min_heap_sort(arr, simulation=False): + """ Heap Sort that uses a min heap to sort an array in ascending order + Complexity: O(n log(n)) + """ + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + + for i in range(0, len(arr) - 1): + iteration = min_heapify(arr, i, simulation, iteration) + + return arr + + +def min_heapify(arr, start, simulation, iteration): + """ Min heapify helper for min_heap_sort + """ + # Offset last_parent by the start (last_parent calculated as if start index was 0) + # All array accesses need to be offset by start + end = len(arr) - 1 + last_parent = (end - start - 1) // 2 + + # Iterate from last parent to first + for parent in range(last_parent, -1, -1): + current_parent = parent + + # Iterate from current_parent to last_parent + while current_parent <= last_parent: + # Find lesser child of current_parent + child = 2 * current_parent + 1 + if child + 1 <= end - start and arr[child + start] > arr[ + child + 1 + start]: + child = child + 1 + + # Swap if child is less than parent + if arr[child + start] < arr[current_parent + start]: + arr[current_parent + start], arr[child + start] = \ + arr[child + start], arr[current_parent + start] + current_parent = child + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + # If no swap occurred, no need to keep iterating + else: + break + return iteration diff --git a/algorithms/sort/insertion_sort.py b/algorithms/sort/insertion_sort.py new file mode 100644 index 000000000..06c86228d --- /dev/null +++ b/algorithms/sort/insertion_sort.py @@ -0,0 +1,25 @@ +def insertion_sort(arr, simulation=False): + """ Insertion Sort + Complexity: O(n^2) + """ + + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + + for i in range(len(arr)): + cursor = arr[i] + pos = i + + while pos > 0 and arr[pos - 1] > cursor: + # Swap the number down the list + arr[pos] = arr[pos - 1] + pos = pos - 1 + # Break and do the final swap + arr[pos] = cursor + + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + + return arr diff --git a/sort/meeting_rooms.py b/algorithms/sort/meeting_rooms.py similarity index 89% rename from sort/meeting_rooms.py rename to algorithms/sort/meeting_rooms.py index 72ebb7d51..2cb60c69a 100644 --- a/sort/meeting_rooms.py +++ b/algorithms/sort/meeting_rooms.py @@ -16,6 +16,6 @@ def can_attend_meetings(intervals): """ intervals = sorted(intervals, key=lambda x: x.start) for i in range(1, len(intervals)): - if intervals[i].start < intervals[i-1].end: + if intervals[i].start < intervals[i - 1].end: return False return True diff --git a/algorithms/sort/merge_sort.py b/algorithms/sort/merge_sort.py new file mode 100644 index 000000000..e2d7f05b9 --- /dev/null +++ b/algorithms/sort/merge_sort.py @@ -0,0 +1,40 @@ +def merge_sort(arr): + """ Merge Sort + Complexity: O(n log(n)) + """ + # Our recursive base case + if len(arr) <= 1: + return arr + mid = len(arr) // 2 + # Perform merge_sort recursively on both halves + left, right = merge_sort(arr[:mid]), merge_sort(arr[mid:]) + + # Merge each side together + # return merge(left, right, arr.copy()) # changed, no need to copy, mutate inplace. + merge(left,right,arr) + return arr + + +def merge(left, right, merged): + """ Merge helper + Complexity: O(n) + """ + + left_cursor, right_cursor = 0, 0 + while left_cursor < len(left) and right_cursor < len(right): + # Sort each one and place into the result + 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 + # Add the left overs if there's any left to the result + for left_cursor in range(left_cursor, len(left)): + merged[left_cursor + right_cursor] = left[left_cursor] + # Add the left overs if there's any left to the result + for right_cursor in range(right_cursor, len(right)): + merged[left_cursor + right_cursor] = right[right_cursor] + + # Return result + # return merged # do not return anything, as it is replacing inplace. diff --git a/algorithms/sort/pancake_sort.py b/algorithms/sort/pancake_sort.py new file mode 100644 index 000000000..19725c043 --- /dev/null +++ b/algorithms/sort/pancake_sort.py @@ -0,0 +1,25 @@ +def pancake_sort(arr): + """ + Pancake_sort + Sorting a given array + mutation of selection sort + + reference: https://www.geeksforgeeks.org/pancake-sorting/ + + Overall time complexity : O(N^2) + """ + + len_arr = len(arr) + if len_arr <= 1: + return arr + for cur in range(len(arr), 1, -1): + #Finding index of maximum number in arr + index_max = arr.index(max(arr[0:cur])) + if index_max+1 != cur: + #Needs moving + if index_max != 0: + #reverse from 0 to index_max + arr[:index_max+1] = reversed(arr[:index_max+1]) + # Reverse list + arr[:cur] = reversed(arr[:cur]) + return arr diff --git a/algorithms/sort/pigeonhole_sort.py b/algorithms/sort/pigeonhole_sort.py new file mode 100644 index 000000000..0569aa994 --- /dev/null +++ b/algorithms/sort/pigeonhole_sort.py @@ -0,0 +1,28 @@ +""" + +https://en.wikipedia.org/wiki/Pigeonhole_sort + +Time complexity: O(n + Range) where n = number of elements and Range = possible values in the array + +Suitable for lists where the number of elements and key values are mostly the same. + +""" + + +def pigeonhole_sort(arr): + Max = max(arr) + Min = min(arr) + size = Max - Min + 1 + + holes = [0]*size + + for i in arr: + holes[i-Min] += 1 + + i = 0 + for count in range(size): + while holes[count] > 0: + holes[count] -= 1 + arr[i] = count + Min + i += 1 + return arr diff --git a/algorithms/sort/quick_sort.py b/algorithms/sort/quick_sort.py new file mode 100644 index 000000000..10794e7a5 --- /dev/null +++ b/algorithms/sort/quick_sort.py @@ -0,0 +1,32 @@ +def quick_sort(arr, simulation=False): + """ Quick sort + Complexity: best O(n log(n)) avg O(n log(n)), worst O(N^2) + """ + + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + arr, _ = quick_sort_recur(arr, 0, len(arr) - 1, iteration, simulation) + return arr + +def quick_sort_recur(arr, first, last, iteration, simulation): + if first < last: + pos = partition(arr, first, last) + # Start our two recursive calls + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + + _, iteration = quick_sort_recur(arr, first, pos - 1, iteration, simulation) + _, iteration = quick_sort_recur(arr, pos + 1, last, iteration, simulation) + + return arr, iteration + +def partition(arr, first, last): + wall = first + for pos in range(first, last): + if arr[pos] < arr[last]: # last is the pivot + arr[pos], arr[wall] = arr[wall], arr[pos] + wall += 1 + arr[wall], arr[last] = arr[last], arr[wall] + return wall diff --git a/algorithms/sort/radix_sort.py b/algorithms/sort/radix_sort.py new file mode 100644 index 000000000..52457063c --- /dev/null +++ b/algorithms/sort/radix_sort.py @@ -0,0 +1,32 @@ +""" +radix sort +complexity: O(nk + n) . n is the size of input list and k is the digit length of the number +""" +def radix_sort(arr, simulation=False): + position = 1 + max_number = max(arr) + + iteration = 0 + if simulation: + print("iteration", iteration, ":", *arr) + + while position <= max_number: + queue_list = [list() for _ in range(10)] + + for num in arr: + digit_number = num // position % 10 + queue_list[digit_number].append(num) + + index = 0 + for numbers in queue_list: + for num in numbers: + arr[index] = num + index += 1 + + if simulation: + iteration = iteration + 1 + print("iteration", iteration, ":", *arr) + + position *= 10 + return arr + \ No newline at end of file diff --git a/algorithms/sort/selection_sort.py b/algorithms/sort/selection_sort.py new file mode 100644 index 000000000..f6d535225 --- /dev/null +++ b/algorithms/sort/selection_sort.py @@ -0,0 +1,23 @@ +def selection_sort(arr, simulation=False): + """ Selection Sort + Complexity: O(n^2) + """ + iteration = 0 + if simulation: + print("iteration",iteration,":",*arr) + + for i in range(len(arr)): + minimum = i + + for j in range(i + 1, len(arr)): + # "Select" the correct value + if arr[j] < arr[minimum]: + minimum = j + + arr[minimum], arr[i] = arr[i], arr[minimum] + + if simulation: + iteration = iteration + 1 + print("iteration",iteration,":",*arr) + + return arr diff --git a/algorithms/sort/shell_sort.py b/algorithms/sort/shell_sort.py new file mode 100644 index 000000000..61f5f612e --- /dev/null +++ b/algorithms/sort/shell_sort.py @@ -0,0 +1,21 @@ +def shell_sort(arr): + ''' Shell Sort + Complexity: O(n^2) + ''' + n = len(arr) + # Initialize size of the gap + gap = n//2 + + while gap > 0: + y_index = gap + while y_index < len(arr): + y = arr[y_index] + x_index = y_index - gap + while x_index >= 0 and y < arr[x_index]: + arr[x_index + gap] = arr[x_index] + x_index = x_index - gap + arr[x_index + gap] = y + y_index = y_index + 1 + gap = gap//2 + + return arr diff --git a/sort/sort_colors.py b/algorithms/sort/sort_colors.py similarity index 89% rename from sort/sort_colors.py rename to algorithms/sort/sort_colors.py index e85879781..aa44e221e 100644 --- a/sort/sort_colors.py +++ b/algorithms/sort/sort_colors.py @@ -25,6 +25,6 @@ def sort_colors(nums): if __name__ == "__main__": - nums = [0,1,1,1,2,2,2,1,1,1,0,0,0,0,1,1,1,0,0,2,2] + nums = [0, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2, 2] sort_colors(nums) print(nums) diff --git a/algorithms/sort/stooge_sort.py b/algorithms/sort/stooge_sort.py new file mode 100644 index 000000000..42f8b3bd4 --- /dev/null +++ b/algorithms/sort/stooge_sort.py @@ -0,0 +1,43 @@ +''' + +Stooge Sort +Time Complexity : O(n2.709) +Reference: https://www.geeksforgeeks.org/stooge-sort/ + +''' + + + +def stoogesort(arr, l, h): + if l >= h: + return + + # If first element is smaller + # than last, swap them + if arr[l]>arr[h]: + t = arr[l] + arr[l] = arr[h] + arr[h] = t + + # If there are more than 2 elements in + # the array + if h-l + 1 > 2: + t = (int)((h-l + 1)/3) + + # Recursively sort first 2 / 3 elements + stoogesort(arr, l, (h-t)) + + # Recursively sort last 2 / 3 elements + stoogesort(arr, l + t, (h)) + + # Recursively sort first 2 / 3 elements + # again to confirm + stoogesort(arr, l, (h-t)) + + +if __name__ == "__main__": + array = [1,3,64,5,7,8] + n = len(array) + stoogesort(array, 0, n-1) + for i in range(0, n): + print(array[i], end = ' ') diff --git a/algorithms/sort/top_sort.py b/algorithms/sort/top_sort.py new file mode 100644 index 000000000..b9188bb28 --- /dev/null +++ b/algorithms/sort/top_sort.py @@ -0,0 +1,66 @@ +GRAY, BLACK = 0, 1 + +def top_sort_recursive(graph): + """ Time complexity is the same as DFS, which is O(V + E) + Space complexity: O(V) + """ + order, enter, state = [], set(graph), {} + + def dfs(node): + state[node] = GRAY + #print(node) + for k in graph.get(node, ()): + sk = state.get(k, None) + if sk == GRAY: + raise ValueError("cycle") + if sk == BLACK: + continue + enter.discard(k) + dfs(k) + order.append(node) + state[node] = BLACK + + while enter: dfs(enter.pop()) + return order + +def top_sort(graph): + """ Time complexity is the same as DFS, which is O(V + E) + Space complexity: O(V) + """ + order, enter, state = [], set(graph), {} + + def is_ready(node): + lst = graph.get(node, ()) + if len(lst) == 0: + return True + for k in lst: + sk = state.get(k, None) + if sk == GRAY: + raise ValueError("cycle") + if sk != BLACK: + return False + return True + + while enter: + node = enter.pop() + stack = [] + while True: + state[node] = GRAY + stack.append(node) + for k in graph.get(node, ()): + sk = state.get(k, None) + if sk == GRAY: + raise ValueError("cycle") + if sk == BLACK: + continue + enter.discard(k) + stack.append(k) + 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 diff --git a/sort/wiggle_sort.py b/algorithms/sort/wiggle_sort.py similarity index 73% rename from sort/wiggle_sort.py rename to algorithms/sort/wiggle_sort.py index 2e09584c3..15dc76d4b 100644 --- a/sort/wiggle_sort.py +++ b/algorithms/sort/wiggle_sort.py @@ -1,4 +1,6 @@ - +""" +Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3].... +""" def wiggle_sort(nums): for i in range(len(nums)): if (i % 2 == 1) == (nums[i-1] > nums[i]): diff --git a/algorithms/stack/__init__.py b/algorithms/stack/__init__.py new file mode 100644 index 000000000..4e8d0603f --- /dev/null +++ b/algorithms/stack/__init__.py @@ -0,0 +1,10 @@ +from .stack import * +from .is_consecutive import * +from .is_sorted import * +from .remove_min import * +from .stutter import * +from .switch_pairs import * +from .valid_parenthesis import * +from .simplify_path import * +from .stack import * +from .ordered_stack import * diff --git a/stack/is_consecutive.py b/algorithms/stack/is_consecutive.py similarity index 85% rename from stack/is_consecutive.py rename to algorithms/stack/is_consecutive.py index f73ddffc7..44e3f90f5 100644 --- a/stack/is_consecutive.py +++ b/algorithms/stack/is_consecutive.py @@ -18,14 +18,15 @@ """ import collections + def first_is_consecutive(stack): storage_stack = [] for i in range(len(stack)): first_value = stack.pop() - if len(stack) == 0: # Case odd number of values in stack + if len(stack) == 0: # Case odd number of values in stack return True second_value = stack.pop() - if first_value - second_value != 1: # Not consecutive + if first_value - second_value != 1: # Not consecutive return False stack.append(second_value) # Backup second value storage_stack.append(first_value) @@ -35,14 +36,15 @@ def first_is_consecutive(stack): stack.append(storage_stack.pop()) return True + def second_is_consecutive(stack): q = collections.deque() for i in range(len(stack)): first_value = stack.pop() - if len(stack) == 0: # Case odd number of values in stack + if len(stack) == 0: # Case odd number of values in stack return True second_value = stack.pop() - if first_value - second_value != 1: # Not consecutive + if first_value - second_value != 1: # Not consecutive return False stack.append(second_value) # Backup second value q.append(first_value) diff --git a/stack/is_sorted.py b/algorithms/stack/is_sorted.py similarity index 89% rename from stack/is_sorted.py rename to algorithms/stack/is_sorted.py index 8824dfa95..b3c3337e5 100644 --- a/stack/is_sorted.py +++ b/algorithms/stack/is_sorted.py @@ -9,19 +9,21 @@ bottom [1, 2, 3, 4, 5, 6] top The function should return true """ + + def is_sorted(stack): storage_stack = [] for i in range(len(stack)): if len(stack) == 0: - return True + break first_val = stack.pop() if len(stack) == 0: - return True + break second_val = stack.pop() if first_val < second_val: return False storage_stack.append(first_val) - storage_stack.append(second_val) + stack.append(second_val) # Backup stack for i in range(len(storage_stack)): diff --git a/algorithms/stack/longest_abs_path.py b/algorithms/stack/longest_abs_path.py new file mode 100644 index 000000000..edc4ee8b0 --- /dev/null +++ b/algorithms/stack/longest_abs_path.py @@ -0,0 +1,65 @@ +# def lengthLongestPath(input): +# maxlen = 0 +# pathlen = {0: 0} +# for line in input.splitlines(): +# print("---------------") +# print("line:", line) +# name = line.strip('\t') +# print("name:", name) +# depth = len(line) - len(name) +# print("depth:", depth) +# if '.' in name: +# maxlen = max(maxlen, pathlen[depth] + len(name)) +# else: +# pathlen[depth + 1] = pathlen[depth] + len(name) + 1 +# print("maxlen:", maxlen) +# return maxlen + +# def lengthLongestPath(input): +# paths = input.split("\n") +# level = [0] * 10 +# maxLength = 0 +# for path in paths: +# print("-------------") +# levelIdx = path.rfind("\t") +# print("Path: ", path) +# print("path.rfind(\\t)", path.rfind("\t")) +# print("levelIdx: ", levelIdx) +# print("level: ", level) +# level[levelIdx + 1] = level[levelIdx] + len(path) - levelIdx + 1 +# print("level: ", level) +# if "." in path: +# maxLength = max(maxLength, level[levelIdx+1] - 1) +# print("maxlen: ", maxLength) +# return maxLength + +def length_longest_path(input): + """ + :type input: str + :rtype: int + """ + curr_len, max_len = 0, 0 # running length and max length + stack = [] # keep track of the name length + for s in input.split('\n'): + print("---------") + print(":", s) + depth = s.count('\t') # the depth of current dir or file + print("depth: ", depth) + print("stack: ", stack) + print("curlen: ", curr_len) + while len(stack) > depth: # go back to the correct depth + curr_len -= stack.pop() + stack.append(len(s.strip('\t'))+1) # 1 is the length of '/' + curr_len += stack[-1] # increase current length + print("stack: ", stack) + print("curlen: ", curr_len) + if '.' in s: # update maxlen only when it is a file + max_len = max(max_len, curr_len-1) # -1 is to minus one '/' + return max_len + + +st = "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdirectory1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext" +st2 = "a\n\tb1\n\t\tf1.txt\n\taaaaa\n\t\tf2.txt" +print("path:", st2) + +print("answer:", length_longest_path(st2)) diff --git a/algorithms/stack/ordered_stack.py b/algorithms/stack/ordered_stack.py new file mode 100644 index 000000000..94191d6cf --- /dev/null +++ b/algorithms/stack/ordered_stack.py @@ -0,0 +1,36 @@ +# The stack remains always ordered such that the highest value +# is at the top and the lowest at the bottom + + +class OrderedStack: + def __init__(self): + self.items = [] + + def is_empty(self): + return self.items == [] + + def push_t(self, item): + self.items.append(item) + + # push method to maintain order when pushing new elements + def push(self, item): + temp_stack = OrderedStack() + if self.is_empty() or item > self.peek(): + self.push_t(item) + else: + while item < self.peek() and not self.is_empty(): + temp_stack.push_t(self.pop()) + self.push_t(item) + while not temp_stack.is_empty(): + self.push_t(temp_stack.pop()) + + def pop(self): + if self.is_empty(): + raise IndexError("Stack is empty") + return self.items.pop() + + def peek(self): + return self.items[len(self.items) - 1] + + def size(self): + return len(self.items) diff --git a/stack/remove_min.py b/algorithms/stack/remove_min.py similarity index 99% rename from stack/remove_min.py rename to algorithms/stack/remove_min.py index 1cd8fc6ae..ce635a4ab 100644 --- a/stack/remove_min.py +++ b/algorithms/stack/remove_min.py @@ -8,6 +8,8 @@ bottom [2, 8, 3, 7, 3] top """ + + def remove_min(stack): storage_stack = [] if len(stack) == 0: # Stack is empty diff --git a/stack/simplify_path.py b/algorithms/stack/simplify_path.py similarity index 84% rename from stack/simplify_path.py rename to algorithms/stack/simplify_path.py index fed4385a6..09f9de826 100644 --- a/stack/simplify_path.py +++ b/algorithms/stack/simplify_path.py @@ -7,8 +7,9 @@ * Did you consider the case where path = "/../"? In this case, you should return "/". -* Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/". - In this case, you should ignore redundant slashes and return "/home/foo". +* Another corner case is the path might contain multiple slashes '/' together, + such as "/home//foo/". In this case, you should ignore redundant + slashes and return "/home/foo". """ def simplify_path(path): """ diff --git a/stack/stack.py b/algorithms/stack/stack.py similarity index 92% rename from stack/stack.py rename to algorithms/stack/stack.py index 216c26492..a79aec490 100644 --- a/stack/stack.py +++ b/algorithms/stack/stack.py @@ -8,12 +8,12 @@ It needs no parameters and returns the item. The stack is modified. peek() returns the top item from the stack but does not remove it. It needs no parameters. The stack is not modified. -isEmpty() tests to see whether the stack is empty. +is_empty() tests to see whether the stack is empty. It needs no parameters and returns a boolean value. -size() returns the number of items on the stack. - It needs no parameters and returns an integer. """ from abc import ABCMeta, abstractmethod + + class AbstractStack(metaclass=ABCMeta): """Abstract Class for Stacks.""" def __init__(self): @@ -72,7 +72,7 @@ def push(self, value): def pop(self): if self.is_empty(): - raise IndexError("stack is empty") + raise IndexError("Stack is empty") value = self._array[self._top] self._top -= 1 return value @@ -80,7 +80,7 @@ def pop(self): def peek(self): """returns the current top element of the stack.""" if self.is_empty(): - raise IndexError("stack is empty") + raise IndexError("Stack is empty") return self._array[self._top] def _expand(self): diff --git a/stack/stutter.py b/algorithms/stack/stutter.py similarity index 99% rename from stack/stutter.py rename to algorithms/stack/stutter.py index 30314992c..ceb6451d4 100644 --- a/stack/stutter.py +++ b/algorithms/stack/stutter.py @@ -13,6 +13,7 @@ """ import collections + def first_stutter(stack): storage_stack = [] for i in range(len(stack)): @@ -24,6 +25,7 @@ def first_stutter(stack): return stack + def second_stutter(stack): q = collections.deque() # Put all values into queue from stack diff --git a/stack/switch_pairs.py b/algorithms/stack/switch_pairs.py similarity index 92% rename from stack/switch_pairs.py rename to algorithms/stack/switch_pairs.py index f5db2280d..d8b22dc08 100644 --- a/stack/switch_pairs.py +++ b/algorithms/stack/switch_pairs.py @@ -4,13 +4,15 @@ For example, if the stack initially stores these values: bottom [3, 8, 17, 9, 1, 10] top -Your function should switch the first pair (3, 8), the second pair (17, 9), ...: +Your function should switch the first pair (3, 8), +the second pair (17, 9), ...: bottom [8, 3, 9, 17, 10, 1] top if there are an odd number of values in the stack, the value at the top of the stack is not moved: For example: bottom [3, 8, 17, 9, 1] top -It would again switch pairs of values, but the value at the top of the stack (1) +It would again switch pairs of values, but the value at the +top of the stack (1) would not be moved bottom [8, 3, 9, 17, 1] top @@ -20,6 +22,7 @@ """ import collections + def first_switch_pairs(stack): storage_stack = [] for i in range(len(stack)): @@ -36,6 +39,7 @@ def first_switch_pairs(stack): stack.append(first) return stack + def second_switch_pairs(stack): q = collections.deque() # Put all values into queue from stack diff --git a/stack/valid_parenthesis.py b/algorithms/stack/valid_parenthesis.py similarity index 99% rename from stack/valid_parenthesis.py rename to algorithms/stack/valid_parenthesis.py index b62ac02c4..8fdb861b5 100644 --- a/stack/valid_parenthesis.py +++ b/algorithms/stack/valid_parenthesis.py @@ -6,6 +6,8 @@ The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "([)]" are not. """ + + def is_valid(s: str) -> bool: stack = [] dic = {")": "(", diff --git a/algorithms/streaming/__init__.py b/algorithms/streaming/__init__.py new file mode 100644 index 000000000..3e764d2c3 --- /dev/null +++ b/algorithms/streaming/__init__.py @@ -0,0 +1,2 @@ +from .one_sparse_recovery import * +from .misra_gries import * diff --git a/algorithms/streaming/misra_gries.py b/algorithms/streaming/misra_gries.py new file mode 100644 index 000000000..58fd84b5e --- /dev/null +++ b/algorithms/streaming/misra_gries.py @@ -0,0 +1,60 @@ + +""" +Implementation of the Misra-Gries algorithm. +Given a list of items and a value k, it returns the every item in the list +that appears at least n/k times, where n is the length of the array + +By default, k is set to 2, solving the majority problem. + +For the majority problem, this algorithm only guarantees that if there is +an element that appears more than n/2 times, it will be outputed. If there +is no such element, any arbitrary element is returned by the algorithm. +Therefore, we need to iterate through again at the end. But since we have filtred +out the suspects, the memory complexity is significantly lower than +it would be to create counter for every element in the list. + +For example: +Input misras_gries([1,4,4,4,5,4,4]) +Output {'4':5} +Input misras_gries([0,0,0,1,1,1,1]) +Output {'1':4} +Input misras_gries([0,0,0,0,1,1,1,2,2],3) +Output {'0':4,'1':3} +Input misras_gries([0,0,0,1,1,1] +Output None +""" + +def misras_gries(array,k=2): + """Misra-Gries algorithm + + Keyword arguments: + array -- list of integers + k -- value of k (default 2) + """ + keys = {} + for i in array: + val = str(i) + if val in keys: + keys[val] = keys[val] + 1 + + elif len(keys) < k - 1: + keys[val] = 1 + + else: + for key in list(keys): + keys[key] = keys[key] - 1 + if keys[key] == 0: + del keys[key] + + suspects = keys.keys() + frequencies = {} + for suspect in suspects: + freq = _count_frequency(array,int(suspect)) + if freq >= len(array) / k: + frequencies[suspect] = freq + + return frequencies if len(frequencies) > 0 else None + + +def _count_frequency(array,element): + return array.count(element) diff --git a/algorithms/streaming/one_sparse_recovery.py b/algorithms/streaming/one_sparse_recovery.py new file mode 100644 index 000000000..18a26415e --- /dev/null +++ b/algorithms/streaming/one_sparse_recovery.py @@ -0,0 +1,68 @@ +""" +Non-negative 1-sparse recovery problem. +This algorithm assumes we have a non negative dynamic stream. + +Given a stream of tuples, where each tuple contains a number and a sign (+/-), it check if the +stream is 1-sparse, meaning if the elements in the stream cancel eacheother out in such +a way that ther is only a unique number at the end. + +Examples: +#1 +Input: [(4,'+'), (2,'+'),(2,'-'),(4,'+'),(3,'+'),(3,'-')], +Output: 4 +Comment: Since 2 and 3 gets removed. +#2 +Input: [(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+')] +Output: 2 +Comment: No other numbers present +#3 +Input: [(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(1,'+')] +Output: None +Comment: Not 1-sparse +""" + +def one_sparse(array): + """1-sparse algorithm + + Keyword arguments: + array -- stream of tuples + """ + sum_signs = 0 + bitsum = [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 + + _get_bit_sum(bitsum,val,sign) + + if sum_signs > 0 and _check_every_number_in_bitsum(bitsum,sum_signs): + return int(sum_values/sum_signs) + else: + return None + +#Helper function to check that every entry in the list is either 0 or the same as the +#sum of signs +def _check_every_number_in_bitsum(bitsum,sum_signs): + for val in bitsum: + if val != 0 and val != sum_signs : + return False + return True + +# Adds bit representation value to bitsum array +def _get_bit_sum(bitsum,val,sign): + i = 0 + if sign == "+": + while val: + bitsum[i] += val & 1 + i +=1 + val >>=1 + else : + while val: + bitsum[i] -= val & 1 + i +=1 + val >>=1 diff --git a/algorithms/strings/__init__.py b/algorithms/strings/__init__.py new file mode 100644 index 000000000..496b1ebe5 --- /dev/null +++ b/algorithms/strings/__init__.py @@ -0,0 +1,41 @@ +from .add_binary import * +from .breaking_bad import * +from .decode_string import * +from .delete_reoccurring import * +from .domain_extractor import * +from .encode_decode import * +from .group_anagrams import * +from .int_to_roman import * +from .is_palindrome import * +from .is_rotated import * +from .license_number import * +from .make_sentence import * +from .merge_string_checker import * +from .multiply_strings import * +from .one_edit_distance import * +from .rabin_karp import * +from .reverse_string import * +from .reverse_vowel import * +from .reverse_words import * +from .roman_to_int import * +from .strip_url_params import * +from .validate_coordinates import * +from .word_squares import * +from .unique_morse import * +from .judge_circle import * +from .strong_password import * +from .caesar_cipher import * +from .check_pangram import * +from .contain_string import * +from .count_binary_substring import * +from .repeat_string import * +from .text_justification import * +from .min_distance import * +from .longest_common_prefix import * +from .rotate import * +from .first_unique_char import * +from .repeat_substring import * +from .atbash_cipher import * +from .longest_palindromic_substring import * +from .knuth_morris_pratt import * +from .panagram import * \ No newline at end of file diff --git a/strings/add_binary.py b/algorithms/strings/add_binary.py similarity index 100% rename from strings/add_binary.py rename to algorithms/strings/add_binary.py diff --git a/algorithms/strings/atbash_cipher.py b/algorithms/strings/atbash_cipher.py new file mode 100644 index 000000000..59bf777db --- /dev/null +++ b/algorithms/strings/atbash_cipher.py @@ -0,0 +1,27 @@ +""" +Atbash cipher is mapping the alphabet to it's reverse. +So if we take "a" as it is the first letter, we change it to the last - z. + +Example: +Attack at dawn --> Zggzxp zg wzdm + +Complexity: O(n) +""" + +def atbash(s): + translated = "" + for i in range(len(s)): + n = ord(s[i]) + + if s[i].isalpha(): + + if s[i].isupper(): + x = n - ord('A') + translated += chr(ord('Z') - x) + + if s[i].islower(): + x = n - ord('a') + translated += chr(ord('z') - x) + else: + translated += s[i] + return translated \ No newline at end of file diff --git a/strings/breaking_bad.py b/algorithms/strings/breaking_bad.py similarity index 100% rename from strings/breaking_bad.py rename to algorithms/strings/breaking_bad.py diff --git a/algorithms/strings/caesar_cipher.py b/algorithms/strings/caesar_cipher.py new file mode 100644 index 000000000..379f63e0d --- /dev/null +++ b/algorithms/strings/caesar_cipher.py @@ -0,0 +1,19 @@ + +""" +Julius Caesar protected his confidential information by encrypting it using a cipher. +Caesar's cipher shifts each letter by a number of letters. If the shift takes you +past the end of the alphabet, just rotate back to the front of the alphabet. +In the case of a rotation by 3, w, x, y and z would map to z, a, b and c. +Original alphabet: abcdefghijklmnopqrstuvwxyz +Alphabet rotated +3: defghijklmnopqrstuvwxyzabc +""" +def caesar_cipher(s, k): + result = "" + for char in s: + n = ord(char) + if 64 < n < 91: + n = ((n - 65 + k) % 26) + 65 + if 96 < n < 123: + n = ((n - 97 + k) % 26) + 97 + result = result + chr(n) + return result diff --git a/algorithms/strings/check_pangram.py b/algorithms/strings/check_pangram.py new file mode 100644 index 000000000..412425793 --- /dev/null +++ b/algorithms/strings/check_pangram.py @@ -0,0 +1,10 @@ +""" +Algorithm that checks if a given string is a pangram or not +""" + +def check_pangram(input_string): + alphabet = "abcdefghijklmnopqrstuvwxyz" + for ch in alphabet: + if ch not in input_string.lower(): + return False + return True \ No newline at end of file diff --git a/algorithms/strings/contain_string.py b/algorithms/strings/contain_string.py new file mode 100644 index 000000000..67056fed6 --- /dev/null +++ b/algorithms/strings/contain_string.py @@ -0,0 +1,25 @@ +""" +Implement strStr(). + +Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. + +Example 1: +Input: haystack = "hello", needle = "ll" +Output: 2 + +Example 2: +Input: haystack = "aaaaa", needle = "bba" +Output: -1 +Reference: https://leetcode.com/problems/implement-strstr/description/ +""" +def contain_string(haystack, needle): + if len(needle) == 0: + return 0 + if len(needle) > len(haystack): + return -1 + for i in range(len(haystack)): + if len(haystack) - i < len(needle): + return -1 + if haystack[i:i+len(needle)] == needle: + return i + return -1 diff --git a/algorithms/strings/count_binary_substring.py b/algorithms/strings/count_binary_substring.py new file mode 100644 index 000000000..bd775ee53 --- /dev/null +++ b/algorithms/strings/count_binary_substring.py @@ -0,0 +1,33 @@ +""" +Give a string s, count the number of non-empty (contiguous) substrings that have + the same number of 0's and 1's, and all the 0's and all the 1's in these substrings are grouped consecutively. + +Substrings that occur multiple times are counted the number of times they occur. +Example 1: +Input: "00110011" +Output: 6 +Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01". + +Notice that some of these substrings repeat and are counted the number of times they occur. + +Also, "00110011" is not a valid substring because all the 0's (and 1's) are not grouped together. + +Example 2: +Input: "10101" +Output: 4 +Explanation: There are 4 substrings: "10", "01", "10", "01" that have equal number of consecutive 1's and 0's. +Reference: https://leetcode.com/problems/count-binary-substrings/description/ +""" +def count_binary_substring(s): + cur = 1 + pre = 0 + count = 0 + for i in range(1, len(s)): + if s[i] != s[i - 1]: + count = count + min(pre, cur) + pre = cur + cur = 1 + else: + cur = cur + 1 + count = count + min(pre, cur) + return count diff --git a/strings/decode_string.py b/algorithms/strings/decode_string.py similarity index 100% rename from strings/decode_string.py rename to algorithms/strings/decode_string.py diff --git a/strings/delete_reoccurring.py b/algorithms/strings/delete_reoccurring.py similarity index 100% rename from strings/delete_reoccurring.py rename to algorithms/strings/delete_reoccurring.py diff --git a/strings/domain_extractor.py b/algorithms/strings/domain_extractor.py similarity index 100% rename from strings/domain_extractor.py rename to algorithms/strings/domain_extractor.py diff --git a/strings/encode_decode.py b/algorithms/strings/encode_decode.py similarity index 100% rename from strings/encode_decode.py rename to algorithms/strings/encode_decode.py diff --git a/algorithms/strings/first_unique_char.py b/algorithms/strings/first_unique_char.py new file mode 100644 index 000000000..44ba05e49 --- /dev/null +++ b/algorithms/strings/first_unique_char.py @@ -0,0 +1,27 @@ +""" +Given a string, find the first non-repeating character in it and return it's +index. If it doesn't exist, return -1. + +For example: +s = "leetcode" +return 0. + +s = "loveleetcode", +return 2. + +Reference: https://leetcode.com/problems/first-unique-character-in-a-string/description/ +""" +def first_unique_char(s): + """ + :type s: str + :rtype: int + """ + if (len(s) == 1): + return 0 + ban = [] + for i in range(len(s)): + if all(s[i] != s[k] for k in range(i + 1, len(s))) == True and s[i] not in ban: + return i + else: + ban.append(s[i]) + return -1 diff --git a/strings/fizzbuzz.py b/algorithms/strings/fizzbuzz.py similarity index 95% rename from strings/fizzbuzz.py rename to algorithms/strings/fizzbuzz.py index 09078563b..c811afa25 100644 --- a/strings/fizzbuzz.py +++ b/algorithms/strings/fizzbuzz.py @@ -1,5 +1,5 @@ """ -Wtite a function that returns an array containing the numbers from 1 to N, +Write a function that returns an array containing the numbers from 1 to N, where N is the parametered value. N will never be less than 1. Replace certain values however if any of the following conditions are met: diff --git a/strings/group_anagrams.py b/algorithms/strings/group_anagrams.py similarity index 100% rename from strings/group_anagrams.py rename to algorithms/strings/group_anagrams.py diff --git a/strings/int_to_roman.py b/algorithms/strings/int_to_roman.py similarity index 100% rename from strings/int_to_roman.py rename to algorithms/strings/int_to_roman.py diff --git a/strings/is_palindrome.py b/algorithms/strings/is_palindrome.py similarity index 82% rename from strings/is_palindrome.py rename to algorithms/strings/is_palindrome.py index 0318bf5f4..11a9e3063 100644 --- a/strings/is_palindrome.py +++ b/algorithms/strings/is_palindrome.py @@ -11,6 +11,7 @@ we define empty string as valid palindrome. """ from string import ascii_letters +from collections import deque def is_palindrome(s): @@ -21,9 +22,9 @@ def is_palindrome(s): i = 0 j = len(s)-1 while i < j: - while i < j and not s[i].isalnum(): + while not s[i].isalnum(): i += 1 - while i < j and not s[j].isalnum(): + while not s[j].isalnum(): j -= 1 if s[i].lower() != s[j].lower(): return False @@ -85,3 +86,20 @@ def is_palindrome_stack(s): if s[i] != stack.pop(): return False return True + +# Variation 4 (using deque) +def is_palindrome_deque(s): + s = remove_punctuation(s) + deq = deque() + for char in s: + deq.appendleft(char) + + equal = True + + while len(deq) > 1 and equal: + first = deq.pop() + last = deq.popleft() + if first != last : + equal = False + + return equal diff --git a/algorithms/strings/is_rotated.py b/algorithms/strings/is_rotated.py new file mode 100644 index 000000000..a827fb497 --- /dev/null +++ b/algorithms/strings/is_rotated.py @@ -0,0 +1,31 @@ +""" +Given two strings s1 and s2, determine if s2 is a rotated version of s1. +For example, +is_rotated("hello", "llohe") returns True +is_rotated("hello", "helol") returns False + +accepts two strings +returns bool +Reference: https://leetcode.com/problems/rotate-string/description/ +""" + +def is_rotated(s1, s2): + if len(s1) == len(s2): + return s2 in s1 + s1 + else: + return False + +""" +Another solution: brutal force +Complexity: O(N^2) +""" +def is_rotated_v1(s1, s2): + if len(s1) != len(s2): + return False + if len(s1) == 0: + return True + + for c in range(len(s1)): + if all(s1[(c + i) % len(s1)] == s2[i] for i in range(len(s1))): + return True + return False diff --git a/algorithms/strings/judge_circle.py b/algorithms/strings/judge_circle.py new file mode 100644 index 000000000..46468a1ad --- /dev/null +++ b/algorithms/strings/judge_circle.py @@ -0,0 +1,25 @@ +""" +Initially, there is a Robot at position (0, 0). Given a sequence of its moves, +judge if this robot makes a circle, which means it moves back to the original place. + +The move sequence is represented by a string. And each move is represent by a +character. The valid robot moves are R (Right), L (Left), U (Up) and D (down). +The output should be true or false representing whether the robot makes a circle. + +Example 1: +Input: "UD" +Output: true +Example 2: +Input: "LL" +Output: false +""" +def judge_circle(moves): + dict_moves = { + 'U' : 0, + 'D' : 0, + 'R' : 0, + 'L' : 0 + } + for char in moves: + dict_moves[char] = dict_moves[char] + 1 + return dict_moves['L'] == dict_moves['R'] and dict_moves['U'] == dict_moves['D'] diff --git a/algorithms/strings/knuth_morris_pratt.py b/algorithms/strings/knuth_morris_pratt.py new file mode 100644 index 000000000..e1953a218 --- /dev/null +++ b/algorithms/strings/knuth_morris_pratt.py @@ -0,0 +1,44 @@ +from typing import Sequence, List + +def knuth_morris_pratt(text : Sequence, pattern : Sequence) -> List[int]: + """ + Given two strings text and pattern, return the list of start indexes in text that matches with the pattern + using knuth_morris_pratt algorithm. + + Args: + text: Text to search + pattern: Pattern to search in the text + Returns: + List of indices of patterns found + + Example: + >>> knuth_morris_pratt('hello there hero!', 'he') + [0, 7, 12] + + If idx is in the list, text[idx : idx + M] matches with pattern. + Time complexity of the algorithm is O(N+M), with N and M the length of text and pattern, respectively. + """ + n = len(text) + m = len(pattern) + pi = [0 for i in range(m)] + i = 0 + j = 0 + # making pi table + for i in range(1, m): + while j and pattern[i] != pattern[j]: + j = pi[j - 1] + if pattern[i] == pattern[j]: + j += 1 + pi[i] = j + # finding pattern + j = 0 + ret = [] + for i in range(n): + while j and text[i] != pattern[j]: + j = pi[j - 1] + if text[i] == pattern[j]: + j += 1 + if j == m: + ret.append(i - m + 1) + j = pi[j - 1] + return ret diff --git a/strings/license_number.py b/algorithms/strings/license_number.py similarity index 100% rename from strings/license_number.py rename to algorithms/strings/license_number.py diff --git a/algorithms/strings/longest_common_prefix.py b/algorithms/strings/longest_common_prefix.py new file mode 100644 index 000000000..80facc934 --- /dev/null +++ b/algorithms/strings/longest_common_prefix.py @@ -0,0 +1,66 @@ +""" +Write a function to find the longest common prefix string amongst an array of strings. + +If there is no common prefix, return an empty string "". + +Example 1: +Input: ["flower","flow","flight"] +Output: "fl" + +Example 2: +Input: ["dog","racecar","car"] +Output: "" +Explanation: There is no common prefix among the input strings. + +Reference: https://leetcode.com/problems/longest-common-prefix/description/ +""" + +""" +First solution: Horizontal scanning +""" +def common_prefix(s1, s2): + "Return prefix common of 2 strings" + if not s1 or not s2: + return "" + k = 0 + while s1[k] == s2[k]: + k = k + 1 + if k >= len(s1) or k >= len(s2): + return s1[0:k] + return s1[0:k] + +def longest_common_prefix_v1(strs): + if not strs: + return "" + result = strs[0] + for i in range(len(strs)): + result = common_prefix(result, strs[i]) + return result + +""" +Second solution: Vertical scanning +""" +def longest_common_prefix_v2(strs): + if not strs: + return "" + for i in range(len(strs[0])): + for string in strs[1:]: + if i == len(string) or string[i] != strs[0][i]: + return strs[0][0:i] + return strs[0] + +""" +Third solution: Divide and Conquer +""" +def longest_common_prefix_v3(strs): + if not strs: + return "" + return longest_common(strs, 0, len(strs) -1) + +def longest_common(strs, left, right): + if left == right: + return strs[left] + mid = (left + right) // 2 + lcp_left = longest_common(strs, left, mid) + lcp_right = longest_common(strs, mid + 1, right) + return common_prefix(lcp_left, lcp_right) diff --git a/algorithms/strings/longest_palindromic_substring.py b/algorithms/strings/longest_palindromic_substring.py new file mode 100644 index 000000000..ad0c831b9 --- /dev/null +++ b/algorithms/strings/longest_palindromic_substring.py @@ -0,0 +1,44 @@ +''' +Given string s, find the longest palindromic substring. + +Example1: + +* input: "dasdasdasdasdasdadsa" +* output: "asdadsa" + +Example2: + +* input: "acdbbdaa" +* output: "dbbd" + +Manacher's algorithm + +''' + +def longest_palindrome(s): + if len(s) < 2: + return s + + n_str = '#' + '#'.join(s) + '#' + p = [0] * len(n_str) + mx, loc = 0, 0 + index, maxlen = 0, 0 + for i in range(len(n_str)): + if i < mx and 2 * loc - i < len(n_str): + p[i] = min(mx - i, p[2 * loc - i]) + else: + p[i] = 1 + + while p[i] + i < len(n_str) and i - p[i] >= 0 and n_str[ + i - p[i]] == n_str[i + p[i]]: + p[i] += 1 + + if i + p[i] > mx: + mx = i + p[i] + loc = i + + if p[i] > maxlen: + index = i + maxlen = p[i] + s = n_str[index - p[index] + 1:index + p[index]] + return s.replace('#', '') diff --git a/strings/make_sentence.py b/algorithms/strings/make_sentence.py similarity index 78% rename from strings/make_sentence.py rename to algorithms/strings/make_sentence.py index 8e6cb5b3b..2b7bcf79f 100644 --- a/strings/make_sentence.py +++ b/algorithms/strings/make_sentence.py @@ -15,13 +15,13 @@ count = 0 -def make_sentence(str_piece, dictionarys): +def make_sentence(str_piece, dictionaries): global count if len(str_piece) == 0: return True for i in range(0, len(str_piece)): prefix, suffix = str_piece[0:i], str_piece[i:] - if prefix in dictionarys: - if suffix in dictionarys or make_sentence(suffix, dictionarys): + if prefix in dictionaries: + if suffix in dictionaries or make_sentence(suffix, dictionaries): count += 1 return True diff --git a/strings/merge_string_checker.py b/algorithms/strings/merge_string_checker.py similarity index 100% rename from strings/merge_string_checker.py rename to algorithms/strings/merge_string_checker.py diff --git a/algorithms/strings/min_distance.py b/algorithms/strings/min_distance.py new file mode 100644 index 000000000..b63f5db2a --- /dev/null +++ b/algorithms/strings/min_distance.py @@ -0,0 +1,62 @@ +""" +Given two words word1 and word2, find the minimum number of steps required to +make word1 and word2 the same, where in each step you can delete one character +in either string. + +For example: +Input: "sea", "eat" +Output: 2 +Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea". + +Reference: https://leetcode.com/problems/delete-operation-for-two-strings/description/ +""" + +def min_distance(word1, word2): + """ + Finds minimum distance by getting longest common subsequence + + :type word1: str + :type word2: str + :rtype: int + """ + return len(word1) + len(word2) - 2 * lcs(word1, word2, len(word1), len(word2)) + +def lcs(word1, word2, i, j): + """ + The length of longest common subsequence among the two given strings word1 and word2 + """ + if i == 0 or j == 0: + return 0 + if word1[i - 1] == word2[j - 1]: + return 1 + lcs(word1, word2, i - 1, j - 1) + return max(lcs(word1, word2, i - 1, j), lcs(word1, word2, i, j - 1)) + +def min_distance_dp(word1, word2): + """ + Finds minimum distance in a dynamic programming manner + TC: O(length1*length2), SC: O(length1*length2) + + :type word1: str + :type word2: str + :rtype: int + """ + length1, length2 = len(word1)+1, len(word2)+1 + res = [[0 for _ in range(length2)] for _ in range(length1)] + + if length1 == length2: + for i in range(1, length1): + res[i][0], res[0][i] = i, i + else: + for i in range(length1): + res[i][0] = i + for i in range(length2): + res[0][i] = i + + for i in range(1, length1): + for j in range(1, length2): + if word1[i-1] == word2[j-1]: + res[i][j] = res[i-1][j-1] + else: + res[i][j] = min(res[i-1][j], res[i][j-1]) + 1 + + return res[len(word1)][len(word2)] diff --git a/strings/multiply_strings.py b/algorithms/strings/multiply_strings.py similarity index 93% rename from strings/multiply_strings.py rename to algorithms/strings/multiply_strings.py index 0ddc3abf4..2cb0d2206 100644 --- a/strings/multiply_strings.py +++ b/algorithms/strings/multiply_strings.py @@ -12,8 +12,7 @@ """ -def multiply(num1:"str", num2:"str")->"str": - carry = 1 +def multiply(num1: "str", num2: "str") -> "str": interm = [] zero = ord('0') i_pos = 1 diff --git a/strings/one_edit_distance.py b/algorithms/strings/one_edit_distance.py similarity index 100% rename from strings/one_edit_distance.py rename to algorithms/strings/one_edit_distance.py diff --git a/algorithms/strings/panagram.py b/algorithms/strings/panagram.py new file mode 100644 index 000000000..d34965356 --- /dev/null +++ b/algorithms/strings/panagram.py @@ -0,0 +1,36 @@ +""" +Given a string, check whether it is a panagram or not. + +A panagram is a sentence that uses every letter at least once. + +The most famous example is: "he quick brown fox jumps over the lazy dog. + +Note: +A panagram in one language isn't necessarily a panagram in another. This +module assumes the english language. Hence, the Finnish panagram +'Törkylempijävongahdus' won't pass for a panagram despite being considered +a perfect panagram in its language. However, the Swedish panagram +'Yxmördaren Julia Blomqvist på fäktning i Schweiz' will pass despite +including letters not used in the english alphabet. This is because the +Swedish alphabet only extends the Latin one. +""" + +from string import ascii_lowercase + +def panagram(string): + """ + Returns whether the input string is an English panagram or not. + + Parameters: + string (str): A sentence in the form of a string. + + Returns: + A boolean with the result. + """ + letters = set(ascii_lowercase) + for c in string: + try: + letters.remove(c.lower()) + except: + pass + return len(letters) == 0 \ No newline at end of file diff --git a/strings/rabin_karp.py b/algorithms/strings/rabin_karp.py similarity index 100% rename from strings/rabin_karp.py rename to algorithms/strings/rabin_karp.py diff --git a/algorithms/strings/repeat_string.py b/algorithms/strings/repeat_string.py new file mode 100644 index 000000000..cc871f64c --- /dev/null +++ b/algorithms/strings/repeat_string.py @@ -0,0 +1,24 @@ +""" +Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of it. If no such solution, return -1. + +For example, with A = "abcd" and B = "cdabcdab". + +Return 3, because by repeating A three times (“abcdabcdabcd”), B is a substring of it; and B is not a substring of A repeated two times ("abcdabcd"). + +Note: +The length of A and B will be between 1 and 10000. + +Reference: https://leetcode.com/problems/repeated-string-match/description/ +""" +def repeat_string(A, B): + count = 1 + tmp = A + max_count = (len(B) / len(A)) + 1 + while not(B in tmp): + tmp = tmp + A + if (count > max_count): + count = -1 + break + count = count + 1 + + return count diff --git a/algorithms/strings/repeat_substring.py b/algorithms/strings/repeat_substring.py new file mode 100644 index 000000000..0f26c2819 --- /dev/null +++ b/algorithms/strings/repeat_substring.py @@ -0,0 +1,25 @@ +""" +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. + +For example: +Input: "abab" +Output: True +Explanation: It's the substring "ab" twice. + +Input: "aba" +Output: False + +Input: "abcabcabcabc" +Output: True +Explanation: It's the substring "abc" four times. + +Reference: https://leetcode.com/problems/repeated-substring-pattern/description/ +""" +def repeat_substring(s): + """ + :type s: str + :rtype: bool + """ + str = (s + s)[1:-1] + return s in str diff --git a/strings/reverse_string.py b/algorithms/strings/reverse_string.py similarity index 100% rename from strings/reverse_string.py rename to algorithms/strings/reverse_string.py diff --git a/strings/reverse_vowel.py b/algorithms/strings/reverse_vowel.py similarity index 100% rename from strings/reverse_vowel.py rename to algorithms/strings/reverse_vowel.py diff --git a/strings/reverse_words.py b/algorithms/strings/reverse_words.py similarity index 100% rename from strings/reverse_words.py rename to algorithms/strings/reverse_words.py diff --git a/strings/roman_to_int.py b/algorithms/strings/roman_to_int.py similarity index 100% rename from strings/roman_to_int.py rename to algorithms/strings/roman_to_int.py diff --git a/algorithms/strings/rotate.py b/algorithms/strings/rotate.py new file mode 100644 index 000000000..5bfcd1971 --- /dev/null +++ b/algorithms/strings/rotate.py @@ -0,0 +1,23 @@ +""" +Given a strings s and int k, return a string that rotates k times + +k can be any positive integer. + +For example, +rotate("hello", 2) return "llohe" +rotate("hello", 5) return "hello" +rotate("hello", 6) return "elloh" +rotate("hello", 7) return "llohe" +rotate("hello", 102) return "lohel" + +""" +def rotate(s, k): + long_string = s * (k // len(s) + 2) + if k <= len(s): + return long_string[k:k + len(s)] + else: + return long_string[k-len(s):k] + +def rotate_alt(string, k): + k = k % len(string) + return string[k:] + string[:k] diff --git a/strings/strip_url_params.py b/algorithms/strings/strip_url_params.py similarity index 100% rename from strings/strip_url_params.py rename to algorithms/strings/strip_url_params.py diff --git a/algorithms/strings/strong_password.py b/algorithms/strings/strong_password.py new file mode 100644 index 000000000..85acd0223 --- /dev/null +++ b/algorithms/strings/strong_password.py @@ -0,0 +1,43 @@ +""" +The signup page required her to input a name and a password. However, the password +must be strong. The website considers a password to be strong if it satisfies the following criteria: + +1) Its length is at least 6. +2) It contains at least one digit. +3) It contains at least one lowercase English character. +4) It contains at least one uppercase English character. +5) It contains at least one special character. The special characters are: !@#$%^&*()-+ +She typed a random string of length in the password field but wasn't sure if it was strong. +Given the string she typed, can you find the minimum number of characters she must add to make her password strong? + +Note: Here's the set of types of characters in a form you can paste in your solution: +numbers = "0123456789" +lower_case = "abcdefghijklmnopqrstuvwxyz" +upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +special_characters = "!@#$%^&*()-+" + +Input Format +The first line contains an integer denoting the length of the string. +The second line contains a string consisting of characters, the password +typed by Louise. Each character is either a lowercase/uppercase English alphabet, a digit, or a special character. + +Sample Input 1: strong_password(3,"Ab1") +Output: 3 (Because She can make the password strong by adding characters,for example, $hk, turning the password into Ab1$hk which is strong. +2 characters aren't enough since the length must be at least 6.) + +Sample Output 2: strong_password(11,"#Algorithms") +Output: 1 (Because the password isn't strong, but she can make it strong by adding a single digit.) + +""" +def strong_password(n, password): + count_error = 0 + # Return the minimum number of characters to make the password strong + if any(i.isdigit() for i in password) == False: + count_error = count_error + 1 + if any(i.islower() for i in password) == False: + count_error = count_error + 1 + if any(i.isupper() for i in password) == False: + count_error = count_error + 1 + if any(i in '!@#$%^&*()-+' for i in password) == False: + count_error = count_error + 1 + return max(count_error, 6 - n) diff --git a/algorithms/strings/text_justification.py b/algorithms/strings/text_justification.py new file mode 100644 index 000000000..a14a8ba39 --- /dev/null +++ b/algorithms/strings/text_justification.py @@ -0,0 +1,89 @@ +""" +Given an array of words and a width maxWidth, format the text such that each line +has exactly maxWidth characters and is fully (left and right) justified. + +You should pack your words in a greedy approach; that is, pack as many words as +you can in each line. Pad extra spaces ' ' when necessary so that each line has +exactly maxWidth characters. + +Extra spaces between words should be distributed as evenly as possible. If the +number of spaces on a line do not divide evenly between words, the empty slots +on the left will be assigned more spaces than the slots on the right. + +For the last line of text, it should be left justified and no extra space is +inserted between words. + +Note: +A word is defined as a character sequence consisting of non-space characters only. +Each word's length is guaranteed to be greater than 0 and not exceed maxWidth. +The input array words contains at least one word. + +Example: +Input: +words = ["What","must","be","acknowledgment","shall","be"] +maxWidth = 16 +Output: +[ + "What must be", + "acknowledgment ", + "shall be " +] +""" + + +def text_justification(words, max_width): + ''' + :type words: list + :type max_width: int + :rtype: list + ''' + ret = [] # return value + row_len = 0 # current length of strs in a row + row_words = [] # current words in a row + index = 0 # the index of current word in words + is_first_word = True # is current word the first in a row + while index < len(words): + while row_len <= max_width and index < len(words): + if len(words[index]) > max_width: + raise ValueError("there exists word whose length is larger than max_width") + tmp = row_len + row_words.append(words[index]) + tmp += len(words[index]) + if not is_first_word: + tmp += 1 # except for the first word, each word should have at least a ' ' before it. + if tmp > max_width: + row_words.pop() + break + row_len = tmp + index += 1 + is_first_word = False + # here we have already got a row of str , then we should supplement enough ' ' to make sure the length is max_width. + row = "" + # if the row is the last + if index == len(words): + for word in row_words: + row += (word + ' ') + row = row[:-1] + row += ' ' * (max_width - len(row)) + # not the last row and more than one word + elif len(row_words) != 1: + space_num = max_width - row_len + space_num_of_each_interval = space_num // (len(row_words) - 1) + space_num_rest = space_num - space_num_of_each_interval * (len(row_words) - 1) + for j in range(len(row_words)): + row += row_words[j] + if j != len(row_words) - 1: + row += ' ' * (1 + space_num_of_each_interval) + if space_num_rest > 0: + row += ' ' + space_num_rest -= 1 + # row with only one word + else: + row += row_words[0] + row += ' ' * (max_width - len(row)) + ret.append(row) + # after a row , reset those value + row_len = 0 + row_words = [] + is_first_word = True + return ret diff --git a/algorithms/strings/unique_morse.py b/algorithms/strings/unique_morse.py new file mode 100644 index 000000000..6d8618644 --- /dev/null +++ b/algorithms/strings/unique_morse.py @@ -0,0 +1,94 @@ +""" +International Morse Code defines a standard encoding where each letter is mapped to +a series of dots and dashes, as follows: "a" maps to ".-", "b" maps to "-...", "c" +maps to "-.-.", and so on. + +For convenience, the full table for the 26 letters of the English alphabet is given below: + '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':"--.." + +Now, given a list of words, each word can be written as a concatenation of the +Morse code of each letter. For example, "cab" can be written as "-.-.-....-", +(which is the concatenation "-.-." + "-..." + ".-"). We'll call such a +concatenation, the transformation of a word. + +Return the number of different transformations among all words we have. +Example: +Input: words = ["gin", "zen", "gig", "msg"] +Output: 2 +Explanation: +The transformation of each word is: +"gin" -> "--...-." +"zen" -> "--...-." +"gig" -> "--...--." +"msg" -> "--...--." + +There are 2 different transformations, "--...-." and "--...--.". +""" + +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): + morse_word = "" + word = word.lower() + for char in word: + morse_word = morse_word + morse_code[char] + return morse_word + +def unique_morse(words): + unique_morse_word = [] + for word in words: + morse_word = convert_morse_word(word) + if morse_word not in unique_morse_word: + unique_morse_word.append(morse_word) + return len(unique_morse_word) diff --git a/strings/validate_coordinates.py b/algorithms/strings/validate_coordinates.py similarity index 100% rename from strings/validate_coordinates.py rename to algorithms/strings/validate_coordinates.py diff --git a/strings/word_squares.py b/algorithms/strings/word_squares.py similarity index 100% rename from strings/word_squares.py rename to algorithms/strings/word_squares.py diff --git a/linkedlist/__init__.py b/algorithms/tree/__init__.py similarity index 100% rename from linkedlist/__init__.py rename to algorithms/tree/__init__.py diff --git a/maths/__init__.py b/algorithms/tree/avl/__init__.py similarity index 100% rename from maths/__init__.py rename to algorithms/tree/avl/__init__.py diff --git a/tree/avl/avl.py b/algorithms/tree/avl/avl.py similarity index 94% rename from tree/avl/avl.py rename to algorithms/tree/avl/avl.py index 102fec82d..e3cbecb7f 100644 --- a/tree/avl/avl.py +++ b/algorithms/tree/avl/avl.py @@ -1,3 +1,4 @@ +""" Imports TreeNodes""" from tree.tree import TreeNode @@ -17,9 +18,9 @@ def insert(self, key): Insert new key into node """ # Create new node - n = TreeNode(key) + node = TreeNode(key) if not self.node: - self.node = n + self.node = node self.node.left = AvlTree() self.node.right = AvlTree() elif key < self.node.val: @@ -65,7 +66,8 @@ def update_heights(self, recursive=True): if self.node.right: self.node.right.update_heights() - self.height = 1 + max(self.node.left.height, self.node.right.height) + self.height = 1 + max(self.node.left.height, + self.node.right.height) else: self.height = -1 diff --git a/algorithms/tree/b_tree.py b/algorithms/tree/b_tree.py new file mode 100644 index 000000000..1e68b432f --- /dev/null +++ b/algorithms/tree/b_tree.py @@ -0,0 +1,251 @@ +""" +B-tree is used to disk operations. Each node (except root) contains +at least t-1 keys (t children) and at most 2*t - 1 keys (2*t children) +where t is the degree of b-tree. It is not a kind of typical bst tree, because +this tree grows up. +B-tree is balanced which means that the difference between height of left +subtree and right subtree is at most 1. + +Complexity + n - number of elements + t - degree of tree + Tree always has height at most logt (n+1)/2 + Algorithm Average Worst case + Space O(n) O(n) + Search O(log n) O(log n) + Insert O(log n) O(log n) + Delete O(log n) O(log n) +""" + + +class Node: + """ Class of Node""" + + def __init__(self): + # self.is_leaf = is_leaf + self.keys = [] + self.children = [] + + def __repr__(self): + return "".format(self.keys) + + @property + def is_leaf(self): + """ Return if it is a leaf""" + return len(self.children) == 0 + + +class BTree: + """ Class of BTree """ + + def __init__(self, t_val=2): + 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): + 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] + # child is left child of parent after splitting + + 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): + """ overflow, tree increases in height """ + 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): + i = len(node.keys) - 1 + while i >= 0 and node.keys[i] >= key: # find position where insert key + i -= 1 + + if node.is_leaf: + node.keys.insert(i + 1, key) + else: + # overflow + if len(node.children[i + 1].keys) >= self.max_number_of_keys: + self._split_child(node, i + 1) + # decide which child is going to have a new key + if node.keys[i + 1] < key: + i += 1 + + self._insert_to_nonfull_node(node.children[i + 1], key) + + def find(self, key) -> bool: + """ Finds key """ + 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): + self._remove_key(self.root, key) + + def _remove_key(self, node: Node, key) -> bool: + 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: # key not found in node + if node.is_leaf: + print("Key not found.") + return False # key not found + else: + i = 0 + number_of_keys = len(node.keys) + # decide in which subtree may be key + 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: + child = node.children[child_index] + # The leaf/node is correct + 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): # 0 <-- 1 + self._rotate_left(node, child_index) + return True + + if child_index > 0: + # merge child with brother on the left + self._merge(node, child_index - 1, child_index) + else: + # merge child with brother on the right + self._merge(node, child_index, child_index + 1) + + return True + + def _rotate_left(self, parent_node: Node, child_index: int): + """ + Take key from right brother of the child and transfer to the child + """ + 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) + # make ownerless_child as a new biggest child (with highest key) + # -> transfer from right subtree to left subtree + parent_node.children[child_index].children.append(ownerless_child) + + def _rotate_right(self, parent_node: Node, child_index: int): + """ + Take key from left brother of the child and transfer to the child + """ + 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() + # make ownerless_child as a new lowest child (with lowest key) + # -> transfer from left subtree to right subtree + parent_node.children[child_index].children.insert( + 0, ownerless_child) + + def _merge(self, parent_node: Node, to_merge_index: int, transfered_child_index: int): + from_merge_node = parent_node.children.pop(transfered_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): + 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): + 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]) + # self._repair_tree(node, ch_index) + return largest_key_in_subtree + + def _find_largest_and_delete_in_right_subtree(self, node: Node): + 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]) + # self._repair_tree(node, ch_index) + return largest_key_in_subtree + + def traverse_tree(self): + self._traverse_tree(self.root) + print() + + def _traverse_tree(self, node: Node): + if node.is_leaf: + print(node.keys, end=" ") + else: + for i, key in enumerate(node.keys): + self._traverse_tree(node.children[i]) + print(key, end=" ") + self._traverse_tree(node.children[-1]) diff --git a/tree/bintree2list.py b/algorithms/tree/bin_tree_to_list.py similarity index 51% rename from tree/bintree2list.py rename to algorithms/tree/bin_tree_to_list.py index 091295444..566ef7568 100644 --- a/tree/bintree2list.py +++ b/algorithms/tree/bin_tree_to_list.py @@ -1,49 +1,37 @@ -class Node(): - def __init__(self, val = 0): - self.val = val - self.left = None - self.right = None +from tree.tree import TreeNode -def bintree2list(root): + +def bin_tree_to_list(root): """ type root: root class """ if not root: return root - root = bintree2list_util(root) + root = bin_tree_to_list_util(root) while root.left: root = root.left return root -def bintree2list_util(root): + +def bin_tree_to_list_util(root): if not root: return root if root.left: - left = bintree2list_util(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 = bintree2list_util(root.right) + right = bin_tree_to_list_util(root.right) while right.left: right = right.left right.left = root root.right = right return root + def print_tree(root): while root: print(root.val) root = root.right - -tree = Node(10) -tree.left = Node(12) -tree.right = Node(15) -tree.left.left = Node(25) -tree.left.left.right = Node(100) -tree.left.right = Node(30) -tree.right.left = Node(36) - -head = bintree2list(tree) -print_tree(head) diff --git a/tree/binary_tree_paths.py b/algorithms/tree/binary_tree_paths.py similarity index 81% rename from tree/binary_tree_paths.py rename to algorithms/tree/binary_tree_paths.py index 3de72f710..218064b97 100644 --- a/tree/binary_tree_paths.py +++ b/algorithms/tree/binary_tree_paths.py @@ -1,12 +1,13 @@ def binary_tree_paths(root): res = [] - if not root: + if root is None: return res dfs(res, root, str(root.val)) return res + def dfs(res, root, cur): - if not root.left and not root.right: + if root.left is None and root.right is None: res.append(cur) if root.left: dfs(res, root.left, cur+'->'+str(root.left.val)) diff --git a/tree/bst/BSTIterator.py b/algorithms/tree/bst/BSTIterator.py similarity index 100% rename from tree/bst/BSTIterator.py rename to algorithms/tree/bst/BSTIterator.py diff --git a/tree/bst/array2bst.py b/algorithms/tree/bst/array_to_bst.py similarity index 75% rename from tree/bst/array2bst.py rename to algorithms/tree/bst/array_to_bst.py index 62fe5b9b7..7ca5cf943 100644 --- a/tree/bst/array2bst.py +++ b/algorithms/tree/bst/array_to_bst.py @@ -11,11 +11,11 @@ def __init__(self, x): self.right = None -def array2bst(nums): +def array_to_bst(nums): if not nums: return None mid = len(nums)//2 node = TreeNode(nums[mid]) - node.left = array2bst(nums[:mid]) - node.right = array2bst(nums[mid+1:]) + node.left = array_to_bst(nums[:mid]) + node.right = array_to_bst(nums[mid+1:]) return node diff --git a/tree/bst/bst.py b/algorithms/tree/bst/bst.py similarity index 95% rename from tree/bst/bst.py rename to algorithms/tree/bst/bst.py index 223b6b722..de1bddd8c 100644 --- a/tree/bst/bst.py +++ b/algorithms/tree/bst/bst.py @@ -89,14 +89,14 @@ def preorder(self, root): def inorder(self, root): if root: - self.preorder(root.left) + self.inorder(root.left) print(str(root.data), end = ' ') - self.preorder(root.right) + self.inorder(root.right) def postorder(self, root): if root: - self.preorder(root.left) - self.preorder(root.right) + self.postorder(root.left) + self.postorder(root.right) print(str(root.data), end = ' ') """ diff --git a/tree/bst/bst_closest_value.py b/algorithms/tree/bst/bst_closest_value.py similarity index 100% rename from tree/bst/bst_closest_value.py rename to algorithms/tree/bst/bst_closest_value.py diff --git a/tree/bst/count_left_node.py b/algorithms/tree/bst/count_left_node.py similarity index 100% rename from tree/bst/count_left_node.py rename to algorithms/tree/bst/count_left_node.py diff --git a/tree/bst/delete_node.py b/algorithms/tree/bst/delete_node.py similarity index 100% rename from tree/bst/delete_node.py rename to algorithms/tree/bst/delete_node.py diff --git a/tree/bst/depth_sum.py b/algorithms/tree/bst/depth_sum.py similarity index 100% rename from tree/bst/depth_sum.py rename to algorithms/tree/bst/depth_sum.py diff --git a/tree/bst/height.py b/algorithms/tree/bst/height.py similarity index 100% rename from tree/bst/height.py rename to algorithms/tree/bst/height.py diff --git a/tree/bst/is_bst.py b/algorithms/tree/bst/is_bst.py similarity index 92% rename from tree/bst/is_bst.py rename to algorithms/tree/bst/is_bst.py index a132a5af3..ad3adc601 100644 --- a/tree/bst/is_bst.py +++ b/algorithms/tree/bst/is_bst.py @@ -20,17 +20,16 @@ Binary tree [1,2,3], return false. """ - def is_bst(root): """ :type root: TreeNode :rtype: bool """ - if not root: - return True + stack = [] pre = None - while root and stack: + + while root or stack: while root: stack.append(root) root = root.left @@ -39,4 +38,5 @@ def is_bst(root): return False pre = root root = root.right + return True diff --git a/tree/bst/kth_smallest.py b/algorithms/tree/bst/kth_smallest.py similarity index 100% rename from tree/bst/kth_smallest.py rename to algorithms/tree/bst/kth_smallest.py diff --git a/tree/bst/lowest_common_ancestor.py b/algorithms/tree/bst/lowest_common_ancestor.py similarity index 100% rename from tree/bst/lowest_common_ancestor.py rename to algorithms/tree/bst/lowest_common_ancestor.py diff --git a/tree/bst/num_empty.py b/algorithms/tree/bst/num_empty.py similarity index 100% rename from tree/bst/num_empty.py rename to algorithms/tree/bst/num_empty.py diff --git a/tree/bst/predecessor.py b/algorithms/tree/bst/predecessor.py similarity index 100% rename from tree/bst/predecessor.py rename to algorithms/tree/bst/predecessor.py diff --git a/tree/bst/serialize_deserialize.py b/algorithms/tree/bst/serialize_deserialize.py similarity index 100% rename from tree/bst/serialize_deserialize.py rename to algorithms/tree/bst/serialize_deserialize.py diff --git a/tree/bst/successor.py b/algorithms/tree/bst/successor.py similarity index 100% rename from tree/bst/successor.py rename to algorithms/tree/bst/successor.py diff --git a/tree/bst/unique_bst.py b/algorithms/tree/bst/unique_bst.py similarity index 100% rename from tree/bst/unique_bst.py rename to algorithms/tree/bst/unique_bst.py diff --git a/algorithms/tree/construct_tree_postorder_preorder.py b/algorithms/tree/construct_tree_postorder_preorder.py new file mode 100644 index 000000000..a1bc1688a --- /dev/null +++ b/algorithms/tree/construct_tree_postorder_preorder.py @@ -0,0 +1,111 @@ +""" + Given two arrays representing preorder and postorder traversal of a full + binary tree, construct the binary tree and print the inorder traversal of the + tree. + A full binary tree has either 0 or 2 children. + Algorithm: + 1. Assign the first element of preorder array as root of the tree. + 2. Find the same element in the postorder array and divide the postorder + array into left and right subtree. + 3. Repeat the above steps for all the elements and construct the tree. + Eg: pre[] = {1, 2, 4, 8, 9, 5, 3, 6, 7} + post[] = {8, 9, 4, 5, 2, 6, 7, 3, 1} + Tree: + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ + 8 9 + Output: 8 4 9 2 5 1 6 3 7 +""" + +class TreeNode: + + def __init__(self, val, left = None, right = None): + self.val = val + self.left = left + self.right = right + +pre_index = 0 + +def construct_tree_util(pre: list, post: list, low: int, high: int, size: int): + """ + Recursive function that constructs tree from preorder and postorder array. + + preIndex is a global variable that keeps track of the index in preorder + array. + preorder and postorder array are represented are pre[] and post[] respectively. + low and high are the indices for the postorder array. + """ + + global pre_index + + if pre_index == -1: + pre_index = 0 + + + #Base case + if(pre_index >= size or low > high): + return None + + root = TreeNode(pre[pre_index]) + pre_index += 1 + + #If only one element in the subarray return root + if(low == high or pre_index >= size): + return root + + #Find the next element of pre[] in post[] + i = low + while i <= high: + if(pre[pre_index] == post[i]): + break + + i += 1 + + #Use index of element present in postorder to divide postorder array + #to two parts: left subtree and right subtree + 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, post: list, size: int): + """ + Main Function that will construct the full binary tree from given preorder + and postorder array. + """ + + global pre_index + root = construct_tree_util(pre, post, 0, size-1, size) + + return print_inorder(root) + + + +def print_inorder(root: TreeNode, result = None): + """ + Prints the tree constructed in inorder format + """ + if root is None: + return [] + if result is None: + result = [] + + print_inorder(root.left, result) + result.append(root.val) + print_inorder(root.right, result) + return result + +if __name__ == '__main__': + pre = [1, 2, 4, 5, 3, 6, 7] + post = [4, 5, 2, 6, 7, 3, 1] + size = len(pre) + + result = construct_tree(pre, post, size) + + print(result) diff --git a/tree/deepest_left.py b/algorithms/tree/deepest_left.py similarity index 60% rename from tree/deepest_left.py rename to algorithms/tree/deepest_left.py index 1448bc979..6d3a1c262 100644 --- a/tree/deepest_left.py +++ b/algorithms/tree/deepest_left.py @@ -12,18 +12,15 @@ # 7 # should return 4. +from tree.tree import TreeNode -class Node: - def __init__(self, val = None): - self.left = None - self.right = None - self.val = val class DeepestLeft: def __init__(self): self.depth = 0 self.Node = None + def find_deepest_left(root, is_left, depth, res): if not root: return @@ -33,15 +30,17 @@ def find_deepest_left(root, is_left, depth, res): find_deepest_left(root.left, True, depth + 1, res) find_deepest_left(root.right, False, depth + 1, res) -root = Node(1) -root.left = Node(2) -root.right = Node(3) -root.left.left = Node(4) -root.left.right = Node(5) -root.right.right = Node(6) -root.right.right.right = Node(7) - -res = DeepestLeft() -find_deepest_left(root, True, 1, res) -if res.Node: - print(res.Node.val) + +if __name__ == '__main__': + root = TreeNode(1) + root.left = TreeNode(2) + root.right = TreeNode(3) + root.left.left = TreeNode(4) + root.left.right = TreeNode(5) + root.right.right = TreeNode(6) + root.right.right.right = TreeNode(7) + + res = DeepestLeft() + find_deepest_left(root, True, 1, res) + if res.Node: + print(res.Node.val) diff --git a/algorithms/tree/fenwick_tree/fenwick_tree.py b/algorithms/tree/fenwick_tree/fenwick_tree.py new file mode 100644 index 000000000..67ec5105e --- /dev/null +++ b/algorithms/tree/fenwick_tree/fenwick_tree.py @@ -0,0 +1,77 @@ +""" +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(object): + 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 diff --git a/tree/invert_tree.py b/algorithms/tree/invert_tree.py similarity index 90% rename from tree/invert_tree.py rename to algorithms/tree/invert_tree.py index 2d70c4f8e..9a6d58eee 100644 --- a/tree/invert_tree.py +++ b/algorithms/tree/invert_tree.py @@ -1,7 +1,7 @@ # invert a binary tree def reverse(root): - if not root: + if root is None: return root.left, root.right = root.right, root.left if root.left: diff --git a/algorithms/tree/is_balanced.py b/algorithms/tree/is_balanced.py new file mode 100644 index 000000000..3910e1b7e --- /dev/null +++ b/algorithms/tree/is_balanced.py @@ -0,0 +1,37 @@ +def is_balanced(root): + return __is_balanced_recursive(root) + + +def __is_balanced_recursive(root): + """ + O(N) solution + """ + return -1 != __get_depth(root) + + +def __get_depth(root): + """ + return 0 if unbalanced else depth + 1 + """ + 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) + + +# def is_balanced(root): +# """ +# O(N^2) solution +# """ +# left = max_height(root.left) +# right = max_height(root.right) +# return abs(left-right) <= 1 and is_balanced(root.left) and +# is_balanced(root.right) + +# def max_height(root): +# if root is None: +# return 0 +# return max(max_height(root.left), max_height(root.right)) + 1 diff --git a/tree/is_subtree.py b/algorithms/tree/is_subtree.py similarity index 93% rename from tree/is_subtree.py rename to algorithms/tree/is_subtree.py index 844b0d5f7..3e267f76e 100644 --- a/tree/is_subtree.py +++ b/algorithms/tree/is_subtree.py @@ -64,10 +64,8 @@ def is_subtree(big, small): def comp(p, q): - if not p and not q: + if p is None and q is None: return True - if p and q: + 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 - - diff --git a/tree/is_symmetric.py b/algorithms/tree/is_symmetric.py similarity index 77% rename from tree/is_symmetric.py rename to algorithms/tree/is_symmetric.py index 3aff3cfd2..19bcd4526 100644 --- a/tree/is_symmetric.py +++ b/algorithms/tree/is_symmetric.py @@ -21,32 +21,32 @@ # TC: O(b) SC: O(log n) def is_symmetric(root): - if not root: + if root is None: return True return helper(root.left, root.right) def helper(p, q): - if not p and not q: + if p is None and q is None: return True - if not p or not q or q.val != p.val: + 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): - if not root: + if root is None: return True stack = [[root.left, root.right]] while stack: left, right = stack.pop() # popleft - if not left and not right: + if left is None and right is None: continue - if not left or not right: + 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.right]) + stack.append([left.right, right.left]) else: return False return True diff --git a/tree/longest_consecutive.py b/algorithms/tree/longest_consecutive.py similarity index 70% rename from tree/longest_consecutive.py rename to algorithms/tree/longest_consecutive.py index fde962c26..84118010c 100644 --- a/tree/longest_consecutive.py +++ b/algorithms/tree/longest_consecutive.py @@ -30,20 +30,20 @@ def longest_consecutive(root): :type root: TreeNode :rtype: int """ - if not root: + if root is None: return 0 - maxlen = 0 - dfs(root, 0, root.val, maxlen) - return maxlen + max_len = 0 + dfs(root, 0, root.val, max_len) + return max_len -def dfs(root, cur, target, maxlen): - if not root: +def dfs(root, cur, target, max_len): + if root is None: return if root.val == target: cur += 1 else: cur = 1 - maxlen = max(cur, maxlen) - dfs(root.left, cur, root.val+1, maxlen) - dfs(root.right, cur, root.val+1, maxlen) + max_len = max(cur, max_len) + dfs(root.left, cur, root.val+1, max_len) + dfs(root.right, cur, root.val+1, max_len) diff --git a/tree/lowest_common_ancestor.py b/algorithms/tree/lowest_common_ancestor.py similarity index 91% rename from tree/lowest_common_ancestor.py rename to algorithms/tree/lowest_common_ancestor.py index 5c4016aed..d0f33ccd1 100644 --- a/tree/lowest_common_ancestor.py +++ b/algorithms/tree/lowest_common_ancestor.py @@ -28,10 +28,10 @@ def lca(root, p, q): :type q: TreeNode :rtype: TreeNode """ - if not root or root is p or root is q: + 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 and right: + if left is not None and right is not None: return root return left if left else right diff --git a/tree/max_height.py b/algorithms/tree/max_height.py similarity index 51% rename from tree/max_height.py rename to algorithms/tree/max_height.py index a88a3fff2..c732ac079 100644 --- a/tree/max_height.py +++ b/algorithms/tree/max_height.py @@ -5,21 +5,18 @@ longest path from the root node down to the farthest leaf node. """ - -class Node(): - def __init__(self, val = 0): - self.val = val - self.left = None - self.right = None - # def max_height(root): - # if not root: - # return 0 - # return max(maxDepth(root.left), maxDepth(root.right)) + 1 +# if not root: +# return 0 +# return max(maxDepth(root.left), maxDepth(root.right)) + 1 # iterative + +from tree import TreeNode + + def max_height(root): - if not root: + if root is None: return 0 height = 0 queue = [root] @@ -28,27 +25,30 @@ def max_height(root): level = [] while queue: node = queue.pop(0) - if node.left: + if node.left is not None: level.append(node.left) - if node.right: + if node.right is not None: level.append(node.right) queue = level return height + def print_tree(root): - if root: + if root is not None: print(root.val) print_tree(root.left) print_tree(root.right) -tree = Node(10) -tree.left = Node(12) -tree.right = Node(15) -tree.left.left = Node(25) -tree.left.left.right = Node(100) -tree.left.right = Node(30) -tree.right.left = Node(36) - -height = max_height(tree) -print_tree(tree) -print("height:", height) + +if __name__ == '__main__': + tree = TreeNode(10) + tree.left = TreeNode(12) + tree.right = TreeNode(15) + tree.left.left = TreeNode(25) + tree.left.left.right = TreeNode(100) + tree.left.right = TreeNode(30) + tree.right.left = TreeNode(36) + + height = max_height(tree) + print_tree(tree) + print("height:", height) diff --git a/tree/max_path_sum.py b/algorithms/tree/max_path_sum.py similarity index 93% rename from tree/max_path_sum.py rename to algorithms/tree/max_path_sum.py index 10559b52a..7f630fe6b 100644 --- a/tree/max_path_sum.py +++ b/algorithms/tree/max_path_sum.py @@ -1,5 +1,3 @@ - - def max_path_sum(root): maximum = float("-inf") helper(root, maximum) @@ -7,7 +5,7 @@ def max_path_sum(root): def helper(root, maximum): - if not root: + if root is None: return 0 left = helper(root.left, maximum) right = helper(root.right, maximum) diff --git a/tree/min_height.py b/algorithms/tree/min_height.py similarity index 52% rename from tree/min_height.py rename to algorithms/tree/min_height.py index a3923d43e..7913e2965 100644 --- a/tree/min_height.py +++ b/algorithms/tree/min_height.py @@ -1,8 +1,4 @@ -class Node(): - def __init__(self, val = 0): - self.val = val - self.left = None - self.right = None +from tree import TreeNode def min_depth(self, root): @@ -10,16 +6,16 @@ def min_depth(self, root): :type root: TreeNode :rtype: int """ - if not root: + if root is None: return 0 - if not root.left or not root.right: + 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 # iterative def min_height(root): - if not root: + if root is None: return 0 height = 0 level = [root] @@ -27,30 +23,32 @@ def min_height(root): height += 1 new_level = [] for node in level: - if not node.left and not node.right: + if node.left is None and node.right is None: return height - if node.left: + if node.left is not None: new_level.append(node.left) - if node.right: + if node.right is not None: new_level.append(node.right) level = new_level return height def print_tree(root): - if root: + if root is not None: print(root.val) print_tree(root.left) print_tree(root.right) -tree = Node(10) -tree.left = Node(12) -tree.right = Node(15) -tree.left.left = Node(25) -tree.left.left.right = Node(100) -tree.left.right = Node(30) -tree.right.left = Node(36) - -height = min_height(tree) -print_tree(tree) -print("height:", height) + +if __name__ == '__main__': + tree = TreeNode(10) + tree.left = TreeNode(12) + tree.right = TreeNode(15) + tree.left.left = TreeNode(25) + tree.left.left.right = TreeNode(100) + tree.left.right = TreeNode(30) + tree.right.left = TreeNode(36) + + height = min_height(tree) + print_tree(tree) + print("height:", height) diff --git a/tree/path_sum.py b/algorithms/tree/path_sum.py similarity index 78% rename from tree/path_sum.py rename to algorithms/tree/path_sum.py index b1001c3a4..aa9ff87e7 100644 --- a/tree/path_sum.py +++ b/algorithms/tree/path_sum.py @@ -21,9 +21,9 @@ def has_path_sum(root, sum): :type sum: int :rtype: bool """ - if not root: + if root is None: return False - if not root.left and not root.right and root.val == sum: + 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) @@ -31,33 +31,33 @@ def has_path_sum(root, sum): # DFS with stack def has_path_sum2(root, sum): - if not root: + if root is None: return False stack = [(root, root.val)] while stack: node, val = stack.pop() - if not node.left and not node.right: + if node.left is None and node.right is None: if val == sum: return True - if node.left: + if node.left is not None: stack.append((node.left, val+node.left.val)) - if node.right: + if node.right is not None: stack.append((node.right, val+node.right.val)) return False # BFS with queue def has_path_sum3(root, sum): - if not root: + if root is None: return False queue = [(root, sum-root.val)] while queue: node, val = queue.pop(0) # popleft - if not node.left and not node.right: + if node.left is None and node.right is None: if val == 0: return True - if node.left: + if node.left is not None: queue.append((node.left, val-node.left.val)) - if node.right: + if node.right is not None: queue.append((node.right, val-node.right.val)) return False diff --git a/tree/path_sum2.py b/algorithms/tree/path_sum2.py similarity index 73% rename from tree/path_sum2.py rename to algorithms/tree/path_sum2.py index c989f9c18..f7c3e3fcf 100644 --- a/tree/path_sum2.py +++ b/algorithms/tree/path_sum2.py @@ -20,51 +20,52 @@ def path_sum(root, sum): - if not root: + if root is None: return [] res = [] dfs(root, sum, [], res) return res + def dfs(root, sum, ls, res): - if not root.left and not root.right and root.val == sum: + if root.left is None and root.right is None and root.val == sum: ls.append(root.val) res.append(ls) - if root.left: + if root.left is not None: dfs(root.left, sum-root.val, ls+[root.val], res) - if root.right: + if root.right is not None: dfs(root.right, sum-root.val, ls+[root.val], res) # DFS with stack def path_sum2(root, s): - if not root: + if root is None: return [] res = [] stack = [(root, [root.val])] while stack: node, ls = stack.pop() - if not node.left and not node.right and sum(ls) == s: + if node.left is None and node.right is None and sum(ls) == s: res.append(ls) - if node.left: + if node.left is not None: stack.append((node.left, ls+[node.left.val])) - if node.right: + if node.right is not None: stack.append((node.right, ls+[node.right.val])) return res # BFS with queue def path_sum3(root, sum): - if not root: + if root is None: return [] res = [] queue = [(root, root.val, [root.val])] while queue: node, val, ls = queue.pop(0) # popleft - if not node.left and not node.right and val == sum: + if node.left is None and node.right is None and val == sum: res.append(ls) - if node.left: + if node.left is not None: queue.append((node.left, val+node.left.val, ls+[node.left.val])) - if node.right: + if node.right is not None: queue.append((node.right, val+node.right.val, ls+[node.right.val])) return res diff --git a/tree/pretty_print.py b/algorithms/tree/pretty_print.py similarity index 99% rename from tree/pretty_print.py rename to algorithms/tree/pretty_print.py index 1e756470a..02fc0f027 100644 --- a/tree/pretty_print.py +++ b/algorithms/tree/pretty_print.py @@ -8,6 +8,7 @@ # e -> Quin -> Book -> 5 # -> TV -> 2 # f -> Adam -> Computer -> 7 + from __future__ import print_function diff --git a/tree/red_black_tree/red_black_tree.py b/algorithms/tree/red_black_tree/red_black_tree.py similarity index 95% rename from tree/red_black_tree/red_black_tree.py rename to algorithms/tree/red_black_tree/red_black_tree.py index 15114e082..20b6e7bc3 100644 --- a/tree/red_black_tree/red_black_tree.py +++ b/algorithms/tree/red_black_tree/red_black_tree.py @@ -93,10 +93,10 @@ def fix_insert(self, 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 is 1: + 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 is 1: + 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 @@ -118,7 +118,7 @@ def fix_insert(self, node): self.right_rotate(node.parent.parent) else: uncle_node = node.parent.parent.left - if uncle_node and uncle_node.color is 1: + 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 @@ -194,7 +194,7 @@ def delete(self, node): node_color = node_min.color temp_node = node_min.right ## - if node_min.parent != node: + 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 @@ -202,15 +202,15 @@ def delete(self, node): 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 + # 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 != self.root and node.color == 0: + while node is not self.root and node.color == 0: # node is not root and color is black - if node == node.parent.left: + if node is node.parent.left: # node is left node node_brother = node.parent.right @@ -241,8 +241,7 @@ def delete_fixup(self, node): node.parent.color = 0 node_brother.right.color = 0 self.left_rotate(node.parent) - node = self.root - break + node = self.root else: node_brother = node.parent.left if node_brother.color == 1: @@ -264,8 +263,7 @@ def delete_fixup(self, node): node.parent.color = 0 node_brother.left.color = 0 self.right_rotate(node.parent) - node = self.root - break + node = self.root node.color = 0 def inorder(self): diff --git a/tree/same_tree.py b/algorithms/tree/same_tree.py similarity index 62% rename from tree/same_tree.py rename to algorithms/tree/same_tree.py index a407f6fd3..c2805a87c 100644 --- a/tree/same_tree.py +++ b/algorithms/tree/same_tree.py @@ -7,11 +7,11 @@ """ -def is_same_tree(p, q): - if not p and not q: +def is_same_tree(tree_p, tree_q): + if tree_p is None and tree_q is None: return True - if p and q and p.val == q.val: - return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right) + 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 # Time Complexity O(min(N,M)) diff --git a/algorithms/tree/segment_tree/iterative_segment_tree.py b/algorithms/tree/segment_tree/iterative_segment_tree.py new file mode 100644 index 000000000..84018edc4 --- /dev/null +++ b/algorithms/tree/segment_tree/iterative_segment_tree.py @@ -0,0 +1,53 @@ +""" +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, l, r): + l, r = l + self.size, r + self.size + res = None + while l <= r: + if l % 2 == 1: + res = self.tree[l] if res is None else self.fn(res, self.tree[l]) + if r % 2 == 0: + res = self.tree[r] if res is None else self.fn(res, self.tree[r]) + l, r = (l + 1) // 2, (r - 1) // 2 + return res diff --git a/tree/segment_tree/segment_tree.py b/algorithms/tree/segment_tree/segment_tree.py similarity index 87% rename from tree/segment_tree/segment_tree.py rename to algorithms/tree/segment_tree/segment_tree.py index de62b6b42..94aeba5dd 100644 --- a/tree/segment_tree/segment_tree.py +++ b/algorithms/tree/segment_tree/segment_tree.py @@ -8,14 +8,14 @@ def __init__(self,arr,function): self.segment = [0 for x in range(3*len(arr)+3)] self.arr = arr self.fn = function - self.maketree(0,0,len(arr)-1) + self.make_tree(0,0,len(arr)-1) - def maketree(self,i,l,r): + def make_tree(self,i,l,r): if l==r: self.segment[i] = self.arr[l] elif l self.size[root2]: + root1, root2 = root2, root1 + self.parents[root1] = root2 + self.size[root2] += self.size[root1] + self.count -= 1 + +def num_islands(positions): + """ + Given a list of positions to operate, count the number of islands + after each addLand operation. An island is surrounded by water and is + formed by connecting adjacent lands horizontally or vertically. You may + assume all four edges of the grid are all surrounded by water. + + Given a 3x3 grid, positions = [[0,0], [0,1], [1,2], [2,1]]. + Initially, the 2d grid grid is filled with water. + (Assume 0 represents water and 1 represents land). + + 0 0 0 + 0 0 0 + 0 0 0 + Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land. + + 1 0 0 + 0 0 0 Number of islands = 1 + 0 0 0 + Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land. + + 1 1 0 + 0 0 0 Number of islands = 1 + 0 0 0 + Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land. + + 1 1 0 + 0 0 1 Number of islands = 2 + 0 0 0 + Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land. + + 1 1 0 + 0 0 1 Number of islands = 3 + 0 1 0 + """ + + ans = [] + 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) + ans += [islands.count] + return ans diff --git a/algorithms/unix/__init__.py b/algorithms/unix/__init__.py new file mode 100644 index 000000000..0d1808da0 --- /dev/null +++ b/algorithms/unix/__init__.py @@ -0,0 +1,4 @@ +from .path.join_with_slash import * +from .path.full_path import * +from .path.split import * +from .path.simplify_path import * diff --git a/algorithms/unix/path/full_path.py b/algorithms/unix/path/full_path.py new file mode 100644 index 000000000..3bf109e12 --- /dev/null +++ b/algorithms/unix/path/full_path.py @@ -0,0 +1,6 @@ +""" +Get a full absolute path a file +""" +import os +def full_path(file): + return os.path.abspath(os.path.expanduser(file)) diff --git a/algorithms/unix/path/join_with_slash.py b/algorithms/unix/path/join_with_slash.py new file mode 100644 index 000000000..beec8d106 --- /dev/null +++ b/algorithms/unix/path/join_with_slash.py @@ -0,0 +1,18 @@ +""" +Both URL and file path joins use slashes as dividers between their parts. +For example: + +path/to/dir + file --> path/to/dir/file +path/to/dir/ + file --> path/to/dir/file +http://algorithms.com/ + part --> http://algorithms.com/part +http://algorithms.com + part --> http://algorithms/part +""" +import os + +def join_with_slash(base, suffix): + # Remove / trailing + base = base.rstrip('/') + # Remove / leading + suffix = suffix.lstrip('/').rstrip() + full_path = "{}/{}".format(base, suffix) + return full_path diff --git a/algorithms/unix/path/simplify_path.py b/algorithms/unix/path/simplify_path.py new file mode 100644 index 000000000..8880fc0c6 --- /dev/null +++ b/algorithms/unix/path/simplify_path.py @@ -0,0 +1,29 @@ +""" +Given an absolute path for a file (Unix-style), simplify it. + +For example, +path = "/home/", => "/home" +path = "/a/./b/../../c/", => "/c" + +Corner Cases: + +Did you consider the case where path = "/../"? +In this case, you should return "/". +Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/". +In this case, you should ignore redundant slashes and return "/home/foo". + +Reference: https://leetcode.com/problems/simplify-path/description/ +""" + +import os +def simplify_path_v1(path): + return os.path.abspath(path) + +def simplify_path_v2(path): + stack, tokens = [], path.split("/") + for token in tokens: + if token == ".." and stack: + stack.pop() + elif token != ".." and token != "." and token: + stack.append(token) + return "/" + "/".join(stack) diff --git a/algorithms/unix/path/split.py b/algorithms/unix/path/split.py new file mode 100644 index 000000000..168b12057 --- /dev/null +++ b/algorithms/unix/path/split.py @@ -0,0 +1,23 @@ +""" +Splitting a path into 2 parts +Example: +Input: https://algorithms/unix/test.py (for url) +Output: + part[0]: https://algorithms/unix + part[1]: test.py + +Input: algorithms/unix/test.py (for file path) +Output: + part[0]: algorithms/unix + part[1]: test.py +""" +import os + +def split(path): + parts = [] + split_part = path.rpartition('/') + # Takt the origin path without the last part + parts.append(split_part[0]) + # Take the last element of list + parts.append(split_part[2]) + return parts diff --git a/arrays/longest_non_repeat.py b/arrays/longest_non_repeat.py deleted file mode 100644 index 43f2055ed..000000000 --- a/arrays/longest_non_repeat.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Given a string, find the length of the longest substring -without repeating characters. -Examples: -Given "abcabcbb", the answer is "abc", which the length is 3. -Given "bbbbb", the answer is "b", with the length of 1. -Given "pwwkew", the answer is "wke", with the length of 3. -Note that the answer must be a substring, -"pwke" is a subsequence and not a substring. -""" - - -def longest_non_repeat(string): - """ - Finds the length of the longest substring - without repeating characters. - """ - if string is None: - return 0 - temp = [] - max_len = 0 - for i in string: - if i in temp: - temp = [] - temp.append(i) - max_len = max(max_len, len(temp)) - return max_len - - -def longest_non_repeat_two(string): - """ - Finds the length of the longest substring - without repeating characters. - Uses alternative algorithm. - """ - if string is None: - return 0 - start, max_len = 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_len = max(max_len, index - start + 1) - used_char[char] = index - return max_len diff --git a/arrays/summary_ranges.py b/arrays/summary_ranges.py deleted file mode 100644 index 3ee3417f2..000000000 --- a/arrays/summary_ranges.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Given a sorted integer array without duplicates, -return the summary of its ranges. - -For example, given [0, 1, 2, 4, 5, 7], return [(0, 2), (4, 5), (7, 7)]. -""" - - -def summary_ranges(array): - """ - :type array: List[int] - :rtype: List[] - """ - res = [] - if len(array) == 1: - return [str(array[0])] - i = 0 - while i < len(array): - num = array[i] - while i + 1 < len(array) and array[i + 1] - array[i] == 1: - i += 1 - if array[i] != num: - res.append((num, array[i])) - else: - res.append((num, num)) - i += 1 - return res diff --git a/backtrack/anagram.py b/backtrack/anagram.py deleted file mode 100644 index 862140fc1..000000000 --- a/backtrack/anagram.py +++ /dev/null @@ -1,62 +0,0 @@ -import unittest - -def all_perms_iter(elements): - """ - iterator: returns a perumation by each call. - """ - if len(elements) <=1: - yield elements - else: - for perm in all_perms_iter(elements[1:]): - for i in range(len(elements)): - yield perm[:i] + elements[0:1] + perm[i:] - - -def all_perms(elements): - """ - returns a list with the permuations. - """ - if len(elements) <=1: - return elements - else: - tmp = [] - for perm in all_perms(elements[1:]): - for i in range(len(elements)): - tmp.append(perm[:i] + elements[0:1] + perm[i:]) - return tmp - - -def anagram(s1, s2): - c1 = [0] * 26 - c2 = [0] * 26 - - for c in s1: - pos = ord(c)-ord('a') - c1[pos] = c1[pos] + 1 - - for c in s2: - pos = ord(c)-ord('a') - c2[pos] = c2[pos] + 1 - - return c1 == c2 - - -class TestSuite (unittest.TestCase): - - def test_all_perms(self): - perms = ['abc', 'bac', 'bca', 'acb', 'cab', 'cba'] - self.assertEqual(perms, all_perms("abc")) - - def test_all_perms_iter(self): - it = all_perms_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")) - - -if __name__ == "__main__": - unittest.main() diff --git a/backtrack/array_sum_combinations.py b/backtrack/array_sum_combinations.py deleted file mode 100644 index b8dbc8046..000000000 --- a/backtrack/array_sum_combinations.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -WAP to take one element from each of the array add it to the target sum. -Print all those three-element combinations. - -/* -A = [1, 2, 3, 3] -B = [2, 3, 3, 4] -C = [1, 2, 2, 2] -target = 7 -*/ - -Result: -[[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]] -""" -import itertools -from functools import partial - -A = [1, 2, 3, 3] -B = [2, 3, 3, 4] -C = [1, 2, 2, 2] -target = 7 - - -def construct_candidates(constructed_sofar): - global A, B, C - array = A - if 1 == len(constructed_sofar): - array = B - elif 2 == len(constructed_sofar): - array = C - return array - - -def over(constructed_sofar): - global target - sum = 0 - to_stop, reached_target = False, False - for elem in constructed_sofar: - sum += elem - if sum >= target or len(constructed_sofar) >= 3: - to_stop = True - if sum == target and 3 == len(constructed_sofar): - reached_target = True - - return to_stop, reached_target - - -def backtrack(constructed_sofar): - to_stop, reached_target = over(constructed_sofar) - if to_stop: - if reached_target: - print(constructed_sofar) - return - candidates = construct_candidates(constructed_sofar) - for candidate in candidates: - constructed_sofar.append(candidate) - backtrack(constructed_sofar[:]) - constructed_sofar.pop() - - -backtrack([]) - - -# Complexity: O(n(m+p)) - -# 1. Sort all the arrays - a,b,c. - This will improve average time complexity. -# 2. If c[i] < Sum, then look for Sum - c[i] in array a and b. When pair found, -# insert c[i], a[j] & b[k] into the result list. This can be done in O(n). -# 3. Keep on doing the above procedure while going through complete c array. - - -A = [1, 2, 3, 3] -B = [2, 3, 3, 4] -C = [1, 2, 2, 2] -S = 7 - - -def check_sum(n, *nums): - if sum(x for x in nums) == n: - return (True, nums) - else: - return (False, nums) - - -pro = itertools.product(A, B, C) -func = partial(check_sum, S) -sums = list(itertools.starmap(func, pro)) - -res = set() -for s in sums: - if s[0] is True and s[1] not in res: - res.add(s[1]) -print(res) diff --git a/backtrack/expression_add_operators.py b/backtrack/expression_add_operators.py deleted file mode 100644 index f8f520f9c..000000000 --- a/backtrack/expression_add_operators.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Given a string that contains only digits 0-9 and a target value, -return all possibilities to add binary operators (not unary) +, -, or * -between the digits so they prevuate to the target value. - -Examples: -"123", 6 -> ["1+2+3", "1*2*3"] -"232", 8 -> ["2*3+2", "2+3*2"] -"105", 5 -> ["1*0+5","10-5"] -"00", 0 -> ["0+0", "0-0", "0*0"] -"3456237490", 9191 -> [] -""" - -def add_operator(num, target): - """ - :type num: str - :type target: int - :rtype: List[str] - """ - res = [] - if not num: return res - helper(res, "", num, target, 0, 0, 0) - return res - - -def helper(res, path, num, target, pos, prev, multed): - if pos == len(num): - if target == prev: - res.append(path) - return - for i in range(pos, len(num)): - if i != pos and num[pos] == '0': # all digits have to be used - break - cur = int(num[pos:i+1]) - if pos == 0: - helper(res, path + str(cur), num, target, i+1, cur, cur) - else: - helper(res, path + "+" + str(cur), num, target, i+1, prev + cur, cur) - helper(res, path + "-" + str(cur), num, target, i+1, prev - cur, -cur) - helper(res, path + "*" + str(cur), num, target, i+1, prev - multed + multed * cur, multed * cur) - - -# "123", 6 -> ["1+2+3", "1*2*3"] -s = "123" -target = 6 -print(add_operator(s, target)) -# "232", 8 -> ["2*3+2", "2+3*2"] -s = "232" -target = 8 -print(add_operator(s, target)) - -s = "123045" -target = 3 -print(add_operator(s, target)) diff --git a/backtrack/general_solution.md b/backtrack/general_solution.md deleted file mode 100644 index 0c0ee0c6f..000000000 --- a/backtrack/general_solution.md +++ /dev/null @@ -1,156 +0,0 @@ -This structure might apply to many other backtracking questions, but here I am just going to demonstrate Subsets, Permutations, and Combination Sum. - -# Subsets : https://leetcode.com/problems/subsets/ - -public List> subsets(int[] nums) { - List> list = new ArrayList<>(); - Arrays.sort(nums); - backtrack(list, new ArrayList<>(), nums, 0); - return list; -} - -private void backtrack(List> list , List tempList, int [] nums, int start){ - list.add(new ArrayList<>(tempList)); - for(int i = start; i < nums.length; i++){ - tempList.add(nums[i]); - backtrack(list, tempList, nums, i + 1); - tempList.remove(tempList.size() - 1); - } -} - -# Subsets II (contains duplicates) : https://leetcode.com/problems/subsets-ii/ - -public List> subsetsWithDup(int[] nums) { - List> list = new ArrayList<>(); - Arrays.sort(nums); - backtrack(list, new ArrayList<>(), nums, 0); - return list; -} - -private void backtrack(List> list, List tempList, int [] nums, int start){ - list.add(new ArrayList<>(tempList)); - for(int i = start; i < nums.length; i++){ - if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates - tempList.add(nums[i]); - backtrack(list, tempList, nums, i + 1); - tempList.remove(tempList.size() - 1); - } -} - -# Permutations : https://leetcode.com/problems/permutations/ - -public List> permute(int[] nums) { - List> list = new ArrayList<>(); - // Arrays.sort(nums); // not necessary - backtrack(list, new ArrayList<>(), nums); - return list; -} - -private void backtrack(List> list, List tempList, int [] nums){ - if(tempList.size() == nums.length){ - list.add(new ArrayList<>(tempList)); - } else{ - for(int i = 0; i < nums.length; i++){ - if(tempList.contains(nums[i])) continue; // element already exists, skip - tempList.add(nums[i]); - backtrack(list, tempList, nums); - tempList.remove(tempList.size() - 1); - } - } -} - -# Permutations II (contains duplicates) : https://leetcode.com/problems/permutations-ii/ - -public List> permuteUnique(int[] nums) { - List> list = new ArrayList<>(); - Arrays.sort(nums); - backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]); - return list; -} - -private void backtrack(List> list, List tempList, int [] nums, boolean [] used){ - if(tempList.size() == nums.length){ - list.add(new ArrayList<>(tempList)); - } else{ - for(int i = 0; i < nums.length; i++){ - if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue; - used[i] = true; - tempList.add(nums[i]); - backtrack(list, tempList, nums, used); - used[i] = false; - tempList.remove(tempList.size() - 1); - } - } -} - -# Combination Sum : https://leetcode.com/problems/combination-sum/ - -public List> combinationSum(int[] nums, int target) { - List> list = new ArrayList<>(); - Arrays.sort(nums); - backtrack(list, new ArrayList<>(), nums, target, 0); - return list; -} - -private void backtrack(List> list, List tempList, int [] nums, int remain, int start){ - if(remain < 0) return; - else if(remain == 0) list.add(new ArrayList<>(tempList)); - else{ - for(int i = start; i < nums.length; i++){ - tempList.add(nums[i]); - backtrack(list, tempList, nums, remain - nums[i], i); // not i + 1 because we can reuse same elements - tempList.remove(tempList.size() - 1); - } - } -} -# Combination Sum II (can't reuse same element) : https://leetcode.com/problems/combination-sum-ii/ - -public List> combinationSum2(int[] nums, int target) { - List> list = new ArrayList<>(); - Arrays.sort(nums); - backtrack(list, new ArrayList<>(), nums, target, 0); - return list; - -} - -private void backtrack(List> list, List tempList, int [] nums, int remain, int start){ - if(remain < 0) return; - else if(remain == 0) list.add(new ArrayList<>(tempList)); - else{ - for(int i = start; i < nums.length; i++){ - if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates - tempList.add(nums[i]); - backtrack(list, tempList, nums, remain - nums[i], i + 1); - tempList.remove(tempList.size() - 1); - } - } -} - - -# Palindrome Partitioning : https://leetcode.com/problems/palindrome-partitioning/ - -public List> partition(String s) { - List> list = new ArrayList<>(); - backtrack(list, new ArrayList<>(), s, 0); - return list; -} - -public void backtrack(List> list, List tempList, String s, int start){ - if(start == s.length()) - list.add(new ArrayList<>(tempList)); - else{ - for(int i = start; i < s.length(); i++){ - if(isPalindrome(s, start, i)){ - tempList.add(s.substring(start, i + 1)); - backtrack(list, tempList, s, i + 1); - tempList.remove(tempList.size() - 1); - } - } - } -} - -public boolean isPalindrome(String s, int low, int high){ - while(low < high) - if(s.charAt(low++) != s.charAt(high--)) return false; - return true; -} diff --git a/backtrack/generate_abbreviations.py b/backtrack/generate_abbreviations.py deleted file mode 100644 index 929c33994..000000000 --- a/backtrack/generate_abbreviations.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -given input word, return the list of abbreviations. -ex) -word => [1ord, w1rd, wo1d, w2d, 3d, w3 ... etc] -""" - - -def generate_abbreviations(word): - result = [] - backtrack(result, word, 0, 0, "") - return result - - -def backtrack(result, word, pos, count, cur): - if pos == len(word): - if count > 0: - cur += str(count) - result.append(cur) - return - - if count > 0: # add the current word - backtrack(result, word, pos+1, 0, cur+str(count)+word[pos]) - else: - backtrack(result, word, pos+1, 0, cur+word[pos]) - # skip the current word - backtrack(result, word, pos+1, count+1, cur) diff --git a/backtrack/generate_parenthesis.py b/backtrack/generate_parenthesis.py deleted file mode 100644 index 6dac22c94..000000000 --- a/backtrack/generate_parenthesis.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Given n pairs of parentheses, write a function to generate -all combinations of well-formed parentheses. - -For example, given n = 3, a solution set is: - -[ - "((()))", - "(()())", - "(())()", - "()(())", - "()()()" -] -""" - -def gen_parenthesis(n:"int")->"List[str]": - res = [] - add_pair(res, "", n, 0) - return res - -def add_pair(res, s, left, right): - if left == 0 and right == 0: - res.append(s) - return - if right > 0: - add_pair(res, s+")", left, right-1) - if left > 0: - add_pair(res, s+"(", left-1, right+1) - - -if __name__=="__main__": - print(gen_parenthesis(3)) diff --git a/backtrack/palindrome_partitioning.py b/backtrack/palindrome_partitioning.py deleted file mode 100644 index 21500c180..000000000 --- a/backtrack/palindrome_partitioning.py +++ /dev/null @@ -1,32 +0,0 @@ -""" It looks like you need to be looking not for all palindromic substrings, -but rather for all the ways you can divide the input string up into palindromic substrings. -(There's always at least one way, since one-character substrings are always palindromes.) -""" - -def palindromic_substrings(s): - if not s: - return [[]] - results = [] - for i in range(len(s), 0, -1): - sub = s[:i] - if sub == sub[::-1]: - for rest in palindromic_substrings(s[i:]): - results.append([sub] + rest) - return results - -""" There's two loops. -The outer loop checks each length of initial substring (in descending length order) to see -if it is a palindrome. If so, it recurses on the rest of the string and loops over the returned -values, adding the initial substring to each item before adding it to the results. -""" -# A slightly more Pythonic approach would be to make a recursive generator: - -def palindromic_substrings(s): - if not s: - yield [] - return - for i in range(len(s), 0, -1): - sub = s[:i] - if sub == sub[::-1]: - for rest in palindromic_substrings(s[i:]): - yield [sub] + rest diff --git a/backtrack/pattern_match.py b/backtrack/pattern_match.py deleted file mode 100644 index 532dfb9c2..000000000 --- a/backtrack/pattern_match.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Given a pattern and a string str, -find if str follows the same pattern. - -Here follow means a full match, such that there is a bijection between -a letter in pattern and a non-empty substring in str. - -Examples: -pattern = "abab", str = "redblueredblue" should return true. -pattern = "aaaa", str = "asdasdasdasd" should return true. -pattern = "aabb", str = "xyzabcxzyabc" should return false. -Notes: -You may assume both pattern and str contains only lowercase letters. -""" - - -def pattern_match(pattern, string): - """ - :type pattern: str - :type string: str - :rtype: bool - """ - return backtrack(pattern, string, {}) - - -def backtrack(pattern, string, dic): - print(dic) - if len(pattern) == 0 and len(string) > 0: - return False - if len(pattern) == len(string) == 0: - return True - for end in range(1, len(string)-len(pattern)+2): - if pattern[0] not in dic and string[:end] not in dic.values(): - dic[pattern[0]] = string[:end] - if backtrack(pattern[1:], string[end:], dic): - return True - del dic[pattern[0]] - elif pattern[0] in dic and dic[pattern[0]] == string[:end]: - if backtrack(pattern[1:], string[end:], dic): - return True - return False - -if __name__ == "__main__": - pattern1 = "abab" - string1 = "redblueredblue" - pattern2 = "aaaa" - string2 = "asdasdasdasd" - pattern3 = "aabb" - string3 = "xyzabcxzyabc" - print(pattern_match(pattern1, string1)) - print(pattern_match(pattern2, string2)) - print(pattern_match(pattern3, string3)) diff --git a/backtrack/permute.py b/backtrack/permute.py deleted file mode 100644 index 397597230..000000000 --- a/backtrack/permute.py +++ /dev/null @@ -1,40 +0,0 @@ -# Given a collection of distinct numbers, return all possible permutations. - -# For example, -# [1,2,3] have the following permutations: -# [ - # [1,2,3], - # [1,3,2], - # [2,1,3], - # [2,3,1], - # [3,1,2], - # [3,2,1] -# ] - -def permute(nums): - perms = [[]] - for n in nums: - new_perms = [] - for perm in perms: - for i in range(len(perm)+1): - new_perms.append(perm[:i] + [n] + perm[i:]) ###insert n - print(i, perm[:i], [n], perm[i:], ">>>>", new_perms) - perms = new_perms - return perms - -# DFS Version -# def permute(nums): - # res = [] - # dfs(res, nums, []) - # return res - -# def dfs(res, nums, path): - # if not nums: - # res.append(path) - # for i in range(len(nums)): - # print(nums[:i]+nums[i+1:]) - # dfs(res, nums[:i]+nums[i+1:], path+[nums[i]]) - -test = [1,2,3] -print(test) -print(permute(test)) diff --git a/backtrack/permute_unique.py b/backtrack/permute_unique.py deleted file mode 100644 index 911d40c76..000000000 --- a/backtrack/permute_unique.py +++ /dev/null @@ -1,24 +0,0 @@ -# Given a collection of numbers that might contain duplicates, -# return all possible unique permutations. - -# For example, -# [1,1,2] have the following unique permutations: -# [ - # [1,1,2], - # [1,2,1], - # [2,1,1] -# ] - -def permute_unique(nums): - perms = [[]] - for n in nums: - new_perms = [] - for l in perms: - for i in range(len(l)+1): - new_perms.append(l[:i]+[n]+l[i:]) - if i= len(nums): - # res.append(cur) - # else: - # backtrack(res, nums, cur+[nums[pos]], pos+1) - # backtrack(res, nums, cur, pos+1) - - -# Iteratively -def subsets2(self, nums): - res = [[]] - for num in sorted(nums): - res += [item+[num] for item in res] - return res - -test = [1,2,3] -print(test) -print(subsets(test)) diff --git a/backtrack/subsets_unique.py b/backtrack/subsets_unique.py deleted file mode 100644 index fac0cb0c2..000000000 --- a/backtrack/subsets_unique.py +++ /dev/null @@ -1,38 +0,0 @@ -# Given a collection of integers that might contain duplicates, nums, -# return all possible subsets. - -# Note: The solution set must not contain duplicate subsets. - -# For example, -# If nums = [1,2,2], a solution is: - -# [ - # [2], - # [1], - # [1,2,2], - # [2,2], - # [1,2], - # [] -# ] - -def subsets_unique(nums): - res = set() - backtrack(res, nums, [], 0) - return list(res) - -def backtrack(res, nums, stack, pos): - if pos == len(nums): - res.add(tuple(stack)) - else: - # take - stack.append(nums[pos]) - backtrack(res, nums, stack, pos+1) - stack.pop() - - # don't take - backtrack(res, nums, stack, pos+1) - - -test = [1,2,2] -print(test) -print(subsets_unique(test)) diff --git a/backtrack/word_search.py b/backtrack/word_search.py deleted file mode 100644 index 989e5b29c..000000000 --- a/backtrack/word_search.py +++ /dev/null @@ -1,111 +0,0 @@ -import unittest -''' -Given a matrix of words and a list of words to search, return a list of words that exists in the board -This is Word Search II on LeetCode - -board = [ - ['o','a','a','n'], - ['e','t','a','e'], - ['i','h','k','r'], - ['i','f','l','v'] - ] - -words = ["oath","pea","eat","rain"] - -''' - -def find_words(board, words): - # make a trie structure that is essentially dictionaries of dictionaries that map each character to a potential next character - trie = {} - for word in words: - curr_trie = trie - for char in word: - if char not in curr_trie: - curr_trie[char] = {} - curr_trie = curr_trie[char] - curr_trie['#'] = '#' - - # result is a set of found words since we do not want repeats - result = set() - used = [[False]*len(board[0]) for _ in range(len(board))] - - for i in range(len(board)): - for j in range(len(board[0])): - backtrack(board, i, j, trie, '', used, result) - return list(result) - -''' -backtrack tries to build each words from the board and return all words found -@param: board, the passed in board of characters -@param: i, the row index -@param: j, the column index -@param: trie, a trie of the passed in words -@param: pre, a buffer of currently build string that differs by recursion stack -@param: used, a replica of the board except in booleans to state whether a character has been used -@param: result, the resulting set that contains all words found - -@return: list of words found -''' - -def backtrack(board, i, j, trie, pre, used, result): - if '#' in trie: - result.add(pre) - - if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]): - return - - if not used[i][j] and board[i][j] in trie: - used[i][j]=True - backtrack(board,i+1,j,trie[board[i][j]],pre+board[i][j], used, result) - backtrack(board,i,j+1,trie[board[i][j]],pre+board[i][j], used, result) - backtrack(board,i-1,j,trie[board[i][j]],pre+board[i][j], used, result) - backtrack(board,i,j-1,trie[board[i][j]],pre+board[i][j], used, result) - used[i][j]=False - -class MyTests(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"] - self.assertEqual(find_words(board, words), ['oath', 'eat']) - - 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) - -if __name__=="__main__": - unittest.main() diff --git a/calculator/math_parser.py b/calculator/math_parser.py deleted file mode 100644 index 830d71794..000000000 --- a/calculator/math_parser.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -Contributed by izanbf1803. - -Example: -------------------------------------------------------------------------------------------------- - Code: - | exp = "2452 * (3 * 6.5 + 1) * 6 / 235" - | print("Expression:", exp) - | print("Parsed expression:", mp.parse(exp)) - | print("Evaluation result:", mp.evaluate(exp)) - - Output: - | Expression: 2452 * (3 * 6 + 1) * 6 / 235 - | Parsed expression: ['2452', '*', '(', '3', '*', '6', '+', '1', ')', '*', '6', '/', '235'] - | Evaluation result: 1189.4808510638297 -------------------------------------------------------------------------------------------------- - -Now added '^' operator for exponents. (by @goswami-rahul) -""" - -from collections import deque -import re - -numeric_value = re.compile('\d+(\.\d+)?') - -__operators__ = "+-/*^" -__parenthesis__ = "()" -__priority__ = { - '+': 0, - '-': 0, - '*': 1, - '/': 1, - '^': 2 -} - -def is_operator(token): - """ - Check if token it's a operator - - token Char: Token - """ - return token in __operators__ - -def higher_priority(op1, op2): - """ - Check if op1 have higher priority than op2 - - op1 Char: Operation Token 1 - op2 Char: Operation Token 2 - """ - return __priority__[op1] >= __priority__[op2] - -def calc(n2, n1, operator): - """ - Calculate operation result - - n2 Number: Number 2 - n1 Number: Number 1 - operator Char: Operation to calculate - """ - if operator == '-': return n1 - n2 - elif operator == '+': return n1 + n2 - elif operator == '*': return n1 * n2 - elif operator == '/': return n1 / n2 - elif operator == '^': return n1 ** n2 - return 0 - -def apply_operation(op_stack, out_stack): - """ - Apply operation to the first 2 items of the output queue - - op_stack Deque (reference) - out_stack Deque (reference) - """ - out_stack.append(calc(out_stack.pop(), out_stack.pop(), op_stack.pop())) - -def parse(expression): - """ - Return array of parsed tokens in the expression - - expression String: Math expression to parse in infix notation - """ - result = [] - current = "" - for i in expression: - if i.isdigit() or i == '.': - current += i - else: - if len(current) > 0: - result.append(current) - current = "" - if i in __operators__ or i in __parenthesis__: - result.append(i) - else: - raise Exception("invalid syntax " + i) - - if len(current) > 0: - result.append(current) - return result - -def evaluate(expression): - """ - Calculate result of expression - - expression String: The expression - type Type (optional): Number type [int, float] - """ - op_stack = deque() # operator stack - out_stack = deque() # output stack (values) - tokens = parse(expression) # calls the function only once! - for token in tokens: - if numeric_value.match(token): - out_stack.append(float(token)) - elif token == '(': - op_stack.append(token) - elif token == ')': - while len(op_stack) > 0 and op_stack[-1] != '(': - apply_operation(op_stack, out_stack) - op_stack.pop() # Remove remaining '(' - else: # is_operator(token) - while len(op_stack) > 0 and is_operator(op_stack[-1]) and higher_priority(op_stack[-1], token): - apply_operation(op_stack, out_stack) - op_stack.append(token) - - while len(op_stack) > 0: - apply_operation(op_stack, out_stack) - - return out_stack[-1] - - -def main(): - """ - simple user-interface - """ - print("\t\tCalculator\n\n") - user_input = input("expression or exit: ") - while user_input != "exit": - try: - print("The result is {0}".format(evaluate(user_input))) - except Exception: - print("invalid syntax!") - user_input = input("expression or exit: ") - print("program end") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/dfs/walls_and_gates.py b/dfs/walls_and_gates.py deleted file mode 100644 index 7d10b4d6e..000000000 --- a/dfs/walls_and_gates.py +++ /dev/null @@ -1,22 +0,0 @@ - - -# fill the empty room with distance to its nearest gate - - -def walls_and_gates(rooms): - 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, i, j, depth): - if (i < 0 or i >= len(rooms)) or (j < 0 or j >= len(rooms[0])): - return # out of bounds - if rooms[i][j] < depth: - return # crossed - 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) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..06f6415a1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = algorithms +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..a515cfe0d --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=algorithms + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..3f4e532f7 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,21 @@ +# Progress bars on iterators +tqdm +sphinx_rtd_theme + +# Downloading data and other files +requests + +# Required for tests only: + +# Style-checking for PEP8 +flake8 + +# Run unit tests +pytest + +# Lets pytest find our code by automatically modifying PYTHONPATH +pytest-pythonpath + +# Coverage statistics +pytest-cov +codecov diff --git a/docs/source/_static/algorithms_logo.png b/docs/source/_static/algorithms_logo.png new file mode 100644 index 000000000..2e8487d9e Binary files /dev/null and b/docs/source/_static/algorithms_logo.png differ diff --git a/docs/source/_static/logo/128pxblack.png b/docs/source/_static/logo/128pxblack.png new file mode 100644 index 000000000..d18f07fd4 Binary files /dev/null and b/docs/source/_static/logo/128pxblack.png differ diff --git a/docs/source/_static/logo/128pxblack.svg b/docs/source/_static/logo/128pxblack.svg new file mode 100644 index 000000000..bff184164 --- /dev/null +++ b/docs/source/_static/logo/128pxblack.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/docs/source/_static/logo/128pxblue.png b/docs/source/_static/logo/128pxblue.png new file mode 100644 index 000000000..b94a8ca38 Binary files /dev/null and b/docs/source/_static/logo/128pxblue.png differ diff --git a/docs/source/_static/logo/128pxblue.svg b/docs/source/_static/logo/128pxblue.svg new file mode 100644 index 000000000..be4cd88f3 --- /dev/null +++ b/docs/source/_static/logo/128pxblue.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/docs/source/_static/logo/128pxorange.png b/docs/source/_static/logo/128pxorange.png new file mode 100644 index 000000000..683883494 Binary files /dev/null and b/docs/source/_static/logo/128pxorange.png differ diff --git a/docs/source/_static/logo/128pxorange.svg b/docs/source/_static/logo/128pxorange.svg new file mode 100644 index 000000000..4fb4476f7 --- /dev/null +++ b/docs/source/_static/logo/128pxorange.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/docs/source/_static/logo/256pxblack.png b/docs/source/_static/logo/256pxblack.png new file mode 100644 index 000000000..4c1f054d5 Binary files /dev/null and b/docs/source/_static/logo/256pxblack.png differ diff --git a/docs/source/_static/logo/256pxblack.svg b/docs/source/_static/logo/256pxblack.svg new file mode 100644 index 000000000..43d314751 --- /dev/null +++ b/docs/source/_static/logo/256pxblack.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/docs/source/_static/logo/256pxblue.png b/docs/source/_static/logo/256pxblue.png new file mode 100644 index 000000000..464efa3f9 Binary files /dev/null and b/docs/source/_static/logo/256pxblue.png differ diff --git a/docs/source/_static/logo/256pxblue.svg b/docs/source/_static/logo/256pxblue.svg new file mode 100644 index 000000000..d85a6f781 --- /dev/null +++ b/docs/source/_static/logo/256pxblue.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/docs/source/_static/logo/256pxorange.png b/docs/source/_static/logo/256pxorange.png new file mode 100644 index 000000000..6eb98e422 Binary files /dev/null and b/docs/source/_static/logo/256pxorange.png differ diff --git a/docs/source/_static/logo/256pxorange.svg b/docs/source/_static/logo/256pxorange.svg new file mode 100644 index 000000000..99fc6d6be --- /dev/null +++ b/docs/source/_static/logo/256pxorange.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/docs/source/_static/logo/512pxblack.png b/docs/source/_static/logo/512pxblack.png new file mode 100644 index 000000000..7254ef341 Binary files /dev/null and b/docs/source/_static/logo/512pxblack.png differ diff --git a/docs/source/_static/logo/512pxblack.svg b/docs/source/_static/logo/512pxblack.svg new file mode 100644 index 000000000..90b4aabc1 --- /dev/null +++ b/docs/source/_static/logo/512pxblack.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/docs/source/_static/logo/512pxblue.png b/docs/source/_static/logo/512pxblue.png new file mode 100644 index 000000000..f0b951af8 Binary files /dev/null and b/docs/source/_static/logo/512pxblue.png differ diff --git a/docs/source/_static/logo/512pxblue.svg b/docs/source/_static/logo/512pxblue.svg new file mode 100644 index 000000000..9cae68895 --- /dev/null +++ b/docs/source/_static/logo/512pxblue.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/docs/source/_static/logo/512pxorange.png b/docs/source/_static/logo/512pxorange.png new file mode 100644 index 000000000..0f00865bf Binary files /dev/null and b/docs/source/_static/logo/512pxorange.png differ diff --git a/docs/source/_static/logo/512pxorange.svg b/docs/source/_static/logo/512pxorange.svg new file mode 100644 index 000000000..185fafdeb --- /dev/null +++ b/docs/source/_static/logo/512pxorange.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/docs/source/_static/logo/add b/docs/source/_static/logo/add new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs/source/_static/logo/add @@ -0,0 +1 @@ + diff --git a/docs/source/_static/logo/logotype1black.png b/docs/source/_static/logo/logotype1black.png new file mode 100644 index 000000000..8ff176ca3 Binary files /dev/null and b/docs/source/_static/logo/logotype1black.png differ diff --git a/docs/source/_static/logo/logotype1black.svg b/docs/source/_static/logo/logotype1black.svg new file mode 100644 index 000000000..81f7b4b29 --- /dev/null +++ b/docs/source/_static/logo/logotype1black.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/logo/logotype1blue.png b/docs/source/_static/logo/logotype1blue.png new file mode 100644 index 000000000..5cc48851f Binary files /dev/null and b/docs/source/_static/logo/logotype1blue.png differ diff --git a/docs/source/_static/logo/logotype1blue.svg b/docs/source/_static/logo/logotype1blue.svg new file mode 100644 index 000000000..2cd790cd0 --- /dev/null +++ b/docs/source/_static/logo/logotype1blue.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/logo/logotype1orange.png b/docs/source/_static/logo/logotype1orange.png new file mode 100644 index 000000000..87a706058 Binary files /dev/null and b/docs/source/_static/logo/logotype1orange.png differ diff --git a/docs/source/_static/logo/logotype1orange.svg b/docs/source/_static/logo/logotype1orange.svg new file mode 100644 index 000000000..45e0e10cf --- /dev/null +++ b/docs/source/_static/logo/logotype1orange.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/logo/logotype2black.png b/docs/source/_static/logo/logotype2black.png new file mode 100644 index 000000000..b5929b89a Binary files /dev/null and b/docs/source/_static/logo/logotype2black.png differ diff --git a/docs/source/_static/logo/logotype2black.svg b/docs/source/_static/logo/logotype2black.svg new file mode 100644 index 000000000..8f2307fe1 --- /dev/null +++ b/docs/source/_static/logo/logotype2black.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/logo/logotype2blue.png b/docs/source/_static/logo/logotype2blue.png new file mode 100644 index 000000000..b73257ca7 Binary files /dev/null and b/docs/source/_static/logo/logotype2blue.png differ diff --git a/docs/source/_static/logo/logotype2blue.svg b/docs/source/_static/logo/logotype2blue.svg new file mode 100644 index 000000000..57f1aa5fd --- /dev/null +++ b/docs/source/_static/logo/logotype2blue.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/logo/logotype2orange.png b/docs/source/_static/logo/logotype2orange.png new file mode 100644 index 000000000..fc5ab676e Binary files /dev/null and b/docs/source/_static/logo/logotype2orange.png differ diff --git a/docs/source/_static/logo/logotype2orange.svg b/docs/source/_static/logo/logotype2orange.svg new file mode 100644 index 000000000..bdd4c2d41 --- /dev/null +++ b/docs/source/_static/logo/logotype2orange.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/source/arrays.rst b/docs/source/arrays.rst new file mode 100644 index 000000000..5e5623f5c --- /dev/null +++ b/docs/source/arrays.rst @@ -0,0 +1,16 @@ +.. role:: hidden + :class: hidden-section + +algorithms.arrays +================= + +.. automodule:: algorithms.arrays +.. currentmodule:: algorithms.arrays + +longest_non_repeat +------------------ + +:hidden:`longest_non_repeat_v1` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: longest_non_repeat_v1 diff --git a/docs/source/backtrack.rst b/docs/source/backtrack.rst new file mode 100644 index 000000000..0c33a3316 --- /dev/null +++ b/docs/source/backtrack.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.backtrack +==================== diff --git a/docs/source/bfs.rst b/docs/source/bfs.rst new file mode 100644 index 000000000..699b9e925 --- /dev/null +++ b/docs/source/bfs.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.bfs +================= diff --git a/docs/source/bit.rst b/docs/source/bit.rst new file mode 100644 index 000000000..b7aa877c0 --- /dev/null +++ b/docs/source/bit.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.bit +================= diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..768277d1b --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# algorithms documentation build configuration file, created by +# sphinx-quickstart on Wed Jun 6 01:17:26 2018. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +from recommonmark.parser import CommonMarkParser + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_parsers = { + '.md': CommonMarkParser +} +source_suffix = ['.rst', '.md'] + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'algorithms' +copyright = '2018, Algorithms Team & Contributors' +author = 'Algorithms Team & Contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1.0' +# The full version, including alpha/beta/rc tags. +release = '0.1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'searchbox.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'algorithmsdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'algorithms.tex', 'algorithms Documentation', + 'Algorithms Team \\& Contributors', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'algorithms', 'algorithms Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'algorithms', 'algorithms Documentation', + author, 'algorithms', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/docs/source/dfs.rst b/docs/source/dfs.rst new file mode 100644 index 000000000..1d2c5b6de --- /dev/null +++ b/docs/source/dfs.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.dfs +================= diff --git a/docs/source/dp.rst b/docs/source/dp.rst new file mode 100644 index 000000000..1cc92081e --- /dev/null +++ b/docs/source/dp.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.dp +================= diff --git a/docs/source/examples.rst b/docs/source/examples.rst new file mode 100644 index 000000000..5d3cbdd76 --- /dev/null +++ b/docs/source/examples.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +Examples +================= diff --git a/docs/source/graph.rst b/docs/source/graph.rst new file mode 100644 index 000000000..925d10524 --- /dev/null +++ b/docs/source/graph.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.graph +================= diff --git a/docs/source/heap.rst b/docs/source/heap.rst new file mode 100644 index 000000000..068578a3e --- /dev/null +++ b/docs/source/heap.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.heap +================= diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..d751ceea4 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,38 @@ +.. image:: /_static/algorithms_logo.png + :target: https://github.com/keon/algorithms + :scale: 50 % + +The :mod:`algorithms` package consists of +minimal and clean example implementations of data structures and algorithms. + +.. toctree:: + :maxdepth: 2 + :caption: Package Reference + + self + algorithms.arrays + algorithms.backtrack + algorithms.bfs + algorithms.bit + algorithms.dfs + algorithms.dp + algorithms.graph + algorithms.heap + algorithms.linkedlist + algorithms.map + algorithms.maths + algorithms.matrix + algorithms.queues + algorithms.search + algorithms.set + algorithms.sort + algorithms.stack + algorithms.strings + algorithms.tree + examples + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` diff --git a/docs/source/linkedlist.rst b/docs/source/linkedlist.rst new file mode 100644 index 000000000..4a37b37e5 --- /dev/null +++ b/docs/source/linkedlist.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.linkedlist +===================== diff --git a/docs/source/map.rst b/docs/source/map.rst new file mode 100644 index 000000000..31d281f85 --- /dev/null +++ b/docs/source/map.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.map +================= diff --git a/docs/source/maths.rst b/docs/source/maths.rst new file mode 100644 index 000000000..1a45b7957 --- /dev/null +++ b/docs/source/maths.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.maths +================= diff --git a/docs/source/matrix.rst b/docs/source/matrix.rst new file mode 100644 index 000000000..4d06e70a4 --- /dev/null +++ b/docs/source/matrix.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.matrix +================= diff --git a/docs/source/queues.rst b/docs/source/queues.rst new file mode 100644 index 000000000..3ee9e18a7 --- /dev/null +++ b/docs/source/queues.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.queue +================= diff --git a/docs/source/search.rst b/docs/source/search.rst new file mode 100644 index 000000000..091f0bf05 --- /dev/null +++ b/docs/source/search.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.search +================= diff --git a/docs/source/set.rst b/docs/source/set.rst new file mode 100644 index 000000000..80984858b --- /dev/null +++ b/docs/source/set.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.set +================= diff --git a/docs/source/sort.rst b/docs/source/sort.rst new file mode 100644 index 000000000..0b106e37b --- /dev/null +++ b/docs/source/sort.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.sort +================= diff --git a/docs/source/stack.rst b/docs/source/stack.rst new file mode 100644 index 000000000..ad1f76525 --- /dev/null +++ b/docs/source/stack.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.stack +================= diff --git a/docs/source/strings.rst b/docs/source/strings.rst new file mode 100644 index 000000000..708df2a0b --- /dev/null +++ b/docs/source/strings.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.string +================= diff --git a/docs/source/tree.rst b/docs/source/tree.rst new file mode 100644 index 000000000..1dea821b4 --- /dev/null +++ b/docs/source/tree.rst @@ -0,0 +1,5 @@ +.. role:: hidden + :class: hidden-section + +algorithms.tree +================= diff --git a/dp/climbing_stairs.py b/dp/climbing_stairs.py deleted file mode 100644 index 6f5f3b697..000000000 --- a/dp/climbing_stairs.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -You are climbing a stair case. -It takes n steps to reach to the top. - -Each time you can either climb 1 or 2 steps. -In how many distinct ways can you climb to the top? - -Note: Given n will be a positive integer. -""" - - -# O(n) space - -def climb_stairs(n): - """ - :type n: int - :rtype: int - """ - arr = [1, 1] - for i in range(2, n+1): - arr.append(arr[-1] + arr[-2]) - return arr[-1] - - -# the above function can be optimized as: -# O(1) space - -def climb_stairs_optimized(n): - a = b = 1 - for _ in range(n): - a, b = b, a + b - return a diff --git a/dp/coin_change.py b/dp/coin_change.py deleted file mode 100644 index dce9bb57a..000000000 --- a/dp/coin_change.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Problem -Given a value N, if we want to make change for N cents, and we have infinite supply of each of -S = { S1, S2, .. , Sm} valued //coins, how many ways can we make the change? -The order of coins doesn’t matter. -For example, for N = 4 and S = [1, 2, 3], there are four solutions: -[1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]. -So output should be 4. - -For N = 10 and S = [2, 5, 3, 6], there are five solutions: -[2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5]. -So the output should be 5. -""" - -def count(s, n): - # We need n+1 rows as the table is consturcted in bottom up - # manner using the base case 0 value case (n = 0) - m = len(s) - table = [[0 for x in range(m)] for x in range(n+1)] - - # Fill the enteries for 0 value case (n = 0) - for i in range(m): - table[0][i] = 1 - - # Fill rest of the table enteries in bottom up manner - for i in range(1, n+1): - for j in range(m): - # Count of solutions including S[j] - x = table[i - s[j]][j] if i-s[j] >= 0 else 0 - - # Count of solutions excluding S[j] - y = table[i][j-1] if j >= 1 else 0 - - # total count - table[i][j] = x + y - - return table[n][m-1] - - -if __name__ == '__main__': - - coins = [1, 2, 3] - n = 4 - assert count(coins, n) == 4 - - coins = [2, 5, 3, 6] - n = 10 - assert count(coins, n) == 5 diff --git a/dp/combination_sum.py b/dp/combination_sum.py deleted file mode 100644 index 67bc6aff7..000000000 --- a/dp/combination_sum.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Given an integer array with all positive numbers and no duplicates, -find the number of possible combinations that -add up to a positive integer target. - -Example: - -nums = [1, 2, 3] -target = 4 - -The possible combination ways are: -(1, 1, 1, 1) -(1, 1, 2) -(1, 2, 1) -(1, 3) -(2, 1, 1) -(2, 2) -(3, 1) - -Note that different sequences are counted as different combinations. - -Therefore the output is 7. -Follow up: -What if negative numbers are allowed in the given array? -How does it change the problem? -What limitation we need to add to the question to allow negative numbers? - -""" - -dp = None - - -def helper_topdown(nums, target): - global dp - if dp[target] != -1: - return dp[target] - res = 0 - for i in range(0, len(nums)): - if target >= nums[i]: - res += helper_topdown(nums, target - nums[i]) - dp[target] = res - return res - - -def combination_sum_topdown(nums, target): - global dp - dp = [-1] * (target + 1) - dp[0] = 1 - return helper_topdown(nums, target) - - -# EDIT: The above solution is top-down. How about a bottom-up one? -def combination_sum_bottom_up(nums, target): - comb = [0] * (target + 1) - comb[0] = 1 - for i in range(0, len(comb)): - for j in range(len(nums)): - if i - nums[j] >= 0: - comb[i] += comb[i - nums[j]] - return comb[target] - - -combination_sum_topdown([1, 2, 3], 4) -print(dp[4]) - -print(combination_sum_bottom_up([1, 2, 3], 4)) diff --git a/dp/egg_drop.py b/dp/egg_drop.py deleted file mode 100644 index c087982c5..000000000 --- a/dp/egg_drop.py +++ /dev/null @@ -1,31 +0,0 @@ -# A Dynamic Programming based Python Program for the Egg Dropping Puzzle -INT_MAX = 32767 - -# Function to get minimum number of trials needed in worst -# case with n eggs and k floors -def egg_drop(n, k): - # A 2D table where entery eggFloor[i][j] will represent minimum - # number of trials needed for i eggs and j floors. - egg_floor = [[0 for x in range(k+1)] for x in range(n+1)] - - # We need one trial for one floor and0 trials for 0 floors - for i in range(1, n+1): - egg_floor[i][1] = 1 - egg_floor[i][0] = 0 - - # We always need j trials for one egg and j floors. - for j in range(1, k+1): - egg_floor[1][j] = j - - # Fill rest of the entries in table using optimal substructure - # property - 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 - - # eggFloor[n][k] holds the result - return egg_floor[n][k] diff --git a/dp/job_scheduling.py b/dp/job_scheduling.py deleted file mode 100644 index 7c7236820..000000000 --- a/dp/job_scheduling.py +++ /dev/null @@ -1,62 +0,0 @@ -# Python program for weighted job scheduling using Dynamic -# Programming and Binary Search - -# Class to represent a job -class Job: - def __init__(self, start, finish, profit): - self.start = start - self.finish = finish - self.profit = profit - - -# A Binary Search based function to find the latest job -# (before current job) that doesn't conflict with current -# job. "index" is index of the current job. This function -# returns -1 if all jobs before index conflict with it. -# The array jobs[] is sorted in increasing order of finish -# time. -def binary_search(job, start_index): - - # Initialize 'lo' and 'hi' for Binary Search - lo = 0 - hi = start_index - 1 - - # Perform binary Search iteratively - while lo <= hi: - mid = (lo + hi) // 2 - if job[mid].finish <= job[start_index].start: - if job[mid + 1].finish <= job[start_index].start: - lo = mid + 1 - else: - return mid - else: - hi = mid - 1 - return -1 - -# The main function that returns the maximum possible -# profit from given array of jobs -def schedule(job): - - # Sort jobs according to finish time - job = sorted(job, key = lambda j: j.finish) - - # Create an array to store solutions of subproblems. table[i] - # stores the profit for jobs till arr[i] (including arr[i]) - n = len(job) - table = [0 for _ in range(n)] - - table[0] = job[0].profit; - - # Fill entries in table[] using recursive property - for i in range(1, n): - - # Find profit including the current job - incl_prof = job[i].profit - l = binary_search(job, i) - if (l != -1): - incl_prof += table[l]; - - # Store maximum of including and excluding - table[i] = max(incl_prof, table[i - 1]) - - return table[n-1] diff --git a/dp/longest_increasing.py b/dp/longest_increasing.py deleted file mode 100644 index 5f6eb8369..000000000 --- a/dp/longest_increasing.py +++ /dev/null @@ -1,23 +0,0 @@ - - -def longest_increasing_subsequence(sequence): - """ - Dynamic Programming Algorithm for - counting the length of longest increasing subsequence - type sequence: List[int] - """ - 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) - print(counts) - return max(counts) - - -sequence = [1, 101, 10, 2, 3, 100, 4, 6, 2] -print("sequence: ", sequence) -print("output: ", longest_increasing_subsequence(sequence)) -print("answer: ", 5) - diff --git a/dp/regex_matching.py b/dp/regex_matching.py deleted file mode 100644 index 78b13c65c..000000000 --- a/dp/regex_matching.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Implement regular expression matching with support for '.' and '*'. - -'.' Matches any single character. -'*' Matches zero or more of the preceding element. - -The matching should cover the entire input string (not partial). - -The function prototype should be: -bool isMatch(const char *s, const char *p) - -Some examples: -isMatch("aa","a") → false -isMatch("aa","aa") → true -isMatch("aaa","aa") → false -isMatch("aa", "a*") → true -isMatch("aa", ".*") → true -isMatch("ab", ".*") → true -isMatch("aab", "c*a*b") → true -""" -import unittest - -class Solution(object): - def is_match(self, s, p): - m, n = len(s) + 1, len(p) + 1 - matches = [[False] * n for _ in range(m)] - - # Match empty string with empty pattern - matches[0][0] = True - - # Match empty string with .* - for i, element in enumerate(p[1:], 2): - matches[0][i] = matches[0][i - 2] and element == '*' - - for i, ss in enumerate(s, 1): - for j, pp in enumerate(p, 1): - if pp != '*': - # The previous character has matched and the current one - # has to be matched. Two possible matches: the same or . - matches[i][j] = matches[i - 1][j - 1] and \ - (ss == pp or pp == '.') - else: - # Horizontal look up [j - 2]. - # Not use the character before *. - matches[i][j] |= matches[i][j - 2] - - # Vertical look up [i - 1]. - # Use at least one character before *. - # p a b * - # s 1 0 0 0 - # a 0 1 0 1 - # b 0 0 1 1 - # b 0 0 0 ? - if ss == p[j - 2] or p[j - 2] == '.': - matches[i][j] |= matches[i - 1][j] - - return matches[-1][-1] - -class TestSolution(unittest.TestCase): - def test_none_0(self): - s = "" - p = "" - self.assertTrue(Solution().isMatch(s, p)) - - def test_none_1(self): - s = "" - p = "a" - self.assertFalse(Solution().isMatch(s, p)) - - def test_no_symbol_equal(self): - s = "abcd" - p = "abcd" - self.assertTrue(Solution().isMatch(s, p)) - - def test_no_symbol_not_equal_0(self): - s = "abcd" - p = "efgh" - self.assertFalse(Solution().isMatch(s, p)) - - def test_no_symbol_not_equal_1(self): - s = "ab" - p = "abb" - self.assertFalse(Solution().isMatch(s, p)) - - def test_symbol_0(self): - s = "" - p = "a*" - self.assertTrue(Solution().isMatch(s, p)) - - def test_symbol_1(self): - s = "a" - p = "ab*" - self.assertTrue(Solution().isMatch(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(Solution().isMatch(s, p)) - - -if __name__ == "__main__": - unittest.main() diff --git a/graph/Transitive_Closure_DFS.py b/graph/Transitive_Closure_DFS.py deleted file mode 100644 index 8e8e74da4..000000000 --- a/graph/Transitive_Closure_DFS.py +++ /dev/null @@ -1,52 +0,0 @@ -# This class represents a directed graph using adjacency -class Graph: - def __init__(self, vertices): - # No. of vertices - self.V = vertices - - # default dictionary to store graph - self.graph = {} - - # To store transitive closure - self.tc = [[0 for j in range(self.V)] for i in range(self.V)] - - # function to add an edge to graph - def add_edge(self, u, v): - if u in self.graph: - self.graph[u].append(v) - else: - self.graph[u] = [v] - - # A recursive DFS traversal function that finds - # all reachable vertices for s - def dfs_util(self, s, v): - - # Mark reachability from s to v as true. - self.tc[s][v] = 1 - - # Find all the vertices reachable through v - for i in self.graph[v]: - if self.tc[s][i] == 0: - self.dfs_util(s, i) - - # The function to find transitive closure. It uses - # recursive dfs_util() - def transitive_closure(self): - - # Call the recursive helper function to print DFS - # traversal starting from all vertices one by one - for i in range(self.V): - self.dfs_util(i, i) - print(self.tc) - - -g = 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) - -print("Transitive closure matrix is") -g.transitive_closure() diff --git a/graph/checkDiGraphStronglyConnected.py b/graph/checkDiGraphStronglyConnected.py deleted file mode 100644 index 25ae80cd2..000000000 --- a/graph/checkDiGraphStronglyConnected.py +++ /dev/null @@ -1,53 +0,0 @@ -from collections import defaultdict - -class Graph: - def __init__(self,v): - self.v = v - self.graph = defaultdict(list) - - def add_edge(self,u,v): - self.graph[u].append(v) - - def dfs(self): - visited = [False] * self.v - self.dfs_util(0,visited) - if visited == [True]*self.v: - return True - return False - - def dfs_util(self,i,visited): - visited[i] = True - for u in self.graph[i]: - if not(visited[u]): - self.dfs_util(u,visited) - - def reverse_graph(self): - g = Graph(self.v) - for i in range(len(self.graph)): - for j in self.graph[i]: - g.add_edge(j,i) - return g - - - def is_sc(self): - if self.dfs(): - gr = self.reverse_graph() - if gr.dfs(): - return True - return False - - -g1 = 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) -print ("Yes") if g1.is_sc() else print("No") - -g2 = Graph(4) -g2.add_edge(0, 1) -g2.add_edge(1, 2) -g2.add_edge(2, 3) -print ("Yes") if g2.is_sc() else print("No") diff --git a/graph/dijkstra.py b/graph/dijkstra.py deleted file mode 100644 index fe5772ec5..000000000 --- a/graph/dijkstra.py +++ /dev/null @@ -1,36 +0,0 @@ -#Dijkstra's single source shortest path algorithm - -class Graph(): - - def __init__(self, vertices): - self.vertices = vertices - self.graph = [[0 for column in range(vertices)] for row in range(vertices)] - - def min_distance(self, dist, min_dist_set): - min_dist = float("inf") - for v in range(self.vertices): - if dist[v] < min_dist and min_dist_set[v] == False: - min_dist = dist[v] - min_index = v - return min_index - - def dijkstra(self, src): - - dist = [float("inf")] * self.vertices - dist[src] = 0 - min_dist_set = [False] * self.vertices - - for count in range(self.vertices): - - #minimum distance vertex that is not processed - u = self.min_distance(dist, min_dist_set) - - #put minimum distance vertex in shortest tree - min_dist_set[u] = True - - #Update dist value of the adjacent vertices - for v in range(self.vertices): - if self.graph[u][v] > 0 and min_dist_set[v] == False and dist[v] > dist[u] + self.graph[u][v]: - dist[v] = dist[u] + self.graph[u][v] - - return dist diff --git a/graph/markov_chain.py b/graph/markov_chain.py deleted file mode 100644 index c588311d2..000000000 --- a/graph/markov_chain.py +++ /dev/null @@ -1,26 +0,0 @@ -import random - -my_chain = { - 'A': {'A': 0.6, - 'E': 0.4}, - 'E': {'A': 0.7, - 'E': 0.3} -} - -def __choose_state(state_map): - choice = random.random() - probability_reached = 0 - for state, probability in state_map.items(): - probability_reached += probability - if probability_reached > choice: - return state - -def next_state(chain, current_state): - next_state_map = chain.get(current_state) - next_state = __choose_state(next_state_map) - return next_state - -def iterating_markov_chain(chain, state): - while True: - state = next_state(chain, state) - yield state diff --git a/graph/minimum_spanning_tree.py b/graph/minimum_spanning_tree.py deleted file mode 100644 index 2a877f985..000000000 --- a/graph/minimum_spanning_tree.py +++ /dev/null @@ -1,130 +0,0 @@ -# Minimum spanning tree (MST) is going to use an undirected graph -# -# The disjoint set is represented with an list of integers where -# is the parent of the node at position . -# If = , it's a root, or a head, of a set - - -class Edge: - def __init__(self, u, v, weight): - self.u = u - self.v = v - self.weight = weight - - -class DisjointSet: - def __init__(self, n): - # Args: - # n (int): Number of vertices in the graph - - self.parent = [None] * n # Contains wich node is the parent of the node at poisition - self.size = [1] * n # Contains size of node at index , used to optimize merge - for i in range(n): - self.parent[i] = i # Make all nodes his own parent, creating n sets. - - def merge_set(self, a, b): - # Args: - # a, b (int): Indexes of nodes whose sets will be merged. - - # Get the set of nodes at position and - # If and are the roots, this will be constant O(1) - a = self.findSet(a) - b = self.findSet(b) - - # Join the shortest node to the longest, minimizing tree size (faster find) - if self.size[a] < self.size[b]: - self.parent[a] = b # Merge set(a) and set(b) - self.size[b] += self.size[a] # Add size of old set(a) to set(b) - else: - self.parent[b] = a # Merge set(b) and set(a) - self.size[a] += self.size[b] # Add size of old set(b) to set(a) - - def find_set(self, a): - if self.parent[a] != a: - # Very important, memoize result of the - # recursion in the list to optimize next - # calls and make this operation practically constant, O(1) - self.parent[a] = self.find_set(self.parent[a]) - - # node it's the set root, so we can return that index - return self.parent[a] - - -def kruskal(n, edges, ds): - # Args: - # n (int): Number of vertices in the graph - # edges (list of Edge): Edges of the graph - # ds (DisjointSet): DisjointSet of the vertices - # Returns: - # int: sum of weights of the minnimum spanning tree - # - # Kruskal algorithm: - # This algorithm will find the optimal graph with less edges and less - # total weight to connect all vertices (MST), the MST will always contain - # n-1 edges because it's the minimum required to connect n vertices. - # - # Procedure: - # Sort the edges (criteria: less weight). - # Only take edges of nodes in different sets. - # If we take a edge, we need to merge the sets to discard these. - # After repeat this until select n-1 edges, we will have the complete MST. - edges.sort(key=lambda edge: edge.weight) - - mst = [] # List of edges taken, minimum spanning tree - - for edge in edges: - set_u = ds.findSet(edge.u) # Set of the node - set_v = ds.findSet(edge.v) # Set of the node - if set_u != set_v: - ds.merge_set(set_u, set_v) - mst.append(edge) - if len(mst) == n-1: - # If we have selected n-1 edges, all the other - # edges will be discarted, so, we can stop here - break - - return sum([edge.weight for edge in mst]) - - - - -if __name__ == "__main__": - # Test. How input works: - # Input consists of different weighted, connected, undirected graphs. - # line 1: - # integers n, m - # lines 2..m+2: - # edge with the format -> node index u, node index v, integer weight - # - # Samples of input: - # - # 5 6 - # 1 2 3 - # 1 3 8 - # 2 4 5 - # 3 4 2 - # 3 5 4 - # 4 5 6 - # - # 3 3 - # 2 1 20 - # 3 1 20 - # 2 3 100 - # - # Sum of weights of the optimal paths: - # 14, 40 - import sys - for n_m in sys.stdin: - n, m = map(int, n_m.split()) - ds = DisjointSet(m) - edges = [None] * m # Create list of size - - # Read edges from input - for i in range(m): - u, v, weight = map(int, input().split()) - u -= 1 # Convert from 1-indexed to 0-indexed - v -= 1 # Convert from 1-indexed to 0-indexed - edges[i] = Edge(u, v, weight) - - # After finish input and graph creation, use Kruskal algorithm for MST: - print("MST weights sum:", kruskal(n, edges, ds)) \ No newline at end of file diff --git a/graph/pathBetweenTwoVerticesInDiGraph.py b/graph/pathBetweenTwoVerticesInDiGraph.py deleted file mode 100644 index ee6ac418f..000000000 --- a/graph/pathBetweenTwoVerticesInDiGraph.py +++ /dev/null @@ -1,51 +0,0 @@ -from collections import defaultdict - -class Graph: - def __init__(self,v): - self.v = v - self.graph = defaultdict(list) - self.has_path = False - - def add_edge(self,u,v): - self.graph[u].append(v) - - def dfs(self,x,y): - visited = [False] * self.v - self.dfsutil(visited,x,y,) - - def dfsutil(self,visited,x,y): - visited[x] = True - for i in self.graph[x]: - if y in self.graph[x]: - self.has_path = True - return - if(not(visited[i])): - self.dfsutil(visited,x,i) - - def is_reachable(self,x,y): - self.has_path = False - self.dfs(x,y) - return self.has_path - - -# Create a graph given in the above diagram -g = 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) - -u =1; v = 3 - -if g.is_reachable(u, v): - print("There is a path from %d to %d" % (u,v)) -else : - print("There is no path from %d to %d" % (u,v)) - -u = 3; v = 1 -if g.is_reachable(u, v) : - print("There is a path from %d to %d" % (u,v)) -else : - print("There is no path from %d to %d" % (u,v)) diff --git a/graph/tarjan.py b/graph/tarjan.py deleted file mode 100644 index 584999b49..000000000 --- a/graph/tarjan.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Implements Tarjan's algorithm for finding strongly connected components -in a graph. -https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm -""" -from graph.graph import DirectedGraph - -class Tarjan(object): - def __init__(self, dict_graph): - self.graph = DirectedGraph(dict_graph) - self.index = 0 - self.stack = [] - - # Runs Tarjan - # Set all node index to None - for v in self.graph.nodes: - v.index = None - - self.sccs = [] - for v in self.graph.nodes: - if v.index == None: - self.strongconnect(v, self.sccs) - - def strongconnect(self, v, sccs): - # Set the depth index for v to the smallest unused index - v.index = self.index - v.lowlink = self.index - self.index += 1 - self.stack.append(v) - v.on_stack = True - - # Consider successors of v - for w in self.graph.adjmt[v]: - if w.index == None: - # Successor w has not yet been visited; recurse on it - self.strongconnect(w, sccs) - v.lowlink = min(v.lowlink, w.lowlink) - elif w.on_stack: - # Successor w is in stack S and hence in the current SCC - # If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored - # Note: The next line may look odd - but is correct. - # It says w.index not w.lowlink; that is deliberate and from the original paper - v.lowlink = min(v.lowlink, w.index) - - # If v is a root node, pop the stack and generate an SCC - if v.lowlink == v.index: - # start a new strongly connected component - scc = [] - while True: - w = self.stack.pop() - w.on_stack = False - scc.append(w) - if w == v: - break - scc.sort() - sccs.append(scc) - \ No newline at end of file diff --git a/graph/traversal.py b/graph/traversal.py deleted file mode 100644 index cbfc65ff4..000000000 --- a/graph/traversal.py +++ /dev/null @@ -1,66 +0,0 @@ -graph = {'A': set(['B', 'C', 'F']), - 'B': set(['A', 'D', 'E']), - 'C': set(['A', 'F']), - 'D': set(['B']), - 'E': set(['B', 'F']), - 'F': set(['A', 'C', 'E'])} - -# dfs and bfs are the ultimately same except that they are visiting nodes in -# different order. To simulate this ordering we would use stack for dfs and -# queue for bfs. -# - -def dfs_traverse(graph, start): - visited, stack = set(), [start] - while stack: - node = stack.pop() - if node not in visited: - visited.add(node) - for nextNode in graph[node]: - if nextNode not in visited: - stack.append(nextNode) - return visited - -# print(dfs_traverse(graph, 'A')) - - -def bfs_traverse(graph, start): - visited, queue = set(), [start] - while queue: - node = queue.pop(0) - if node not in visited: - visited.add(node) - for nextNode in graph[node]: - if nextNode not in visited: - queue.append(nextNode) - return visited - -# print(bfs_traverse(graph, 'A')) - -def dfs_traverse_recursive(graph, start, visited=None): - if visited is None: - visited = set() - visited.add(start) - for nextNode in graph[start]: - if nextNode not in visited: - dfs_traverse_recursive(graph, nextNode, visited) - return visited - -# print(dfs_traverse_recursive(graph, 'A')) - -# def find_path(graph, start, end, visited=[]): - # # basecase - # visitied = visited + [start] - # if start == end: - # return visited - # if start not in graph: - # return None - # for node in graph[start]: - # if node not in visited: - # new_visited = find_path(graph, node, end, visited) - # return new_visited - # return None - -# print(find_path(graph, 'A', 'F')) - - diff --git a/linkedlist/copy_random_pointer.py b/linkedlist/copy_random_pointer.py deleted file mode 100644 index 7653d4902..000000000 --- a/linkedlist/copy_random_pointer.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -A linked list is given such that each node contains an additional random -pointer which could point to any node in the list or null. - -Return a deep copy of the list. -""" -# from collections import defaultdict - -# TODO: This requires to be rewritten -- commenting it out for now -# class Solution0: - # @param head, a RandomListNode - # @return a RandomListNode - # def copyRandomList(self, head): - # dic = dict() - # m = n = head - # while m: - # dic[m] = RandomListNode(m.label) - # m = m.next - # while n: - # dic[n].next = dic.get(n.next) - # dic[n].random = dic.get(n.random) - # n = n.next - # return dic.get(head) -# -# -# class Solution1: # O(n) -# @param head, a RandomListNode -# @return a RandomListNode - # def copyRandomList(self, head): - # copy = 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] diff --git a/maths/base_conversion.py b/maths/base_conversion.py deleted file mode 100644 index 1ea31e3be..000000000 --- a/maths/base_conversion.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Integer base conversion algorithm - -int2base(5, 2) return '101'. -base2int('F', 16) return 15. - -""" - -import string - -def int2base(n, base): - """ - :type n: int - :type base: int - :rtype: str - """ - is_negative = False - if n == 0: - return '0' - elif n < 0: - is_negative = True - n *= -1 - digit = string.digits + string.ascii_uppercase - res = '' - while n > 0: - res += digit[n % base] - n //= base - if is_negative: - return '-' + res[::-1] - else: - return res[::-1] - - -def base2int(s, base): - """ - Note : You can use int() built-in function instread of this. - :type s: str - :type base: int - :rtype: int - """ - - digit = {} - for i,c in enumerate(string.digits + string.ascii_uppercase): - digit[c] = i - multiplier = 1 - res = 0 - for c in s[::-1]: - res += digit[c] * multiplier - multiplier *= base - return res diff --git a/maths/extended_gcd.py b/maths/extended_gcd.py deleted file mode 100644 index 96c31ac65..000000000 --- a/maths/extended_gcd.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -extended GCD algorithm -return s,t,g -such that a s + b t = GCD(a, b) -and s and t are coprime -""" - -def extended_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 diff --git a/maths/gcd.py b/maths/gcd.py deleted file mode 100644 index 1f80fc981..000000000 --- a/maths/gcd.py +++ /dev/null @@ -1,12 +0,0 @@ -def gcd(a, b): - """Computes the greatest common divisor of integers a and b using - Euclid's Algorithm. - """ - while b != 0: - a, b = b, a % b - return a - - -def lcm(a, b): - """Computes the lowest common multiple of integers a and b.""" - return a * b / gcd(a, b) diff --git a/maths/nth_digit.py b/maths/nth_digit.py deleted file mode 100644 index ea2d6819e..000000000 --- a/maths/nth_digit.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -find nth digit -1. find the length of the number where the nth digit is from. -2. find the actual number where the nth digit is from -3. find the nth digit and return -""" - - -def find_nth_digit(n): - len = 1 - count = 9 - start = 1 - while n > len * count: - n -= len * count - len += 1 - count *= 10 - start *= 10 - start += (n-1) / len - s = str(start) - return int(s[(n-1) % len]) \ No newline at end of file diff --git a/maths/prime_check.py b/maths/prime_check.py deleted file mode 100644 index 1821d250b..000000000 --- a/maths/prime_check.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -prime_test(n) returns a True if n is a prime number else it returns False -""" - -def prime_check(n): - 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 - - -def prime_check2(n): - # prime numbers are greater than 1 - if n > 1: - # check for factors - for i in range(2, int(n ** 0.5) + 1): - if (n % i) == 0: - # print(num, "is not a prime number") - # print(i, "times", num//i, "is", num) - return False - - # print(num, "is a prime number") - return True - - # if input number is less than - # or equal to 1, it is not prime - else: - return False diff --git a/maths/pythagoras.py b/maths/pythagoras.py deleted file mode 100644 index d89626039..000000000 --- a/maths/pythagoras.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -input two of the three side in right angled triangle and return the third. use "?" to indicate the unknown side. -""" - -def pythagoras(opposite,adjacent,hypotenuse): - try: - if opposite == str("?"): - return ("Opposite = " + str(((hypotenuse**2) - (adjacent**2))**0.5)) - elif adjacent == str("?"): - return ("Adjacent = " + str(((hypotenuse**2) - (opposite**2))**0.5)) - elif hypotenuse == str("?"): - return ("Hypotenuse = " + str(((opposite**2) + (adjacent**2))**0.5)) - else: - return "You already know the answer!" - except: - raise ValueError("invalid argument were given.") diff --git a/maths/sqrt_precision_factor.py b/maths/sqrt_precision_factor.py deleted file mode 100644 index ce4f0cf94..000000000 --- a/maths/sqrt_precision_factor.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Given a positive integer N and a precision factor P, -write a square root function that produce an output -with a maximum error P from the actual square root of N. - -Example: -Given N = 5 and P = 0.001, can produce output O such that -2.235 < O > 2.237. Actual square root of 5 being 2.236. -""" - -def square_root(n,p): - guess = float(n) / 2 - - while abs(guess * guess - n) > p: - guess = (guess + (n / guess)) / 2 - - return guess diff --git a/matrix/matrix_rotation.txt b/matrix/matrix_rotation.txt deleted file mode 100644 index 9ce876e6a..000000000 --- a/matrix/matrix_rotation.txt +++ /dev/null @@ -1,580 +0,0 @@ -I'd like to add a little more detail. -In this answer, key concepts are repeated, the pace is slow and intentionally -repetitive. The solution provided here is not the most syntactically compact, -it is however intended for those who wish to learn what matrix rotation is and -the resulting implementation. - -Firstly, what is a matrix? For the purposes of this answer, a matrix is just -a grid where the width and height are the same. Note, the width and height -of a matrix can be different, but for simplicity, this tutorial considers only -matrices with equal width and height -(and yes, matrices is the plural of matrix). -Example matrices are: 2x2, 3x3 or 5x5. Or, more generally, NxN. -A 2x2 matrix will have 4 squares because 2x2=4. -A 5x5 matrix will have 25 squares because 5x5=25. -Each square is called an element or entry. We’ll represent each element with -a period (.) in the diagrams below: - -2x2 matrix - - . . - . . - -3x3 matrix - - . . . - . . . - . . . - -4x4 matrix - - . . . . - . . . . - . . . . - . . . . - - -So, what does it mean to rotate a matrix? Let’s take a 2x2 matrix and -put some numbers in each element so the rotation can be observed: - - 0 1 - 2 3 - -Rotating this by 90 degrees gives us: - - 2 0 - 3 1 - -We literally turned the whole matrix once to the right just like turning the -steering wheel of a car. It may help to think of “tipping” the matrix onto -its right side. We want to write a function, in Python, that takes a matrix and -rotates in once to the right. The function signature will be: - - def rotate(matrix): - # Algorithm goes here. - -The matrix will be defined using a two-dimensional array: - - matrix = [ - [0,1], - [2,3] - ] - -Therefore the first index position accesses the row. -The second index position accesses the column: - - matrix[row][column] - -We’ll define a utility function to print a matrix. - - def print_matrix(matrix): - for row in matrix: - print row - -One method of rotating a matrix is to do it a layer at a time. -But what is a layer? Think of an onion. Just like the layers of an onion, -as each layer is removed, we move towards the center. -ther analogies is a Matryoshka doll or a game of pass-the-parcel. - -The width and height of a matrix dictate the number of layers in that matrix. -Let’s use different symbols for each layer: - -2x2 matrix has 1 layer - - . . - . . - -3x3 matrix has 2 layers - - . . . - . x . - . . . - -4x4 matrix has 2 layers - - . . . . - . x x . - . x x . - . . . . - -5x5 matrix has 3 layers - - . . . . . - . x x x . - . x O x . - . x x x . - . . . . . - -6x6 matrix has 3 layers - - . . . . . . - . x x x x . - . x O O x . - . x O O x . - . x x x x . - . . . . . . - -7x7 matrix has 4 layers - - . . . . . . . - . x x x x x . - . x O O O x . - . x O - O x . - . x O O O x . - . x x x x x . - . . . . . . . - -You may notice that incrementing the width and height of a matrix by one, -does not always increase the number of layers. -Taking the above matrices and tabulating the layers and dimensions, -we see the number of layers increases once for every two increments of -width and height: - - +-----+--------+ - | NxN | Layers | - +-----+--------+ - | 1x1 | 1 | - | 2x2 | 1 | - | 3x3 | 2 | - | 4x4 | 2 | - | 5x5 | 3 | - | 6x6 | 3 | - | 7x7 | 4 | - +-----+--------+ - -However, not all layers need rotating. -A 1x1 matrix is the same before and after rotation. -The central 1x1 layer is always the same before and -after rotation no matter how large the overall matrix: - - +-----+--------+------------------+ - | NxN | Layers | Rotatable Layers | - +-----+--------+------------------+ - | 1x1 | 1 | 0 | - | 2x2 | 1 | 1 | - | 3x3 | 2 | 1 | - | 4x4 | 2 | 2 | - | 5x5 | 3 | 2 | - | 6x6 | 3 | 3 | - | 7x7 | 4 | 3 | - +-----+--------+------------------+ - -Given NxN matrix, how can we programmatically determine the number of layers -we need to rotate? -If we divide the width or height by two and ignore the remainder -we get the following results. - - +-----+--------+------------------+---------+ - | NxN | Layers | Rotatable Layers | N/2 | - +-----+--------+------------------+---------+ - | 1x1 | 1 | 0 | 1/2 = 0 | - | 2x2 | 1 | 1 | 2/2 = 1 | - | 3x3 | 2 | 1 | 3/2 = 1 | - | 4x4 | 2 | 2 | 4/2 = 2 | - | 5x5 | 2 | 2 | 5/2 = 2 | - | 6x6 | 3 | 3 | 6/2 = 3 | - | 7x7 | 4 | 3 | 7/2 = 3 | - +-----+--------+------------------+---------+ - -Notice how N/2 matches the number of layers that need to be rotated? -Sometimes the number of rotatable layers is one less the total number of -layers in the matrix. -This occurs when the innermost layer is formed of only one element -(i.e. a 1x1 matrix) and therefore need not be rotated. It simply gets ignored. - -We will undoubtedly need this information in our function to -rotate a matrix, so let’s add it now: - - def rotate(matrix): - size = len(matrix) - # Rotatable layers only. - layer_count = size / 2 - -Now we know what layers are and how to determine the number of layers -that actually need rotating, how do we isolate a single layer so we can rotate -it? Firstly, we inspect a matrix from the outermost layer, -inwards, to the innermost layer. -A 5x5 matrix has three layers in total and two layers that need rotating: - - . . . . . - . x x x . - . x O x . - . x x x . - . . . . . - -Let’s look at columns first. The position of the columns defining the -outermost layer, assuming we count from 0, are 0 and 4: - - +--------+-----------+ - | Column | 0 1 2 3 4 | - +--------+-----------+ - | | . . . . . | - | | . x x x . | - | | . x O x . | - | | . x x x . | - | | . . . . . | - +--------+-----------+ - -0 and 4 are also the positions of the rows for the outermost layer. - - +-----+-----------+ - | Row | | - +-----+-----------+ - | 0 | . . . . . | - | 1 | . x x x . | - | 2 | . x O x . | - | 3 | . x x x . | - | 4 | . . . . . | - +-----+-----------+ - -This will always be the case since the width and height are the same. -Therefore we can define the column and row positions of a layer with just -two values (rather than four). - -Moving inwards to the second layer, the position of the columns are 1 and 3. -And, yes, you guessed it, it’s the same for rows. -It’s important to understand we had to both increment and decrement the row -and column positions when moving inwards to the next layer. - - +-----------+---------+---------+---------+ - | Layer | Rows | Columns | Rotate? | - +-----------+---------+---------+---------+ - | Outermost | 0 and 4 | 0 and 4 | Yes | - | Inner | 1 and 3 | 1 and 3 | Yes | - | Innermost | 2 | 2 | No | - +-----------+---------+---------+---------+ - -So, to inspect each layer, we want a loop with both increasing and decreasing -counters that represent moving inwards, starting from the outermost layer. -We’ll call this our ‘layer loop’. - - def rotate(matrix): - size = len(matrix) - layer_count = size / 2 - - for layer in range(0, layer_count): - first = layer - last = size - first - 1 - print 'Layer %d: first: %d, last: %d'%(layer, first, last) - - # 5x5 matrix - matrix = [ - [0,1,2,3,4], - [5,6,7,8,9], - [10,11,12,13,14], - [15,16,17,18,19], - [20,21,22,23,24] - ] - - rotate(matrix) - -The code above loops through the (row and column) positions of any layers that need rotating. - - first: 0, last: 4 - first: 1, last: 3 - -We now have a loop providing the positions of the rows and columns of each layer. The variables first and last identify the index position of the first and last rows and columns. Referring back to our row and column tables: - - +--------+-----------+ - | Column | 0 1 2 3 4 | - +--------+-----------+ - | | . . . . . | - | | . x x x . | - | | . x O x . | - | | . x x x . | - | | . . . . . | - +--------+-----------+ - - +-----+-----------+ - | Row | | - +-----+-----------+ - | 0 | . . . . . | - | 1 | . x x x . | - | 2 | . x O x . | - | 3 | . x x x . | - | 4 | . . . . . | - +-----+-----------+ - -So we can navigate through the layers of a matrix. Now we need a way of navigating within a layer so we can move elements around that layer. Note, elements never ‘jump’ from one layer to another, but they do move within their respective layers. - -Rotating each element in a layer rotates the entire layer. Rotating all layers in a matrix rotates the entire matrix. This sentence is very important, so please try your best to understand it before moving on. - -Now, we need a way of actually moving elements, i.e. rotate each element, and subsequently the layer, and ultimately the matrix. For simplicity, we’ll revert to a 3x3 matrix - that has one rotatable layer. - - 0 1 2 - 3 4 5 - 6 7 8 - -Our layer loop provides the indexes of the first and last columns, as well as first and last rows: - - +-----+-------+ - | Col | 0 1 2 | - +-----+-------+ - | | 0 1 2 | - | | 3 4 5 | - | | 6 7 8 | - +-----+-------+ - - +-----+-------+ - | Row | | - +-----+-------+ - | 0 | 0 1 2 | - | 1 | 3 4 5 | - | 2 | 6 7 8 | - +-----+-------+ - -Because our matrices are always square, we need just two variables, first and last, since index positions are the same for rows and columns. - - def rotate(matrix): - size = len(matrix) - layer_count = size / 2 - - # Our layer loop i=0, i=1, i=2 - for layer in range(0, layer_count): - - first = layer - last = size - first - 1 - - # We want to move within a layer here. - -The variables first and last can easily be used to reference the four corners of a matrix. This is because the corners themselves can be defined used various permutations of first and last (with no subtraction, addition or offset of those variables): - - +-----------------+-----------------+-------------+ - | Corner | Position | 3x3 Values | - +-----------------+-----------------+-------------+ - | top left | (first, first) | (0,0) | - | top right | (first, last) | (0,2) | - | bottom right | (last, last) | (2,2) | - | bottom left | (last, first) | (2,0) | - +-----------------+-----------------+-------------+ - -For this reason, we start our rotation at the outer four corners: We’ll rotate those first, let’s highlight them with *. - - * 1 * - 3 4 5 - * 7 * - -We want to swap each *, with the * to the right of it. So let’s go ahead a print out our corners defined using only various permutations of first and last: - - def rotate(matrix): - size = len(matrix) - layer_count = size / 2 - for layer in range(0, layer_count): - - first = layer - last = size - first - 1 - - top_left = (first, first) - top_right = (first, last) - bottom_right = (last, last) - bottom_left = (last, first) - - print 'top_left: %s'%(top_left,) - print 'top_right: %s'%(top_right,) - print 'bottom_right: %s'%(bottom_right,) - print 'bottom_left: %s'%(bottom_left,) - - matrix = [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8] - ] - - rotate(matrix) - -Output should be: - - top_left: (0, 0) - top_right: (0, 2) - bottom_right: (2, 2) - bottom_left: (2, 0) - -Now we could quite easily swap each of the corners from within our layer loop: - - def rotate(matrix): - size = len(matrix) - layer_count = size / 2 - for layer in range(0, layer_count): - - first = layer - last = size - first - 1 - - top_left = matrix[first][first] - top_right = matrix[first][last] - bottom_right = matrix[last][last] - bottom_left = matrix[last][first] - - # bottom_left -> top_left - matrix[first][first] = bottom_left - # top_left -> top_right - matrix[first][last] = top_left - # top_right -> bottom_right - matrix[last][last] = top_right - # bottom_right -> bottom_left - matrix[last][first] = bottom_right - - - print_matrix(matrix) - print '---------' - rotate(matrix) - print_matrix(matrix) - -Matrix before rotating corners: - - [0, 1, 2] - [3, 4, 5] - [6, 7, 8] - -Matrix after rotating corners: - - [6, 1, 0] - [3, 4, 5] - [8, 7, 2] - -Great! We have successfully rotated each corner of the matrix. But, we haven’t rotated the elements in the middle of each layer. Clearly we need a way of iterating within a layer. - -The problem is, the only loop in our function so far (our layer loop), moves to the next layer on each iteration. Since our matrix has only one rotatable layer, the layer loop exits after rotating only the corners. Let’s look at what happens with a larger, 5x5 matrix (where two layers need rotating). The function code has been omitted, but it remains the same as above: - - matrix = [ - [0, 1, 2, 3, 4] - [5, 6, 7, 8, 9] - [10, 11, 12, 13, 14] - [15, 16, 17, 18, 19] - [20, 21, 22, 23, 24] - ] - print_matrix(matrix) - print '--------------------' - rotate(matrix) - print_matrix(matrix) - -The output is: - - [20, 1, 2, 3, 0 ] - [5, 16, 7, 6, 9 ] - [10, 11, 12, 13, 14] - [15, 18, 17, 8, 19] - [24, 21, 22, 23, 4 ] - -It shouldn’t be a surprise that the corners of the outermost layer have been rotated, but, you may also notice the corners of the next layer (inwards) have also been rotated. This makes sense. We’ve written code to navigate through layers and also to rotate the corners of each layer. This feels like progress, but unfortunately we must take a step back. It’s just no use moving onto the next layer until the previous (outer) layer has been fully rotated. That is, until each element in the layer has been rotated. Rotating only the corners won’t do! - -Take a deep breath. We need another loop. A nested loop no less. The new, nested loop, will use the first and last variables, plus an offset to navigate within a layer. We’ll call this new loop our ‘element loop’. The element loop will visit each element along the top row, each element down the right side, each element along the bottom row and each element up the left side. - - - Moving across the top row requires the column index to be - incremented. - - Moving down the right side requires the row index to be - incremented. - - Moving backwards along the bottom requires the column - index to be decremented. - - Moving up the left side requires the row - index to be decremented. - -This sound complex, but it’s made easy because the number of times we increment and decrement to achieve the above remains the same along all four sides of the matrix. For example: - - - Move 1 element across the top row. - - Move 1 element down the right side. - - Move 1 element backwards along the bottom row. - - Move 1 element up the left side. - -This means we can use a single variable, in combination with the first and last variables to move within a layer. It may help to note that moving across the top row and down the right side both require incrementing. While moving backwards along the bottom and up the left side both require decrementing. - - def rotate(matrix): - size = len(matrix) - layer_count = size / 2 - - # Move through layers (i.e. layer loop). - for layer in range(0, layer_count): - - first = layer - last = size - first - 1 - - # Move within a single layer (i.e. element loop). - for element in range(first, last): - - offset = element - first - - # ‘element’ increments column (across right) - top = (first, element) - # ‘element’ increments row (move down) - right_side = (element, last) - # ‘last-offset’ decrements column (across left) - bottom = (last, last-offset) - # ‘last-offset’ decrements row (move up) - left_side = (last-offset, first) - - print 'top: %s'%(top,) - print 'right_side: %s'%(right_side,) - print 'bottom: %s'%(bottom,) - print 'left_side: %s'%(left_side,) - -Now we simply need to assign the top to the right side, right side to the bottom, bottom to the left side, and left side to the top. Putting this all together we get: - - def rotate(matrix): - size = len(matrix) - layer_count = size / 2 - - for layer in range(0, layer_count): - first = layer - last = size - first - 1 - - for element in range(first, last): - offset = element - first - - top = matrix[first][element] - right_side = matrix[element][last] - bottom = matrix[last][last-offset] - left_side = matrix[last-offset][first] - - matrix[first][element] = left_side - matrix[element][last] = top - matrix[last][last-offset] = right_side - matrix[last-offset][first] = bottom - -Given the matrix: - - 0, 1, 2 - 3, 4, 5 - 6, 7, 8 - -Our rotate function results in: - - 6, 3, 0 - 7, 4, 1 - 8, 5, 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/queues/__init__.py b/queues/__init__.py deleted file mode 100644 index 2b5cb8ad8..000000000 --- a/queues/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .queue import * diff --git a/queues/priority_queue.py b/queues/priority_queue.py deleted file mode 100644 index a28ae9697..000000000 --- a/queues/priority_queue.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Implementation of priority queue using linear array. -Insertion - O(n) -Extract min/max Node - O(1) -""" -import collections - - -class PriorityQueueNode: - def __init__(self, data, priority): - self.data = data - self.priority = priority - - def __repr__(self): - return str(self.data) + ": " + str(self.priority) - -class PriorityQueue: - def __init__(self): - self.priority_queue_list = collections.deque() - - def __repr__(self): - return "PriorityQueue({!r})".format(list(self.priority_queue_list)) - - def size(self): - return len(self.priority_queue_list) - - def push(self, item, priority=None): - 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 - # when traversed complete queue - self.priority_queue_list.append(node) - - def pop(self): - # remove and return the first node from the queue - return self.priority_queue_list.popleft() diff --git a/search/__init__.py b/search/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/search/binary_search.py b/search/binary_search.py deleted file mode 100644 index 644f98f8e..000000000 --- a/search/binary_search.py +++ /dev/null @@ -1,29 +0,0 @@ -# -# Binary search works for a sorted array. -# Note: The code logic is written for an array sorted in -# increasing order. -# T(n): O(log n) -# -def binary_search(array, query): - lo, hi = 0, len(array) - 1 - while lo <= hi: - mid = (hi + lo) // 2 - val = array[mid] - if val == query: - return mid - elif val < query: - lo = mid + 1 - else: - hi = mid - 1 - return None - -def binary_search_recur(array, low, high, val): - if low > high: # error case - return -1 - mid = (low + high) // 2 - if val < array[mid]: - return binary_search_recur(array, low, mid - 1, val) - elif val > array[mid]: - return binary_search_recur(array, mid + 1, high, val) - else: - return mid diff --git a/search/first_occurance.py b/search/first_occurance.py deleted file mode 100644 index addf17058..000000000 --- a/search/first_occurance.py +++ /dev/null @@ -1,18 +0,0 @@ -# -# Find first occurance of a number in a sorted array (increasing order) -# Approach- Binary Search -# T(n)- O(log n) -# -def first_occurance(array, query): - lo, hi = 0, len(array) - 1 - while lo <= hi: - mid = (lo + hi) // 2 - #print("lo: ", lo, " hi: ", hi, " mid: ", mid) - if lo == hi: - break - if array[mid] < query: - lo = mid + 1 - else: - hi = mid - if array[lo] == query: - return lo diff --git a/search/last_occurance.py b/search/last_occurance.py deleted file mode 100644 index b8ee4fa62..000000000 --- a/search/last_occurance.py +++ /dev/null @@ -1,16 +0,0 @@ -# -# Find last occurance of a number in a sorted array (increasing order) -# Approach- Binary Search -# T(n)- O(log n) -# -def last_occurance(array, query): - lo, hi = 0, len(array) - 1 - while lo <= hi: - mid = (hi + lo) // 2 - if (array[mid] == query and mid == len(array)-1) or \ - (array[mid] == query and array[mid+1] > query): - return mid - elif (array[mid] <= query): - lo = mid + 1 - else: - hi = mid - 1 diff --git a/search/search_insert.py b/search/search_insert.py deleted file mode 100644 index b10eb7d5f..000000000 --- a/search/search_insert.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -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. - -For example: -[1,3,5,6], 5 -> 2 -[1,3,5,6], 2 -> 1 -[1,3,5,6], 7 -> 4 -[1,3,5,6], 0 -> 0 -""" -def search_insert(array, val): - 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 diff --git a/setup.py b/setup.py index b7374e85c..c5bb20141 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,28 @@ -import os +import io from setuptools import find_packages, setup + +def long_description(): + with io.open('README.md', 'r', encoding='utf-8') as f: + readme = f.read() + return readme + + setup(name='algorithms', - version='1.1', + version='0.1.4', description='Pythonic Data Structures and Algorithms', + long_description=long_description(), + long_description_content_type="text/markdown", url='https://github.com/keon/algorithms', - author='Keon Kim, Hai Hoang Dang, Rahul Goswami, Christian Bender, Saad', + author='Algorithms Team & Contributors', + author_email="kwk236@gmail.com", license='MIT', - packages=find_packages(), + packages=find_packages(exclude=('tests', 'tests.*')), classifiers=[ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], zip_safe=False) diff --git a/sort/__init__.py b/sort/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/sort/bubble_sort.py b/sort/bubble_sort.py deleted file mode 100644 index da9cebdf1..000000000 --- a/sort/bubble_sort.py +++ /dev/null @@ -1,21 +0,0 @@ -""" - -https://en.wikipedia.org/wiki/Bubble_sort - -Worst-case performance: O(N^2) - -""" -def bubble_sort(arr): - - def swap(i, j): - arr[i], arr[j] = arr[j], arr[i] - - n = len(arr) - swapped = True - while swapped: - swapped = False - for i in range(1, n): - if arr[i - 1] > arr[i]: - swap(i - 1, i) - swapped = True - return arr diff --git a/sort/counting_sort.py b/sort/counting_sort.py deleted file mode 100644 index 9b11af3cc..000000000 --- a/sort/counting_sort.py +++ /dev/null @@ -1,36 +0,0 @@ -def counting_sort(arr): - """ - Counting_sort - Sorting a array which has no element greater than k - Creating a new temp_arr,where temp_arr[i] contain the number of - element less than or equal to i in the arr - Then placing the number i into a correct position in the result_arr - return the result_arr - Complexity: 0(n) - """ - - m = min(arr) - #in case there are negative elements, change the array to all positive element - different = 0 - if m < 0: - #save the change, so that we can convert the array back to all positive number - different = -m - for i in range (len(arr)): - arr[i]+= -m - k = max(arr) - temp_arr = [0]*(k+1) - for i in range(0,len(arr)): - temp_arr[arr[i]] = temp_arr[arr[i]]+1 - #temp_array[i] contain the times the number i appear in arr - - for i in range(1, k+1): - temp_arr[i] = temp_arr[i] + temp_arr[i-1] - #temp_array[i] contain the number of element less than or equal i in arr - - result_arr = [0]*len(arr) - #creating a result_arr an put the element in a correct positon - for i in range(len(arr)-1,-1,-1): - result_arr[temp_arr[arr[i]]-1] = arr[i]-different - temp_arr[arr[i]] = temp_arr[arr[i]]-1 - - return result_arr diff --git a/sort/heap_sort.py b/sort/heap_sort.py deleted file mode 100644 index 5cdc78665..000000000 --- a/sort/heap_sort.py +++ /dev/null @@ -1,77 +0,0 @@ -def max_heap_sort(arr): - """ Heap Sort that uses a max heap to sort an array in ascending order - Complexity: O(n log(n)) - """ - for i in range(len(arr)-1,0,-1): - max_heapify(arr, i) - - temp = arr[0] - arr[0] = arr[i] - arr[i] = temp - return arr - -def max_heapify(arr, end): - """ Max heapify helper for max_heap_sort - """ - last_parent = int((end-1)/2) - - # Iterate from last parent to first - for parent in range(last_parent,-1,-1): - current_parent = parent - - # Iterate from current_parent to last_parent - while current_parent <= last_parent: - # Find greatest child of current_parent - child = 2*current_parent + 1 - if child + 1 <= end and arr[child] < arr[child+1]: - child = child + 1 - - # Swap if child is greater than parent - if arr[child] > arr[current_parent]: - temp = arr[current_parent] - arr[current_parent] = arr[child] - arr[child] = temp - - current_parent = child - # If no swap occured, no need to keep iterating - else: - break - - -def min_heap_sort(arr): - """ Heap Sort that uses a min heap to sort an array in ascending order - Complexity: O(n log(n)) - """ - for i in range(0, len(arr)-1): - min_heapify(arr, i) - return arr - -def min_heapify(arr, start): - """ Min heapify helper for min_heap_sort - """ - # Offset last_parent by the start (last_parent calculated as if start index was 0) - # All array accesses need to be offet by start - end = len(arr)-1 - last_parent = int((end-start-1)/2) - - # Iterate from last parent to first - for parent in range(last_parent,-1,-1): - current_parent = parent - - # Iterate from current_parent to last_parent - while current_parent <= last_parent: - # Find lesser child of current_parent - child = 2*current_parent + 1 - if child + 1 <= end-start and arr[child+start] > arr[child+1+start]: - child = child + 1 - - # Swap if child is less than parent - if arr[child+start] < arr[current_parent+start]: - temp = arr[current_parent+start] - arr[current_parent+start] = arr[child+start] - arr[child+start] = temp - - current_parent = child - # If no swap occured, no need to keep iterating - else: - break diff --git a/sort/insertion_sort.py b/sort/insertion_sort.py deleted file mode 100644 index 07a0a5265..000000000 --- a/sort/insertion_sort.py +++ /dev/null @@ -1,15 +0,0 @@ -def insertion_sort(arr): - """ Insertion Sort - Complexity: O(n^2) - """ - for i in range(len(arr)): - cursor = arr[i] - pos = i - while pos > 0 and arr[pos-1] > cursor: - # Swap the number down the list - arr[pos] = arr[pos-1] - pos = pos-1 - # Break and do the final swap - arr[pos] = cursor - - return arr diff --git a/sort/merge_sort.py b/sort/merge_sort.py deleted file mode 100644 index e1b62b337..000000000 --- a/sort/merge_sort.py +++ /dev/null @@ -1,36 +0,0 @@ -def merge_sort(arr): - """ Merge Sort - Complexity: O(n log(n)) - """ - # Our recursive base case - if len(arr)<= 1: - return arr - mid = len(arr)//2 - # Perform merge_sort recursively on both halves - left, right = merge_sort(arr[mid:]), merge_sort(arr[:mid]) - - # Merge each side together - return merge(left, right) - -def merge(left, right): - """ Merge helper - Complexity: O(n) - """ - arr = [] - left_cursor, right_cursor = 0,0 - while left_cursor < len(left) and right_cursor < len(right): - # Sort each one and place into the result - if left[left_cursor] <= right[right_cursor]: - arr.append(left[left_cursor]) - left_cursor+=1 - else: - arr.append(right[right_cursor]) - right_cursor+=1 - # Add the left overs if there's any left to the result - for i in range(left_cursor,len(left)): - arr.append(left[i]) - for i in range(right_cursor,len(right)): - arr.append(right[i]) - - # Return result - return arr diff --git a/sort/quick_sort.py b/sort/quick_sort.py deleted file mode 100644 index ac91d6b49..000000000 --- a/sort/quick_sort.py +++ /dev/null @@ -1,18 +0,0 @@ -def quick_sort(arr, first, last): - """ Quicksort - Complexity: best O(n log(n)) avg O(n log(n)), worst O(N^2) - """ - if first < last: - pos = partition(arr, first, last) - # Start our two recursive calls - quick_sort(arr, first, pos-1) - quick_sort(arr, pos+1, last) - return arr -def partition(arr, first, last): - wall = first - for pos in range(first, last): - if arr[pos] < arr[last]: # last is the pivot - arr[pos], arr[wall] = arr[wall], arr[pos] - wall += 1 - arr[wall], arr[last] = arr[last], arr[wall] - return wall diff --git a/sort/selection_sort.py b/sort/selection_sort.py deleted file mode 100644 index fed1c3466..000000000 --- a/sort/selection_sort.py +++ /dev/null @@ -1,13 +0,0 @@ -def selection_sort(arr): - """ Selection Sort - Complexity: O(n^2) - """ - for i in range(len(arr)): - minimum = i - for j in range(i+1, len(arr)): - # "Select" the correct value - if arr[j] < arr[minimum]: - minimum = j - - arr[minimum], arr[i] = arr[i], arr[minimum] - return arr diff --git a/sort/topsort.py b/sort/topsort.py deleted file mode 100644 index bca556d22..000000000 --- a/sort/topsort.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Given a list of system packages, -some packages cannot be installed until the other packages are installed. -Provide a valid sequence to install all of the packages. - -e.g. -a relies on b -b relies on c - -then a valid sequence is [c, b, a] -""" - -depGraph = { - - "a" : [ "b" ], - "b" : [ "c" ], - "c" : [ 'e'], - 'e' : [ ], - "d" : [ ], - "f" : ["e" , "d"] -} - - -given = [ "b", "c", "a", "d", "e", "f" ] - -def ret_deps(visited, start): - queue = [] - out = [] - queue.append(start) - while queue: - new_node = queue.pop(0) - if new_node not in visited: - visited.add(new_node) - for child in depGraph[new_node]: - queue.append(child) - out.append(child) - out.append(start) - return out - - -def ret_dep_graph(): - visited = set() - out = [] - # visited.add(given[0]) - for pac in given: - if pac in visited: - continue - visited.add(pac) - #out.append(pac) - if pac in depGraph: - # find all children - for child in depGraph[pac]: - if child in visited: - continue - out.extend(ret_deps(visited, child)) - out.append(pac) - print(out) -ret_dep_graph() diff --git a/stack/__init__.py b/stack/__init__.py deleted file mode 100644 index 843a7582a..000000000 --- a/stack/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .stack import * diff --git a/stack/longest_abs_path.py b/stack/longest_abs_path.py deleted file mode 100644 index 64c7a67af..000000000 --- a/stack/longest_abs_path.py +++ /dev/null @@ -1,65 +0,0 @@ -# def lengthLongestPath(input): - # maxlen = 0 - # pathlen = {0: 0} - # for line in input.splitlines(): - # print("---------------") - # print("line:", line) - # name = line.strip('\t') - # print("name:", name) - # depth = len(line) - len(name) - # print("depth:", depth) - # if '.' in name: - # maxlen = max(maxlen, pathlen[depth] + len(name)) - # else: - # pathlen[depth + 1] = pathlen[depth] + len(name) + 1 - # print("maxlen:", maxlen) - # return maxlen - -# def lengthLongestPath(input): - # paths = input.split("\n") - # level = [0] * 10 - # maxLength = 0 - # for path in paths: - # print("-------------") - # levelIdx = path.rfind("\t") - # print("Path: ", path) - # print("path.rfind(\\t)", path.rfind("\t")) - # print("levelIdx: ", levelIdx) - # print("level: ", level) - # level[levelIdx + 1] = level[levelIdx] + len(path) - levelIdx + 1 - # print("level: ", level) - # if "." in path: - # maxLength = max(maxLength, level[levelIdx+1] - 1) - # print("maxlen: ", maxLength) - # return maxLength - -def length_longest_path(input): - """ - :type input: str - :rtype: int - """ - currlen, maxlen = 0, 0 # running length and max length - stack = [] # keep track of the name length - for s in input.split('\n'): - print("---------") - print(":", s) - depth = s.count('\t') # the depth of current dir or file - print("depth: ", depth) - print("stack: ", stack) - print("curlen: ", currlen) - while len(stack) > depth: # go back to the correct depth - currlen -= stack.pop() - stack.append(len(s.strip('\t'))+1) # 1 is the length of '/' - currlen += stack[-1] # increase current length - print("stack: ", stack) - print("curlen: ", currlen) - if '.' in s: # update maxlen only when it is a file - maxlen = max(maxlen, currlen-1) # -1 is to minus one '/' - return maxlen - -st= "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdirectory1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext" -st2 = "a\n\tb1\n\t\tf1.txt\n\taaaaa\n\t\tf2.txt" -print("path:", st2) - -print("answer:", length_longest_path(st2)) - diff --git a/stack/ordered_stack.py b/stack/ordered_stack.py deleted file mode 100644 index 7a30d143e..000000000 --- a/stack/ordered_stack.py +++ /dev/null @@ -1,33 +0,0 @@ -#The stack remains always ordered such that the highest value is at the top and the lowest at the bottom - -class OrderedStack: - def __init__(self): - self.items = [] - - def is_empty(self): - return self.items == [] - - def push_t(self, item): - self.items.append(item) - - def push(self, item): #push method to maintain order when pushing new elements - temp_stack = OrderedStack() - if self.is_empty() or item > self.peek(): - self.push_t(item) - else: - while item < self.peek() and not self.is_empty(): - temp_stack.push_t(self.pop()) - self.push_t(item) - while not temp_stack.is_empty(): - self.push_t(temp_stack.pop()) - - def pop(self): - if self.is_empty(): - raise IndexError("Stack is empty") - return self.items.pop() - - def peek(self): - return self.items[len(self.items) - 1] - - def size(self): - return len(self.items) diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 000000000..f0afad0e9 --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,7 @@ +flake8 +python-coveralls +coverage +nose +pytest +tox +black diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_array.py b/tests/test_array.py index acae48393..b73ecb17b 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1,16 +1,25 @@ -from arrays.delete_nth import delete_nth, delete_nth_naive -from arrays.flatten import flatten, flatten_iter -from arrays.garage import garage -from arrays.josephus_problem import josephus -from arrays.longest_non_repeat import longest_non_repeat, longest_non_repeat_two -from arrays.merge_intervals import Interval, merge_v2 -from arrays.missing_ranges import missing_ranges -from arrays.move_zeros_to_end import move_zeros -from arrays.plus_one import plus_one, plus_one_v2, plus_one_v3 -from arrays.rotate_array import rotate_v1, rotate_v2, rotate_v3 -from arrays.summary_ranges import summary_ranges -from arrays.three_sum import three_sum -from arrays.two_sum import two_sum +from algorithms.arrays import ( + delete_nth, delete_nth_naive, + flatten_iter, flatten, + garage, + josephus, + longest_non_repeat_v1, longest_non_repeat_v2, + get_longest_non_repeat_v1, get_longest_non_repeat_v2, + Interval, merge_intervals, + missing_ranges, + move_zeros, + plus_one_v1, plus_one_v2, plus_one_v3, + remove_duplicates, + rotate_v1, rotate_v2, rotate_v3, + summarize_ranges, + three_sum, + two_sum, + max_ones_index, + trimmean, + top_1, + limit, + n_sum +) import unittest @@ -37,15 +46,21 @@ class TestDeleteNth(unittest.TestCase): def test_delete_nth_naive(self): - self.assertListEqual(delete_nth_naive([20, 37, 20, 21, 37, 21, 21], n=1), + 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), + 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), + 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), + 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): @@ -54,11 +69,13 @@ def test_delete_nth(self): [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), + 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), + self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, + 5, 3, 1], n=0), []) @@ -117,32 +134,91 @@ def test_garage(self): 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]]) + 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(self): + def test_longest_non_repeat_v1(self): string = "abcabcbb" - self.assertEqual(longest_non_repeat(string), 3) + self.assertEqual(longest_non_repeat_v1(string), 3) string = "bbbbb" - self.assertEqual(longest_non_repeat(string), 1) + self.assertEqual(longest_non_repeat_v1(string), 1) string = "pwwkew" - self.assertEqual(longest_non_repeat(string), 3) + self.assertEqual(longest_non_repeat_v1(string), 3) - def test_longest_non_repeat_two(self): + 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(longest_non_repeat_two(string), 3) + self.assertEqual(get_longest_non_repeat_v2(string), (3, 'abc')) string = "bbbbb" - self.assertEqual(longest_non_repeat_two(string), 1) + self.assertEqual(get_longest_non_repeat_v2(string), (1, 'b')) string = "pwwkew" - self.assertEqual(longest_non_repeat_two(string), 3) + 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): @@ -156,9 +232,9 @@ def test_merge(self): [Interval(1, 6), Interval(8, 10), Interval(15, 18)] ) - def test_merge_v2(self): + def test_merge_intervals(self): interval_list = [[1, 3], [2, 6], [8, 10], [15, 18]] - merged_intervals = merge_v2(interval_list) + merged_intervals = merge_intervals(interval_list) self.assertEqual( merged_intervals, [[1, 6], [8, 10], [15, 18]] @@ -172,7 +248,8 @@ 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)]) + [(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)]) @@ -185,70 +262,95 @@ 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]), + 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(self): + def test_plus_one_v1(self): - self.assertListEqual(plus_one([0]), [1]) - self.assertListEqual(plus_one([9]), [1, 0]) - self.assertListEqual(plus_one([1, 0, 9]), [1, 1, 0]) - self.assertListEqual(plus_one([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) - self.assertListEqual(plus_one([9, 9, 9, 9]), [1, 0, 0, 0, 0]) + 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]) + 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]) + 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])) + self.assertListEqual(remove_duplicates(["hey", "hello", "hello", "car", "house", "house"])) + self.assertListEqual(remove_duplicates([True, True, False, True, False, None, None])) + self.assertListEqual(remove_duplicates([1,1,"hello", "hello", True, False, False])) + self.assertListEqual(remove_duplicates([1, "hello", True, 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, 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, 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, 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_summary_ranges(self): + def test_summarize_ranges(self): - self.assertListEqual(summary_ranges([0, 1, 2, 4, 5, 7]), + self.assertListEqual(summarize_ranges([0, 1, 2, 4, 5, 7]), [(0, 2), (4, 5), (7, 7)]) - self.assertListEqual(summary_ranges([-5, -4, -3, 1, 2, 4, 5, 6]), + self.assertListEqual(summarize_ranges([-5, -4, -3, 1, 2, 4, 5, 6]), [(-5, -3), (1, 2), (4, 6)]) - self.assertListEqual(summary_ranges([-2, -1, 0, 1, 2]), + self.assertListEqual(summarize_ranges([-2, -1, 0, 1, 2]), [(-2, 2)]) @@ -263,7 +365,7 @@ def test_three_sum(self): {(-4, 1, 3), (-2, -1, 3), (-1, -1, 2)}) -class TestSuite(unittest.TestCase): +class TestTwoSum(unittest.TestCase): def test_two_sum(self): @@ -273,6 +375,67 @@ def test_two_sum(self): 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() diff --git a/tests/test_automata.py b/tests/test_automata.py new file mode 100644 index 000000000..dbd766a3f --- /dev/null +++ b/tests/test_automata.py @@ -0,0 +1,48 @@ +from algorithms.automata import DFA + + +import unittest + + +class TestDFA(unittest.TestCase): + def test_DFA(self): + transitions = { + 'a': {'1': 'a', '0': 'b'}, + 'b': {'1': 'b', '0': 'a'} + } + + final = ['a'] + start = 'a' + + self.assertEqual(False, DFA(transitions, start, final, "000111100")) + self.assertEqual(True, DFA(transitions, start, final, "111000011")) + + transitions1 = { + '0': {'0': '1', '1': '0'}, + '1': {'0': '2', '1': '0'}, + '2': {'0': '2', '1': '3'}, + '3': {'0': '3', '1': '3'} + } + + final1 = ['0', '1', '2'] + start1 = '0' + + self.assertEqual(False, DFA(transitions1, start1, final1, "0001111")) + self.assertEqual(True, DFA(transitions1, start1, final1, "01010101")) + + transitions2 = { + '0': {'a': '0', 'b': '1'}, + '1': {'a': '0', 'b': '2'}, + '2': {'a': '3', 'b': '2'}, + '3': {'a': '3', 'b': '3'} + } + + final2 = ['3'] + start2 = '0' + + self.assertEqual(False, DFA(transitions2, start2, final2, "aaabbb")) + self.assertEqual(True, DFA(transitions2, start2, final2, "baabba")) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_backtrack.py b/tests/test_backtrack.py new file mode 100644 index 000000000..516ed6d2d --- /dev/null +++ b/tests/test_backtrack.py @@ -0,0 +1,396 @@ +from algorithms.backtrack import ( + add_operators, + permute_iter, + 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, + pattern_match, + permute_unique, + permute, + permute_recursive, + subsets_unique, + subsets, + subsets_v2, +) + +import unittest + + +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() diff --git a/tests/test_bfs.py b/tests/test_bfs.py new file mode 100644 index 000000000..f9b22f134 --- /dev/null +++ b/tests/test_bfs.py @@ -0,0 +1,62 @@ +from algorithms.bfs import ( + count_islands, + maze_search, + ladder_length +) + +import unittest + + +class TestCountIslands(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 TestMazeSearch(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'])) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_bit.py b/tests/test_bit.py index 33d80d9b5..c494a9762 100644 --- a/tests/test_bit.py +++ b/tests/test_bit.py @@ -1,24 +1,36 @@ -from bit.add_bitwise_operator import add_bitwise_operator -from bit.count_ones import count_ones_iter, count_ones_recur -from bit.find_missing_number import find_missing_number, find_missing_number2 -from bit.power_of_two import is_power_of_two -from bit.reverse_bits import reverse_bits -from bit.single_number import single_number -from bit.single_number2 import single_number2 -from bit.single_number3 import single_number3 -from bit.subsets import subsets -from bit.bit_operation import get_bit, set_bit, clear_bit, update_bit -from bit.swap_pair import swap_pair -from bit.find_difference import find_difference -from bit.has_alternative_bit import has_alternative_bit, has_alternative_bit_fast -from bit.insert_bit import insert_one_bit, insert_mult_bits -from bit.remove_bit import remove_bit +from algorithms.bit import ( + add_bitwise_operator, + count_ones_iter, count_ones_recur, + count_flips_to_convert, + find_missing_number, find_missing_number2, + flip_bit_longest_seq, + is_power_of_two, + reverse_bits, + single_number, + single_number2, + single_number3, + subsets, + get_bit, set_bit, clear_bit, update_bit, + int_to_bytes_big_endian, int_to_bytes_little_endian, + bytes_big_endian_to_int, bytes_little_endian_to_int, + swap_pair, + find_difference, + has_alternative_bit, has_alternative_bit_fast, + insert_one_bit, insert_mult_bits, + remove_bit, + binary_gap +) import unittest import random + 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)) @@ -53,9 +65,15 @@ def test_count_ones_iter(self): # 0 -> 0 self.assertEqual(0, count_ones_iter(0)) - def setUp(self): - """Initialize seed.""" - random.seed("test") + 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): @@ -77,6 +95,16 @@ def test_find_missing_number2(self): 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)) @@ -123,20 +151,24 @@ def test_single_number2(self): self.assertEqual(single, single_number2(nums)) def test_single_number3(self): - self.assertEqual(sorted([2,5]), + self.assertEqual(sorted([2, 5]), sorted(single_number3([2, 1, 5, 6, 6, 1]))) - self.assertEqual(sorted([4,3]), + 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)}) + {(), (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,)}) + {(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 @@ -152,11 +184,25 @@ def test_clear_bit(self): 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 + # 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 + # 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)) @@ -213,5 +259,16 @@ def test_remove_bit(self): 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() diff --git a/tests/test_compression.py b/tests/test_compression.py new file mode 100644 index 000000000..503369c4a --- /dev/null +++ b/tests/test_compression.py @@ -0,0 +1,74 @@ +from algorithms.compression.huffman_coding import HuffmanCoding +from algorithms.compression.rle_compression import (decode_rle, encode_rle) +from algorithms.compression.elias import (elias_gamma, elias_delta) + +import unittest + + +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 i 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() diff --git a/tests/test_dfs.py b/tests/test_dfs.py new file mode 100644 index 000000000..3164ab873 --- /dev/null +++ b/tests/test_dfs.py @@ -0,0 +1,80 @@ +from algorithms.dfs import ( + get_factors, get_factors_iterative1, get_factors_iterative2, + num_islands, + pacific_atlantic, + Sudoku, + walls_and_gates, + find_path +) + +import unittest + + +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 TestCountIslands(unittest.TestCase): + def test_num_islands(self): + self.assertEqual(1, num_islands([[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([[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 TestMazeSearch(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(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(maze_2)) + maze_3 = [[1, 0, 0], [0, 1, 1], [0, 1, 1]] + self.assertEqual(-1, find_path(maze_3)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_dp.py b/tests/test_dp.py new file mode 100644 index 000000000..e92800a11 --- /dev/null +++ b/tests/test_dp.py @@ -0,0 +1,263 @@ +from algorithms.dp import ( + max_profit_naive, max_profit_optimized, + climb_stairs, climb_stairs_optimized, + count, + combination_sum_topdown, combination_sum_bottom_up, + edit_distance, + egg_drop, + fib_recursive, fib_list, fib_iter, + hosoya_testing, + house_robber, + Job, schedule, + Item, get_maximum_value, + longest_increasing_subsequence, + longest_increasing_subsequence_optimized, + longest_increasing_subsequence_optimized2, + int_divide,find_k_factor, + planting_trees, regex_matching +) + + +import unittest + + +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 Test_dp_K_Factor(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] + L = 10 + W = 1 + + # act + res = planting_trees(trees, L, W) + + # assert + self.assertEqual(res, 2.414213562373095) + + def test_simple2(self): + # arrange + trees = [0, 3, 5, 5, 6, 9] + L = 10 + W = 1 + + # act + res = planting_trees(trees, L, W) + + # 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() diff --git a/tests/test_graph.py b/tests/test_graph.py index e62a10226..540d29983 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -1,4 +1,20 @@ -from graph.tarjan import Tarjan +from algorithms.graph import Tarjan +from algorithms.graph import check_bipartite +from algorithms.graph.dijkstra import Dijkstra +from algorithms.graph import ford_fulkerson +from algorithms.graph import edmonds_karp +from algorithms.graph import dinic +from algorithms.graph import maximum_flow_bfs +from algorithms.graph import maximum_flow_dfs +from algorithms.graph import all_pairs_shortest_path +from algorithms.graph import bellman_ford +from algorithms.graph import count_connected_number_of_component +from algorithms.graph import prims_minimum_spanning +from algorithms.graph import check_digraph_strongly_connected +from algorithms.graph import cycle_detection +from algorithms.graph import find_path +from algorithms.graph import path_between_two_vertices_in_digraph +from algorithms.graph import strongly_connected_components_kosaraju import unittest @@ -25,7 +41,8 @@ def test_tarjan_example_1(self): } g = Tarjan(example) - self.assertEqual(g.sccs, [['F', 'G'], ['C', 'D', 'H'], ['A', 'B', 'E']]) + 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 @@ -41,4 +58,311 @@ def test_tarjan_example_2(self): } g = Tarjan(example) - self.assertEqual(g.sccs, [['A', 'B', 'E'], ['C', 'D'], ['F', 'G'], ['H']]) \ No newline at end of file + 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 TestMaximum_Flow_Bfs(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 TestMaximum_Flow_Dfs(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 TestAll_Pairs_Shortest_Path(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 + l = [[2], + [5], + [0,4], + [], + [2], + [1]] + + size = 5 + result = count_connected_number_of_component.count_components(l, size) + self.assertEqual(result, expected_result) + + def test_connected_components_with_empty_graph(self): + + """ + input : + output : 0 + """ + l = [[]] + expected_result = 0 + size = 0 + result = count_connected_number_of_component.count_components(l, size) + self.assertEqual(result, expected_result) + + def test_connected_components_without_edges_graph(self): + """ + input : 0 2 3 4 + output : 4 + """ + l = [[0], [], [2], [3], [4]] + size = 4 + expected_result = 4 + result = count_connected_number_of_component.count_components(l, 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): + V = 6 + adj = [ + [2], + [0], + [3], + [1, 4], + [5], + [4] + ] + + result = strongly_connected_components_kosaraju.Kosaraju().kosaraju(V, adj) + + # Expected result: 2 strongly connected components + self.assertEqual(result, 2) diff --git a/tests/test_greedy.py b/tests/test_greedy.py new file mode 100644 index 000000000..095f2a282 --- /dev/null +++ b/tests/test_greedy.py @@ -0,0 +1,21 @@ +from algorithms.greedy import ( + max_contiguous_subsequence_sum, +) + +import unittest + +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() \ No newline at end of file diff --git a/tests/test_heap.py b/tests/test_heap.py index c6f2c7e60..afae0d93e 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -1,13 +1,18 @@ -from heap.binary_heap import BinaryHeap -from heap.skyline import get_skyline -from heap.sliding_window_max import max_sliding_window +from algorithms.heap import ( + BinaryHeap, + get_skyline, + max_sliding_window, + k_closest +) import unittest + class TestBinaryHeap(unittest.TestCase): """ Test suite for the binary_heap data structures """ + def setUp(self): self.min_heap = BinaryHeap() self.min_heap.insert(4) @@ -22,31 +27,43 @@ def test_insert(self): # 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.currentSize) + 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(4, ret) self.assertEqual([0, 7, 50, 87, 55, 90], - self.min_heap.heap) - self.assertEqual(5, self.min_heap.currentSize) + 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] ] + 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] ] + 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() diff --git a/tests/test_histogram.py b/tests/test_histogram.py new file mode 100644 index 000000000..cd35bd216 --- /dev/null +++ b/tests/test_histogram.py @@ -0,0 +1,17 @@ +from algorithms.distribution.histogram import get_histogram + +import unittest + + +class TestListsInHistogram(unittest.TestCase): + def test_histogram(self): + list_1 = [3, 3, 2, 1] + list_2 = [2, 3, 5, 5, 5, 6, 4, 3, 7] + + self.assertEqual(get_histogram(list_1), {1: 1, 2: 1, 3: 2}) + self.assertEqual(get_histogram(list_2), + {2: 1, 3: 2, 4: 1, 5: 3, 6: 1, 7: 1}) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_iterative_segment_tree.py b/tests/test_iterative_segment_tree.py new file mode 100644 index 000000000..66d222267 --- /dev/null +++ b/tests/test_iterative_segment_tree.py @@ -0,0 +1,99 @@ +from algorithms.tree.segment_tree.iterative_segment_tree import SegmentTree +from functools import reduce + +import unittest + + +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) diff --git a/tests/test_linkedlist.py b/tests/test_linkedlist.py index 89096bc9b..6b7f1f4fa 100644 --- a/tests/test_linkedlist.py +++ b/tests/test_linkedlist.py @@ -1,19 +1,24 @@ -from linkedlist.reverse import reverse_list, reverse_list_recursive -from linkedlist.is_sorted import is_sorted -from linkedlist.remove_range import remove_range -from linkedlist.swap_in_pairs import swap_pairs -from linkedlist.rotate_list import rotate_right -from linkedlist.is_cyclic import is_cyclic -from linkedlist.merge_two_list import merge_two_list, merge_two_list_recur -from linkedlist.is_palindrome import is_palindrome, is_palindrome_stack, is_palindrome_dict - import unittest +from algorithms.linkedlist import ( + reverse_list, reverse_list_recursive, + is_sorted, + remove_range, + swap_pairs, + rotate_right, + is_cyclic, + merge_two_list, merge_two_list_recur, + is_palindrome, is_palindrome_stack, is_palindrome_dict, + RandomListNode, copy_random_pointer_v1, copy_random_pointer_v2 +) + + class Node(object): def __init__(self, x): self.val = x self.next = None + # Convert from linked list Node to list for testing def convert(head): ret = [] @@ -24,6 +29,7 @@ def convert(head): current = current.next return ret + class TestSuite(unittest.TestCase): def setUp(self): # list test for palindrome @@ -67,7 +73,6 @@ def test_is_sorted(self): self.assertFalse(is_sorted(head)) def test_remove_range(self): - # Test case: middle case. head = Node(0) head.next = Node(1) @@ -161,12 +166,50 @@ def test_merge_two_list(self): 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() diff --git a/tests/test_map.py b/tests/test_map.py new file mode 100644 index 000000000..a62656d15 --- /dev/null +++ b/tests/test_map.py @@ -0,0 +1,191 @@ +from algorithms.map import ( + HashTable, ResizableHashTable, + SeparateChainingHashTable, + word_pattern, + is_isomorphic, + is_anagram, + longest_palindromic_subsequence, +) + +import unittest + + +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() diff --git a/tests/test_maths.py b/tests/test_maths.py index a7567f72f..c4a54af03 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -1,16 +1,57 @@ -from maths.base_conversion import int2base, base2int -from maths.extended_gcd import extended_gcd -from maths.gcd import gcd, lcm -from maths.generate_strobogrammtic import gen_strobogrammatic, strobogrammatic_in_range -from maths.is_strobogrammatic import is_strobogrammatic, is_strobogrammatic2 -from maths.next_perfect_square import find_next_square, find_next_square2 -from maths.prime_check import prime_check, prime_check2 -from maths.primes_sieve_of_eratosthenes import primes -from maths.pythagoras import pythagoras -from maths.rabin_miller import is_prime -from maths.rsa import encrypt, decrypt, generate_key +from algorithms.maths import ( + power, power_recur, + int_to_base, base_to_int, + decimal_to_binary_ip, + euler_totient, + extended_gcd, + factorial, factorial_recur, + gcd, lcm, trailing_zero, gcd_bit, + gen_strobogrammatic, strobogrammatic_in_range, + is_strobogrammatic, is_strobogrammatic2, + modular_inverse, + modular_exponential, + find_next_square, find_next_square2, + prime_check, + get_primes, + pythagoras, + is_prime, + encrypt, decrypt, + combination, combination_memo, + hailstone, + cosine_similarity, + magic_number, + find_order, + find_primitive_root, + num_digits, + diffie_hellman_key_exchange, krishnamurthy_number, + num_perfect_squares, + chinese_remainder_theorem, + fft +) import unittest +import pytest + + +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): @@ -21,15 +62,47 @@ class TestBaseConversion(unittest.TestCase): unittest {[type]} -- [description] """ - def test_int2base(self): - self.assertEqual("101", int2base(5, 2)) - self.assertEqual("0", int2base(0, 2)) - self.assertEqual("FF", int2base(255, 16)) + 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)) - def test_base2int(self): - self.assertEqual(5, base2int("101", 2)) - self.assertEqual(0, base2int("0", 2)) - self.assertEqual(255, base2int("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): @@ -57,8 +130,48 @@ 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): @@ -93,6 +206,39 @@ def test_is_strobogrammatic2(self): 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 @@ -119,7 +265,8 @@ class TestPrimesSieveOfEratosthenes(unittest.TestCase): """ def test_primes(self): - self.assertEqual([2, 3, 5, 7], primes(7)) + self.assertListEqual([2, 3, 5, 7], get_primes(7)) + self.assertRaises(ValueError, get_primes, -42) class TestPrimeTest(unittest.TestCase): @@ -141,17 +288,6 @@ def test_prime_test(self): counter += 1 self.assertEqual(25, counter) - def test_prime_test2(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_check2(i): - counter += 1 - self.assertEqual(25, counter) - class TestPythagoras(unittest.TestCase): """[summary] @@ -162,7 +298,8 @@ class TestPythagoras(unittest.TestCase): """ def test_pythagoras(self): - self.assertEqual("Hypotenuse = 3.605551275463989", pythagoras(3, 2, "?")) + self.assertEqual("Hypotenuse = 3.605551275463989", + pythagoras(3, 2, "?")) class TestRabinMiller(unittest.TestCase): @@ -188,7 +325,6 @@ class TestRSA(unittest.TestCase): """ 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! @@ -201,5 +337,261 @@ def test_encrypt_decrypt(self): # 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): + chinese_remainder_theorem.solve_chinese_remainder(num, rem) + + def test_empty_lists(self): + num = [] + rem = [] + with self.assertRaises(Exception): + 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("%.3f" % abs(f)) 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("%.1f" % abs(f)) 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("%.1f" % abs(f)) 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] + realResult = [float("%.1f" % f.real) for f in fft.fft(x)] + imagResult = [float("%.1f" % f.imag) for f in fft.fft(x)] + self.assertEqual(real, realResult) + self.assertEqual(imag, imagResult) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_matrix.py b/tests/test_matrix.py new file mode 100644 index 000000000..262fdbe62 --- /dev/null +++ b/tests/test_matrix.py @@ -0,0 +1,391 @@ +from algorithms.matrix import ( + bomb_enemy, + copy_transform, + crout_matrix_decomposition, + cholesky_matrix_decomposition, + matrix_exponentiation, + matrix_inversion, + multiply, + rotate_image, + sparse_dot_vector, + spiral_traversal, + sudoku_validator, + sum_sub_squares, + sort_matrix_diagonally +) +import unittest + + +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., 2., 3.]), + sparse_dot_vector. + vector_to_index_value_list([0., 2., 2.])), + 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() diff --git a/tests/test_ml.py b/tests/test_ml.py new file mode 100644 index 000000000..1622ae1a8 --- /dev/null +++ b/tests/test_ml.py @@ -0,0 +1,40 @@ +from algorithms.ml.nearest_neighbor import ( + distance, + nearest_neighbor +) + +import unittest + + +class TestML(unittest.TestCase): + def setUp(self): + # train set for the AND-function + self.trainSetAND = {(0, 0): 0, (0, 1): 0, (1, 0): 0, (1, 1): 1} + + # train set for light or dark colors + self.trainSetLight = {(11, 98, 237): 'L', (3, 39, 96): 'D', + (242, 226, 12): 'L', (99, 93, 4): 'D', + (232, 62, 32): 'L', (119, 28, 11): 'D', + (25, 214, 47): 'L', (89, 136, 247): 'L', + (21, 34, 63): 'D', (237, 99, 120): 'L', + (73, 33, 39): 'D'} + + def test_nearest_neighbor(self): + # AND-function + self.assertEqual(nearest_neighbor((1, 1), self.trainSetAND), 1) + self.assertEqual(nearest_neighbor((0, 1), self.trainSetAND), 0) + + # dark/light color test + self.assertEqual(nearest_neighbor((31, 242, 164), + self.trainSetLight), 'L') + self.assertEqual(nearest_neighbor((13, 94, 64), + self.trainSetLight), 'D') + self.assertEqual(nearest_neighbor((230, 52, 239), + self.trainSetLight), 'L') + + def test_distance(self): + self.assertAlmostEqual(distance((1, 2, 3), (1, 0, -1)), 4.47, 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_monomial.py b/tests/test_monomial.py new file mode 100644 index 000000000..31e56f79b --- /dev/null +++ b/tests/test_monomial.py @@ -0,0 +1,220 @@ +from algorithms.maths.polynomial import Monomial +from fractions import Fraction +import math + + +import unittest + + +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() diff --git a/tests/test_polynomial.py b/tests/test_polynomial.py new file mode 100644 index 000000000..ab5674017 --- /dev/null +++ b/tests/test_polynomial.py @@ -0,0 +1,201 @@ +from algorithms.maths.polynomial import ( + Polynomial, + Monomial +) +from fractions import Fraction +import math + + +import unittest + +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 + diff --git a/tests/test_queues.py b/tests/test_queues.py index a37d95f43..5a2f4c89d 100644 --- a/tests/test_queues.py +++ b/tests/test_queues.py @@ -1,9 +1,13 @@ -from queues.queue import ArrayQueue, LinkedListQueue -from queues.max_sliding_window import max_sliding_window -from queues.reconstruct_queue import reconstruct_queue -from queues.priority_queue import PriorityQueue, PriorityQueueNode import unittest +from algorithms.queues import ( + ArrayQueue, LinkedListQueue, + max_sliding_window, + reconstruct_queue, + PriorityQueue +) + + class TestQueue(unittest.TestCase): """ Test suite for the Queue data structures. @@ -67,37 +71,40 @@ def test_LinkedListQueue(self): self.assertTrue(queue.is_empty()) -class TestSuite(unittest.TestCase): +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=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]) + 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]])) + 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. """ - Test suite for the PriorityQueue data structures. - """ + def test_PriorityQueue(self): - queue = PriorityQueue() - queue.push(3) - queue.push(4) - queue.push(1) - queue.push(6) - self.assertEqual(4,queue.size()) - self.assertEqual(str(1) + ": " + str(1),str(queue.pop())) + 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__": +if __name__ == "__main__": unittest.main() diff --git a/tests/test_search.py b/tests/test_search.py index ca55940fd..f515cfcb9 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,27 +1,35 @@ -from search.binary_search import binary_search, binary_search_recur -from search.first_occurance import first_occurance -from search.last_occurance import last_occurance -from search.search_insert import search_insert -from search.two_sum import two_sum, two_sum1, two_sum2 -from search.search_range import search_range -from search.find_min_rotate import find_min_rotate, find_min_rotate_recur -from search.search_rotate import search_rotate, search_rotate_recur +from algorithms.search import ( + binary_search, binary_search_recur, + ternary_search, + first_occurrence, + last_occurrence, + linear_search, + search_insert, + two_sum, two_sum1, two_sum2, + search_range, + find_min_rotate, find_min_rotate_recur, + search_rotate, search_rotate_recur, + jump_search, + next_greatest_letter, next_greatest_letter_v1, next_greatest_letter_v2, + interpolation_search +) import unittest + class TestSuite(unittest.TestCase): - def test_first_occurance(self): + def test_first_occurrence(self): def helper(array, query): idx = array.index(query) if query in array else None return idx array = [1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 6, 6] - self.assertEqual(first_occurance(array, 1), helper(array, 1)) - self.assertEqual(first_occurance(array, 3), helper(array, 3)) - self.assertEqual(first_occurance(array, 5), helper(array, 5)) - self.assertEqual(first_occurance(array, 6), helper(array, 6)) - self.assertEqual(first_occurance(array, 7), helper(array, 7)) - self.assertEqual(first_occurance(array, -1), helper(array, -1)) + 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] @@ -35,16 +43,31 @@ def test_binary_search(self): self.assertEqual(-1, binary_search_recur(array, 0, 11, 7)) self.assertEqual(-1, binary_search_recur(array, 0, 11, -1)) - def test_last_occurance(self): + 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(-1, 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_occurance(array, 3)) - self.assertEqual(10, last_occurance(array, 5)) - self.assertEqual(None, last_occurance(array, 7)) - self.assertEqual(0, last_occurance(array, 1)) - self.assertEqual(13, last_occurance(array, 6)) + self.assertEqual(5, last_occurrence(array, 3)) + self.assertEqual(10, last_occurrence(array, 5)) + self.assertEqual(None, 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] + 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)) @@ -68,6 +91,11 @@ def test_search_range(self): 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)) @@ -86,6 +114,40 @@ def test_search_rotate(self): 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() diff --git a/tests/test_set.py b/tests/test_set.py new file mode 100644 index 000000000..e2985c942 --- /dev/null +++ b/tests/test_set.py @@ -0,0 +1,12 @@ +from algorithms.set import ( + find_keyboard_row +) + +import unittest + + +class TestFindKeyboardRow(unittest.TestCase): + def test_find_keyboard_row(self): + self.assertEqual(["Alaska", "Dad"], + find_keyboard_row(["Hello", "Alaska", + "Dad", "Peace"])) diff --git a/tests/test_sort.py b/tests/test_sort.py index 3a62d07cd..c80290fdf 100644 --- a/tests/test_sort.py +++ b/tests/test_sort.py @@ -1,50 +1,130 @@ -from sort.bubble_sort import bubble_sort -from sort.comb_sort import comb_sort -from sort.counting_sort import counting_sort -from sort.heap_sort import max_heap_sort, min_heap_sort -from sort.insertion_sort import insertion_sort -from sort.merge_sort import merge_sort -from sort.quick_sort import quick_sort -from sort.selection_sort import selection_sort +from algorithms.sort import ( + bitonic_sort, + bogo_sort, + bubble_sort, + comb_sort, + counting_sort, + cycle_sort, + exchange_sort, + max_heap_sort, min_heap_sort, + merge_sort, + pancake_sort, + pigeonhole_sort, + quick_sort, + selection_sort, + bucket_sort, + shell_sort, + radix_sort, + gnome_sort, + cocktail_shaker_sort, + top_sort, top_sort_recursive +) import unittest + +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 + """ + for i in range(len(array) - 1): + if array[i] > array[i + 1]: + return False + + return True + + class TestSuite(unittest.TestCase): - def test_bubble_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - bubble_sort([1, 5, 65, 23, 57, 1232])) - - def test_comb_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - comb_sort([1, 5, 65, 23, 57, 1232])) - - def test_counting_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - counting_sort([1, 5, 65, 23, 57, 1232])) - self.assertEqual([-1232, -65, -57, -23, -5, -1], - counting_sort([-1, -5, -65, -23, -57, -1232])) - - def test_heap_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - max_heap_sort([1, 5, 65, 23, 57, 1232])) - self.assertEqual([1, 5, 23, 57, 65, 1232], - min_heap_sort([1, 5, 65, 23, 57, 1232])) - - def test_insertion_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - insertion_sort([1, 5, 65, 23, 57, 1232])) - - def test_merge_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - merge_sort([1, 5, 65, 23, 57, 1232])) - - def test_quick_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - quick_sort([1, 5, 65, 23, 57, 1232], 0, 5)) - - def test_selection_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - selection_sort([1, 5, 65, 23, 57, 1232])) + 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(bitonic_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]))) + + +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) + # print(res) + self.assertTrue(res.index('g') < res.index('e')) + res = top_sort(self.depGraph) + self.assertTrue(res.index('g') < res.index('e')) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_stack.py b/tests/test_stack.py index e3b85d414..93b389737 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -1,14 +1,18 @@ -from stack.is_consecutive import first_is_consecutive, second_is_consecutive -from stack.is_sorted import is_sorted -from stack.remove_min import remove_min -from stack.stutter import first_stutter, second_stutter -from stack.switch_pairs import first_switch_pairs, second_switch_pairs -from stack.valid_parenthesis import is_valid -from stack.simplify_path import simplify_path -from stack.stack import ArrayStack, LinkedListStack -from stack.ordered_stack import OrderedStack +from algorithms.stack import ( + first_is_consecutive, second_is_consecutive, + is_sorted, + remove_min, + first_stutter, second_stutter, + first_switch_pairs, second_switch_pairs, + is_valid, + simplify_path, + ArrayStack, LinkedListStack, + OrderedStack +) import unittest + + class TestSuite(unittest.TestCase): def test_is_consecutive(self): self.assertTrue(first_is_consecutive([3, 4, 5, 6, 7])) @@ -23,6 +27,7 @@ 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 @@ -130,6 +135,7 @@ def test_LinkedListStack(self): self.assertTrue(stack.is_empty()) + class TestOrderedStack(unittest.TestCase): def test_OrderedStack(self): stack = OrderedStack() diff --git a/tests/test_streaming.py b/tests/test_streaming.py new file mode 100644 index 000000000..2f59827ef --- /dev/null +++ b/tests/test_streaming.py @@ -0,0 +1,41 @@ +from algorithms.streaming.misra_gries import ( + misras_gries, +) +from algorithms.streaming import ( + one_sparse +) +import unittest + + +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, '+')])) diff --git a/tests/test_strings.py b/tests/test_strings.py index 8cee75bcf..e7a68302a 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -1,26 +1,49 @@ -from strings.add_binary import add_binary -from strings.breaking_bad import match_symbol, match_symbol_1, bracket -from strings.decode_string import decode_string -from strings.delete_reoccurring import delete_reoccurring_characters -from strings.domain_extractor import domain_name_1, domain_name_2 -from strings.encode_decode import encode, decode -from strings.group_anagrams import group_anagrams -from strings.int_to_roman import int_to_roman -from strings.is_palindrome import is_palindrome, is_palindrome_reverse, \ -is_palindrome_two_pointer, is_palindrome_stack -from strings.license_number import license_number -from strings.make_sentence import make_sentence -from strings.merge_string_checker import is_merge_recursive, is_merge_iterative -from strings.multiply_strings import multiply -from strings.one_edit_distance import is_one_edit, is_one_edit2 -from strings.rabin_karp import rabin_karp -from strings.reverse_string import * -from strings.reverse_vowel import reverse_vowel -from strings.reverse_words import reverse_words -from strings.roman_to_int import roman_to_int -from strings.strip_url_params import * -from strings.validate_coordinates import * -from strings.word_squares import word_squares +from algorithms.strings import ( + add_binary, + match_symbol, match_symbol_1, bracket, + decode_string, + delete_reoccurring_characters, + domain_name_1, domain_name_2, + encode, decode, + group_anagrams, + int_to_roman, + is_palindrome, is_palindrome_reverse, + is_palindrome_two_pointer, is_palindrome_stack, is_palindrome_deque, + is_rotated, is_rotated_v1, + license_number, + make_sentence, + is_merge_recursive, is_merge_iterative, + multiply, + is_one_edit, is_one_edit2, + rabin_karp, + ultra_pythonic, iterative, recursive, pythonic, + reverse_vowel, + reverse_words, + roman_to_int, + is_valid_coordinates_0, + word_squares, + convert_morse_word, unique_morse, + judge_circle, + strong_password, + caesar_cipher, + check_pangram, + contain_string, + count_binary_substring, + repeat_string, + text_justification, + min_distance, + min_distance_dp, + longest_common_prefix_v1, longest_common_prefix_v2, + longest_common_prefix_v3, + rotate, rotate_alt, + first_unique_char, + repeat_substring, + atbash, + longest_palindrome, + knuth_morris_pratt, + panagram, + fizzbuzz +) import unittest @@ -51,12 +74,21 @@ 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)) + 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)) + 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(('[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): @@ -93,7 +125,9 @@ class TestDomainExtractor(unittest.TestCase): """ def test_valid(self): - self.assertEqual(domain_name_1("https://github.com/SaadBenn"), "github") + self.assertEqual(domain_name_1("https://github.com/SaadBenn"), + "github") + def test_invalid(self): self.assertEqual(domain_name_2("http://google.com"), "google") @@ -108,8 +142,10 @@ class TestEncodeDecode(unittest.TestCase): 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")) + self.assertEqual(['keon', 'is', 'awesome'], + decode("4:keon2:is7:awesome")) class TestGroupAnagrams(unittest.TestCase): @@ -122,7 +158,8 @@ class TestGroupAnagrams(unittest.TestCase): def test_group_anagrams(self): self.assertEqual([['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']], \ - group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"])) + group_anagrams(["eat", "tea", "tan", "ate", "nat", + "bat"])) class TestIntToRoman(unittest.TestCase): @@ -151,19 +188,64 @@ 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] @@ -205,6 +287,7 @@ class TestMergeStringChecker(unittest.TestCase): 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")) @@ -236,6 +319,7 @@ 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")) @@ -265,13 +349,16 @@ class TestReverseString(unittest.TestCase): 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] @@ -295,7 +382,7 @@ class TestReverseWords(unittest.TestCase): 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")) + reverse_words("I am keon kim and I like pizza")) class TestRomanToInt(unittest.TestCase): @@ -321,14 +408,20 @@ def test_roman_to_int(self): # """ # 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") +# 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") +# 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") +# 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): @@ -340,11 +433,14 @@ class TestValidateCoordinates(unittest.TestCase): """ def test_valid(self): - valid_coordinates = ["-23, 25","4, -3","90, 180","-90, -180"] + 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"] + 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)) @@ -358,9 +454,279 @@ class TestWordSquares(unittest.TestCase): """ def test_word_squares(self): - self.assertEqual([['wall', 'area', 'lead', 'lady'], ['ball', 'area', 'lead', 'lady']], \ - word_squares(["area","lead","wall","lady","ball"])) + 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 TestCountBinarySubstring(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() \ No newline at end of file + unittest.main() diff --git a/tests/test_tree.py b/tests/test_tree.py new file mode 100644 index 000000000..c9d0f0fd4 --- /dev/null +++ b/tests/test_tree.py @@ -0,0 +1,180 @@ +from algorithms.tree.traversal import ( + preorder, + preorder_rec, + postorder, + postorder_rec, + inorder, + inorder_rec +) +from algorithms.tree.b_tree import BTree + +from algorithms.tree import construct_tree_postorder_preorder as ctpp + +from algorithms.tree.fenwick_tree.fenwick_tree import Fenwick_Tree + +import unittest + + +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 i 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 i 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() diff --git a/tests/test_unix.py b/tests/test_unix.py new file mode 100644 index 000000000..3cafba98f --- /dev/null +++ b/tests/test_unix.py @@ -0,0 +1,48 @@ +from algorithms.unix import ( + join_with_slash, + full_path, + split, + simplify_path_v1, simplify_path_v2 +) +import os +import unittest + + +class TestUnixPath(unittest.TestCase): + def test_join_with_slash(self): + self.assertEqual("path/to/dir/file", + join_with_slash("path/to/dir/", "file")) + self.assertEqual("path/to/dir/file", + join_with_slash("path/to/dir", "file")) + self.assertEqual("http://algorithms/part", + join_with_slash("http://algorithms", "part")) + self.assertEqual("http://algorithms/part", + join_with_slash("http://algorithms/", "part")) + + def test_full_path(self): + file_name = "file_name" + # Test full path relative + expect_path = "{}/{}".format(os.getcwd(), file_name) + self.assertEqual(expect_path, full_path(file_name)) + # Test full path with expanding user + # ~/file_name + expect_path = "{}/{}".format(os.path.expanduser('~'), file_name) + self.assertEqual(expect_path, full_path("~/{}".format(file_name))) + + def test_split(self): + # Test url path + path = "https://algorithms/unix/test.py" + expect_result = split(path) + self.assertEqual("https://algorithms/unix", expect_result[0]) + self.assertEqual("test.py", expect_result[1]) + # Test file path + path = "algorithms/unix/test.py" + expect_result = split(path) + self.assertEqual("algorithms/unix", expect_result[0]) + self.assertEqual("test.py", expect_result[1]) + + def test_simplify_path(self): + self.assertEqual("/", simplify_path_v1("/../")) + self.assertEqual("/home/foo", simplify_path_v1("/home//foo/")) + self.assertEqual("/", simplify_path_v2("/../")) + self.assertEqual("/home/foo", simplify_path_v2("/home//foo/")) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..f9182236e --- /dev/null +++ b/tox.ini @@ -0,0 +1,63 @@ +[tox] +envlist = + py35 + py36 + py37 + coverage + +[testenv] +passenv = TRAVIS TRAVIS_* +basepython = + py35: python3.5 + py36: python3.6 + py37: python3.7 +deps = + coverage + coveralls +commands = + coverage run --source=tests,algorithms -m unittest discover tests + coverage report -m + coveralls + +[testenv:py35] +passenv = CI TRAVIS TRAVIS_* +basepython = + python3.5 +deps = + pytest +commands = + python3 -m unittest discover tests + python3 -m pytest tests + +[testenv:py36] +passenv = CI TRAVIS TRAVIS_* +basepython = + python3.6 +deps = + pytest +commands = + python3 -m unittest discover tests + python3 -m pytest tests + +[testenv:py37] +passenv = CI TRAVIS TRAVIS_* +basepython = + python3.7 +deps = + pytest +commands = + python3 -m unittest discover tests + python3 -m pytest tests + +[testenv:coverage] +passenv = CI TRAVIS TRAVIS_* +skip_install = True +basepython = + python3.7 +commands = + coverage run --source=tests,algorithms -m unittest discover tests + coverage report -m + coveralls +deps = + coverage + coveralls diff --git a/tree.md b/tree.md deleted file mode 100644 index 069df396d..000000000 --- a/tree.md +++ /dev/null @@ -1,241 +0,0 @@ -``` - -. -├── array -│   ├── circular_counter.py -│   ├── flatten.py -│   ├── garage.py -│   ├── longest_non_repeat.py -│   ├── merge_intervals.py -│   ├── missing_ranges.py -│   ├── plus_one.py -│   ├── rotate_array.py -│   ├── summary_ranges.py -│   ├── three_sum.py -│   └── two_sum.py -├── backtrack -│   ├── anagram.py -│   ├── array_sum_combinations.py -│   ├── combination_sum.py -│   ├── expression_add_operators.py -│   ├── factor_combinations.py -│   ├── general_solution.md -│   ├── generate_abbreviations.py -│   ├── generate_parenthesis.py -│   ├── letter_combination.py -│   ├── palindrome_partitioning.py -│   ├── pattern_match.py -│   ├── permute.py -│   ├── permute_unique.py -│   ├── subsets.py -│   ├── subsets_unique.py -│   └── word_search.py -├── bfs -│   ├── shortest_distance_from_all_buildings.py -│   └── word_ladder.py -├── bit -│   ├── add_bitwise_operator.py -│   ├── bytes_int_conversion.py -│   ├── count_ones.py -│   ├── find_missing_number.py -│   ├── power_of_two.py -│   ├── reverse_bits.py -│   ├── single_number2.py -│   ├── single_number.py -│   └── subsets.py -├── calculator -│   └── math_parser.py -├── CODE_OF_CONDUCT.md -├── CONTRIBUTING.md -├── dfs -│   ├── all_factors.py -│   ├── count_islands.py -│   ├── pacific_atlantic.py -│   ├── sudoku_solver.py -│   └── walls_and_gates.py -├── dp -│   ├── buy_sell_stock.py -│   ├── climbing_stairs.py -│   ├── coin_change.py -│   ├── combination_sum.py -│   ├── egg_drop.py -│   ├── house_robber.py -│   ├── job_scheduling.py -│   ├── knapsack.py -│   ├── longest_increasing.py -│   ├── matrix_chain_order.py -│   ├── max_product_subarray.py -│   ├── max_subarray.py -│   ├── min_cost_path.py -│   ├── num_decodings.py -│   ├── regex_matching.py -│   ├── rod_cut.py -│   └── word_break.py -├── graph -│   ├── checkDiGraphStronglyConnected.py -│   ├── clone_graph.py -│   ├── cycle_detection.py -│   ├── find_all_cliques.py -│   ├── find_path.py -│   ├── graph.py -│   ├── markov_chain.py -│   ├── minimum_spanning_tree.py -│   ├── pathBetweenTwoVerticesInDiGraph.py -│   ├── satisfiability.py -│   ├── tarjan.py -│   ├── Transitive_Closure_DFS.py -│   └── traversal.py -├── heap -│   ├── merge_sorted_k_lists.py -│   ├── skyline.py -│   └── sliding_window_max.py -├── LICENSE -├── linkedlist -│   ├── add_two_numbers.py -│   ├── copy_random_pointer.py -│   ├── delete_node.py -│   ├── first_cyclic_node.py -│   ├── intersection.py -│   ├── is_cyclic.py -│   ├── is_palindrome.py -│   ├── kth_to_last.py -│   ├── linkedlist.py -│   ├── partition.py -│   ├── remove_duplicates.py -│   ├── reverse.py -│   ├── rotate_list.py -│   └── swap_in_pairs.py -├── map -│   ├── hashtable.py -│   ├── longest_common_subsequence.py -│   ├── randomized_set.py -│   └── valid_sudoku.py -├── maths -│   ├── base_conversion.py -│   ├── extended_gcd.py -│   ├── gcd.py -│   ├── generate_strobogrammtic.py -│   ├── is_strobogrammatic.py -│   ├── next_perfect_square.py -│   ├── nth_digit.py -│   ├── primes_sieve_of_eratosthenes.py -│   ├── prime_test.py -│   ├── pythagoras.py -│   ├── rabin_miller.py -│   ├── rsa.py -│   └── sqrt_precision_factor.py -├── matrix -│   ├── bomb_enemy.py -│   ├── copy_transform.py -│   ├── count_paths.py -│   ├── matrix_rotation.txt -│   ├── rotate_image.py -│   ├── search_in_sorted_matrix.py -│   ├── sparse_dot_vector.py -│   ├── sparse_mul.py -│   └── spiral_traversal.py -├── queues -│   ├── __init__.py -│   ├── max_sliding_window.py -│   ├── moving_average.py -│   ├── __pycache__ -│   │   └── __init__.cpython-36.pyc -│   ├── queue.py -│   ├── reconstruct_queue.py -│   └── zigzagiterator.py -├── README_CN.md -├── README.md -├── search -│   ├── binary_search.py -│   ├── first_occurance.py -│   └── last_occurance.py -├── set -│   ├── randomized_set.py -│   └── set_covering.py -├── sort -│   ├── bubble_sort.py -│   ├── comb_sort.py -│   ├── counting_sort.py -│   ├── heap_sort.py -│   ├── insertion_sort.py -│   ├── meeting_rooms.py -│   ├── merge_sort.py -│   ├── quick_sort.py -│   ├── selection_sort.py -│   ├── sort_colors.py -│   ├── topsort.py -│   └── wiggle_sort.py -├── stack -│   ├── __init__.py -│   ├── longest_abs_path.py -│   ├── simplify_path.py -│   ├── stack.py -│   └── valid_parenthesis.py -├── strings -│   ├── add_binary.py -│   ├── breaking_bad.py -│   ├── decode_string.py -│   ├── encode_decode.py -│   ├── group_anagrams.py -│   ├── int_to_roman.py -│   ├── is_palindrome.py -│   ├── license_number.py -│   ├── make_sentence.py -│   ├── multiply_strings.py -│   ├── one_edit_distance.py -│   ├── rabin_karp.py -│   ├── reverse_string.py -│   ├── reverse_vowel.py -│   ├── reverse_words.py -│   ├── roman_to_int.py -│   └── word_squares.py -├── tmp -│   └── temporary.md -├── tree -│   ├── binary_tree_paths.py -│   ├── bintree2list.py -│   ├── bst -│   │   ├── array2bst.py -│   │   ├── bst_closest_value.py -│   │   ├── BSTIterator.py -│   │   ├── delete_node.py -│   │   ├── is_bst.py -│   │   ├── kth_smallest.py -│   │   ├── lowest_common_ancestor.py -│   │   ├── predecessor.py -│   │   ├── serialize_deserialize.py -│   │   ├── successor.py -│   │   └── unique_bst.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_sum2.py -│   ├── path_sum.py -│   ├── pretty_print.py -│   ├── rbtree -│   │   └── rbtree.py -│   ├── same_tree.py -│   ├── segment_tree -│   │   └── segment_tree.py -│   ├── traversal -│   │   ├── inorder.py -│   │   ├── level_order.py -│   │   └── zigzag.py -│   ├── tree.py -│   └── trie -│   ├── add_and_search.py -│   └── trie.py -├── tree.md -└── union-find - └── count_islands.py - -28 directories, 206 files - -``` diff --git a/tree/avl/__init__.py b/tree/avl/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tree/is_balanced.py b/tree/is_balanced.py deleted file mode 100644 index 69a420e83..000000000 --- a/tree/is_balanced.py +++ /dev/null @@ -1,33 +0,0 @@ - -def is_balanced(root): - """ - O(N) solution - """ - return -1 != get_depth(root) - -def get_depth(root): - """ - return 0 if unbalanced else depth + 1 - """ - if not root: - return 0 - left = get_depth(root.left) - right = get_depth(root.right) - if abs(left-right) > 1: - return -1 - return 1 + max(left, right) - -################################ - -def is_balanced(root): - """ - O(N^2) solution - """ - left = max_height(root.left) - right = max_height(root.right) - return abs(left-right) <= 1 and is_balanced(root.left) and is_balanced(root.right) - -def max_height(root): - if not root: - return 0 - return max(max_height(root.left), max_height(root.right)) + 1 diff --git a/union-find/count_islands.py b/union-find/count_islands.py deleted file mode 100644 index 56bed5bdd..000000000 --- a/union-find/count_islands.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -A 2d grid map of m rows and n columns is initially filled with water. -We may perform an addLand operation which turns the water at position -(row, col) into a land. Given a list of positions to operate, -count the number of islands after each addLand operation. -An island is surrounded by water and is formed by connecting adjacent -lands horizontally or vertically. -You may assume all four edges of the grid are all surrounded by water. - -Given m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]. -Initially, the 2d grid grid is filled with water. -(Assume 0 represents water and 1 represents land). - -0 0 0 -0 0 0 -0 0 0 -Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land. - -1 0 0 -0 0 0 Number of islands = 1 -0 0 0 -Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land. - -1 1 0 -0 0 0 Number of islands = 1 -0 0 0 -Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land. - -1 1 0 -0 0 1 Number of islands = 2 -0 0 0 -Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land. - -1 1 0 -0 0 1 Number of islands = 3 -0 1 0 -""" - - -class Solution(object): - def num_islands2(self, m, n, positions): - ans = [] - islands = Union() - for p in map(tuple, positions): - islands.add(p) - for dp in (0, 1), (0, -1), (1, 0), (-1, 0): - q = (p[0] + dp[0], p[1] + dp[1]) - if q in islands.id: - islands.unite(p, q) - ans += [islands.count] - return ans - -class Union(object): - def __init__(self): - self.id = {} - self.sz = {} - self.count = 0 - - def add(self, p): - self.id[p] = p - self.sz[p] = 1 - self.count += 1 - - def root(self, i): - while i != self.id[i]: - self.id[i] = self.id[self.id[i]] - i = self.id[i] - return i - - def unite(self, p, q): - i, j = self.root(p), self.root(q) - if i == j: - return - if self.sz[i] > self.sz[j]: - i, j = j, i - self.id[i] = j - self.sz[j] += self.sz[i] - self.count -= 1