├── .coverage ├── .github └── workflows │ ├── doc.yml │ ├── python-package.yml │ └── python-publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST ├── Makefile ├── README.md ├── codecov.yaml ├── docs ├── Makefile ├── _static │ ├── example_dijkstra.png │ ├── example_dijkstra.tex │ ├── logo_B.png │ ├── logo_B.xcf │ ├── logo_W.png │ └── logo_white.png ├── conf.py ├── content.rst ├── example_dijkstra.py ├── index.rst ├── install.rst ├── quickstart.rst ├── requirements.txt └── tryalgo │ └── modules.rst ├── examples ├── README.rst ├── TryAlgo Maps in Paris.ipynb ├── arithm_expr_target.py ├── coin_change.py ├── paris.txt ├── pathfinding_in_paris.html └── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── test_PC_tree.py ├── test_arithm.py ├── test_freivalds.py ├── test_interval_cover.py ├── test_next_permutation.py ├── test_our_std.py └── test_tryalgo.py └── tryalgo ├── ABOUT.txt ├── PC_tree.py ├── README.rst ├── Sequence.py ├── __init__.py ├── a_star.py ├── anagrams.py ├── arithm.py ├── arithm_expr_eval.py ├── arithm_expr_target.py ├── bellman_ford.py ├── bfs.py ├── biconnected_components.py ├── binary_search.py ├── bipartite_matching.py ├── bipartite_vertex_cover.py ├── closest_points.py ├── closest_values.py ├── convex_hull.py ├── dancing_links.py ├── dfs.py ├── dijkstra.py ├── dilworth.py ├── dinic.py ├── dist_grid.py ├── dyn_prog_tricks.py ├── edmonds_karp.py ├── eulerian_tour.py ├── fast_exponentiation.py ├── fenwick.py ├── fft.py ├── floyd_warshall.py ├── ford_fulkerson.py ├── freivalds.py ├── gale_shapley.py ├── gauss_jordan.py ├── graph.py ├── graph01.py ├── hamiltonian_cycle.py ├── horn_sat.py ├── huffman.py ├── interval_cover.py ├── interval_tree.py ├── intervals_union.py ├── karatsuba.py ├── knapsack.py ├── knuth_morris_pratt.py ├── kruskal.py ├── kuhn_munkres.py ├── kuhn_munkres_n4.py ├── laser_mirrors.py ├── left_right_inversions.py ├── levenshtein.py ├── longest_common_subsequence.py ├── longest_increasing_subsequence.py ├── lowest_common_ancestor.py ├── majority.py ├── manacher.py ├── matrix_chain_mult.py ├── max_interval_intersec.py ├── merge_ordered_lists.py ├── min_mean_cycle.py ├── next_permutation.py ├── our_heap.py ├── our_queue.py ├── our_std.py ├── pareto.py ├── partition_refinement.py ├── permutation_rank.py ├── polygon.py ├── predictive_text.py ├── primes.py ├── rabin_karp.py ├── range_minimum_query.py ├── rectangles_from_grid.py ├── rectangles_from_histogram.py ├── rectangles_from_points.py ├── roman_numbers.py ├── scalar.py ├── shortest_cycle.py ├── skip_list.py ├── strongly_connected_components.py ├── subsetsum.py ├── subsetsum_divide.py ├── sudoku.py ├── suffix_array.py ├── three_partition.py ├── topological_order.py ├── tortoise_hare.py ├── trie.py ├── two_sat.py ├── union_rectangles.py └── windows_k_distinct.py /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/.coverage -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: [push, pull_request, workflow_dispatch] 3 | jobs: 4 | docs: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-python@v2 9 | - name: Install dependencies 10 | run: | 11 | pip install -r docs/requirements.txt 12 | - name: Sphinx build 13 | run: | 14 | sphinx-apidoc -f -o docs/tryalgo tryalgo 15 | sphinx-build docs _build 16 | - name: Deploy 17 | uses: peaceiris/actions-gh-pages@v3 18 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} 19 | with: 20 | publish_branch: gh-pages 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | publish_dir: _build/ 23 | force_orphan: true 24 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["pypy-3.8", "3.8", "3.9", "3.10"] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install flake8 pytest 31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | run: | 40 | pytest 41 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish distribution 📦 to Test PyPI 36 | uses: pypa/gh-action-pypi-publish@release/v1 37 | with: 38 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 39 | repository_url: https://test.pypi.org/legacy/ 40 | - name: Publish distribution 📦 to PyPI 41 | if: startsWith(github.ref, 'refs/tags') 42 | uses: pypa/gh-action-pypi-publish@release/v1 43 | with: 44 | password: ${{ secrets.PYPI_API_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .mypy_cache 2 | .ipynb_checkpoints 3 | .DS_Store 4 | __pycache__ 5 | _build 6 | build 7 | /dist 8 | venv 9 | *.pyc 10 | *.bak 11 | docs/_static 12 | docs/auto_examples 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | ## 1.7.1 5 | 6 | - corrected a bug in PC_tree. When splitting along the terminal path, some nodes can become full in fact, because they are detached from their neighbors along the path before splitting. 7 | 8 | ## 1.7.0 9 | 10 | - added suffix_array 11 | 12 | ## 1.6.1 13 | 14 | - corrected dyn_prog_Monge 15 | - added opt_bin_search_tree1, opt_bin_search_tree2 and decode_root_matrix_to_level 16 | 17 | ## 1.6 18 | 19 | - Added the data structure PC_tree 20 | - Improved the implementations of maximum cardinality bipartite matching 21 | 22 | ## 1.5 23 | 24 | - Corrected a bug in GraphNamedVertices. Now only the minimum weight edge is kept among multiple edges with same endpoints. 25 | - Added algorithm a_star to compute shortest paths 26 | - Added hamiltonian_cycle to compute a Hamiltonian cycle 27 | - Started to add types to some function parameters 28 | 29 | ## 1.4 30 | 31 | - Move to GitHub Actions and drop Python 2.7 support completely 32 | - Added a Graph class in the module graph, which allows accessing vertices by names instead of indices 33 | - Added alternative versions of union_rectangles 34 | - Added an alternative version of bellman_ford which marks with distance -infinity the vertices reachable from the source by paths of arbitrary small weight 35 | - Added an alternative version of subsetsum 36 | - Added FenwickMin, a minimum variant of Fenwick Trees 37 | - Added module fft for the Fast Fourier Transformation 38 | - Added module karatsuba for multiplying polynomials 39 | - Added module pareto for computing the Pareto set in 2 or 3 dimensions 40 | - An alternative version of floyd_warshall added by Pascal Ortiz 41 | - Changed the web host of the documentation 42 | - Corrected the function building the Huffman tree 43 | - Fenwick Trees indices now start at zero 44 | - Removed erroneous PQ_trees 45 | - Renamed module eratosthene into primes. Added the Gries-Misra sieve in this module 46 | - Simplified interval_cover 47 | - Simplified knuth_morris_pratt.maximum_border_length 48 | 49 | ## 1.3 50 | 51 | - Added LazySegmentTree 52 | - Added shortest_cycle 53 | - Functions and classes are now provided also directly by tryalgo without the need to specify the module 54 | 55 | ## 1.2 56 | 57 | - Added horn\_sat 58 | - Added partition_refinement 59 | - Added left\_right\_inversions 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jill-Jênn Vie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | tryalgo/__init__.py 4 | tryalgo/anagrams.py 5 | tryalgo/arithm.py 6 | tryalgo/arithm_expr_eval.py 7 | tryalgo/arithm_expr_target.py 8 | tryalgo/bellman_ford.py 9 | tryalgo/bfs.py 10 | tryalgo/biconnected_components.py 11 | tryalgo/binary_search.py 12 | tryalgo/bipartite_matching.py 13 | tryalgo/bipartite_vertex_cover.py 14 | tryalgo/closest_points.py 15 | tryalgo/closest_values.py 16 | tryalgo/convex_hull.py 17 | tryalgo/dancing_links.py 18 | tryalgo/dfs.py 19 | tryalgo/dijkstra.py 20 | tryalgo/dilworth.py 21 | tryalgo/dinic.py 22 | tryalgo/dist_grid.py 23 | tryalgo/edmonds_karp.py 24 | tryalgo/eulerian_tour.py 25 | tryalgo/fast_exponentiation.py 26 | tryalgo/fenwick.py 27 | tryalgo/floyd_warshall.py 28 | tryalgo/ford_fulkerson.py 29 | tryalgo/freivalds.py 30 | tryalgo/gale_shapley.py 31 | tryalgo/gauss_jordan.py 32 | tryalgo/graph.py 33 | tryalgo/graph01.py 34 | tryalgo/horn_sat.py 35 | tryalgo/huffman.py 36 | tryalgo/interval_cover.py 37 | tryalgo/interval_tree.py 38 | tryalgo/intervals_union.py 39 | tryalgo/knapsack.py 40 | tryalgo/knuth_morris_pratt.py 41 | tryalgo/knuth_morris_pratt_border.py 42 | tryalgo/kruskal.py 43 | tryalgo/kuhn_munkres.py 44 | tryalgo/kuhn_munkres_n4.py 45 | tryalgo/laser_mirrors.py 46 | tryalgo/left_right_inversions.py 47 | tryalgo/levenshtein.py 48 | tryalgo/longest_common_subsequence.py 49 | tryalgo/longest_increasing_subsequence.py 50 | tryalgo/lowest_common_ancestor.py 51 | tryalgo/majority.py 52 | tryalgo/manacher.py 53 | tryalgo/matrix_chain_mult.py 54 | tryalgo/max_interval_intersec.py 55 | tryalgo/merge_ordered_lists.py 56 | tryalgo/min_mean_cycle.py 57 | tryalgo/next_permutation.py 58 | tryalgo/our_heap.py 59 | tryalgo/our_queue.py 60 | tryalgo/partition_refinement.py 61 | tryalgo/permutation_rank.py 62 | tryalgo/polygon.py 63 | tryalgo/predictive_text.py 64 | tryalgo/primes.py 65 | tryalgo/rabin_karp.py 66 | tryalgo/range_minimum_query.py 67 | tryalgo/rectangles_from_grid.py 68 | tryalgo/rectangles_from_histogram.py 69 | tryalgo/rectangles_from_points.py 70 | tryalgo/roman_numbers.py 71 | tryalgo/scalar.py 72 | tryalgo/shortest_cycle.py 73 | tryalgo/skip_list.py 74 | tryalgo/strongly_connected_components.py 75 | tryalgo/subsetsum.py 76 | tryalgo/subsetsum_divide.py 77 | tryalgo/sudoku.py 78 | tryalgo/three_partition.py 79 | tryalgo/topological_order.py 80 | tryalgo/trie.py 81 | tryalgo/two_sat.py 82 | tryalgo/union_rectangles.py 83 | tryalgo/windows_k_distinct.py 84 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help:: 2 | @echo "make pycodestyle run pycodestyle on Python source" 3 | @echo "make pylint run pylint on Python source" 4 | 5 | tests:: 6 | python3 -m unittest 7 | mypy tryalgo/*.py 8 | 9 | pycodestyle:: 10 | -@find setup.py tryalgo -type f -name '*.py' | xargs pycodestyle 11 | 12 | pylint:: 13 | -@find setup.py tryalgo -type f -name '*.py' | xargs pylint --disable=C0103 -j 4 14 | 15 | .PHONY: help pycodestyle pylint bin docs 16 | 17 | docs:: 18 | cd docs/_static && convert logo_W.png logo_B.png +append logo_white.png 19 | sphinx-apidoc -f -o docs/tryalgo tryalgo 20 | sphinx-build docs docs/_build 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI](https://img.shields.io/pypi/v/tryalgo.svg)](https://pypi.python.org/pypi/tryalgo/) 2 | [![PyPI](https://img.shields.io/pypi/pyversions/tryalgo.svg)](https://pypi.python.org/pypi/tryalgo/) 3 | ![Pylint score](https://mperlet.github.io/pybadge/badges/10.svg) 4 | [![Codecov](https://img.shields.io/codecov/c/github/jilljenn/tryalgo.svg)](https://codecov.io/gh/jilljenn/tryalgo/) 5 | 6 | # Algorithmic Problem Solving 7 | 8 | Algorithms and data structures for preparing programming competitions (e.g. ICPC, [see more](https://tryalgo.org/contests/)) and coding interviews. 9 | By Christoph Dürr and Jill-Jênn Vie. 10 | 11 | [Our book](https://tryalgo.org/book) is available in French, English, Simplified and Traditional Chinese. 12 | 13 | ## Install 14 | 15 | pip install tryalgo 16 | 17 | ## Documentation 18 | 19 | - [Documentation](http://jilljenn.github.io/tryalgo/) of tryalgo 1.4 20 | - [Blog tryalgo.org](http://tryalgo.org) in French and English 21 | 22 | ## Demo: [TryAlgo in Paris](http://nbviewer.jupyter.org/github/jilljenn/tryalgo/blob/master/examples/TryAlgo%20Maps%20in%20Paris.ipynb) 23 | 24 | Shortest paths on the graph of Paris. 25 | 26 | To run it yourself: 27 | 28 | pip install -r examples/requirements.txt 29 | jupyter notebook # Then go to examples folder 30 | 31 | 32 | 33 | ## Usage 34 | 35 | **Dynamic programming** some example with coin change: 36 | 37 | ```python 38 | from tryalgo import coin_change 39 | 40 | print(coin_change([3, 5, 11], 29)) # True because 29 = 6 x 3 + 0 x 5 + 1 x 11 41 | ``` 42 | 43 | ***Des chiffres et des lettres*** (that inspired *Countdown*) 44 | 45 | ```python 46 | from tryalgo.arithm_expr_target import arithm_expr_target 47 | 48 | arithm_expr_target([25, 50, 75, 100, 3, 6], 952) 49 | ``` 50 | 51 | Returns `'((((75*3)*(100+6))-50)/25)=952'`. 52 | 53 | ## Tests 54 | 55 | All algorithms are thoroughly tested. These tests can be used to [practice your programming skills](https://tryalgo.org/en/miscellaneous/2019/08/10/how-to-practice-algorithms-with-tryalgo/)! 56 | 57 | ```python 58 | python -m unittest 59 | ``` 60 | 61 | Most snippets from the book are within 76 columns (French version) or 75 columns (English version). 62 | 63 | Our code is checked. Using optional requirements, you can check it too: 64 | 65 | pip install pycodestyle pylint 66 | make pycodestyle # PEP8 67 | make pylint 68 | 69 | ## Found a bug? 70 | 71 | Please [drop an issue](https://github.com/jilljenn/tryalgo/issues). 72 | 73 | ## Authors 74 | 75 | © 2016–2023, Christoph Dürr and Jill-Jênn Vie (vie@jill-jenn.net). 76 | Released under the MIT License. 77 | 78 | ## Contributors 79 | 80 | Thanks! 81 | 82 | - Louis Abraham 83 | - Lilian Besson 84 | - Xavier Carcelle 85 | - Stéphane Henriot 86 | - Ryan Lahfa 87 | - Olivier Marty 88 | - Samuel Tardieu 89 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: "4b52f970-610e-4954-8b2e-ea6cdbbf1b3e" 3 | ignore: 4 | - "tests/test_*.py" 5 | -------------------------------------------------------------------------------- /docs/_static/example_dijkstra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/docs/_static/example_dijkstra.png -------------------------------------------------------------------------------- /docs/_static/example_dijkstra.tex: -------------------------------------------------------------------------------- 1 | \documentclass{standalone} 2 | \usepackage{tikz} 3 | 4 | \begin{document} 5 | \begin{tikzpicture} 6 | \node[circle,draw,fill=black,text=white] (s) at (0,0) {0}; 7 | %\node[left=1em] {$s$}; 8 | \node[circle,draw] (a) at (1.5,1.5) {1}; 9 | \node[circle,draw] (b) at (3,1.5) {2}; 10 | \node[circle,draw] (c) at (3,0) {3}; 11 | \node[circle,draw] (d) at (4.5,3) {4}; 12 | \node[circle,draw] (e) at (4.5,1.5) {5}; 13 | \node[circle,draw] (f) at (4.5,0) {6}; 14 | \node[circle,draw,fill=white] (g) at (6,3) {7}; 15 | \node[circle,draw,fill=white] (h) at (6,1.5) {8}; 16 | \node[circle,draw,fill=white] (i) at (6,0) {9}; 17 | \node[circle,draw,fill=white] (j) at (7.5,1.5) {10}; 18 | \node[circle,draw,fill=white] (k) at (7.5,0) {11}; 19 | \draw (s) edge node[above=2pt] {1} (a); 20 | \draw (s) edge node[above=2pt] {4} (c); 21 | \draw (a) edge node[above=2pt] {1} (b); 22 | \draw (a) edge[-] node[above=2pt] {3} (c); 23 | \draw (b) edge node[above=2pt] {3} (d); 24 | \draw (c) edge node[above=2pt] {2} (e); 25 | \draw (c) edge node[above=2pt] {2} (f); 26 | \draw (b) edge[-] node[above=2pt] {8} (e); 27 | \draw (d) edge[-] node[above=2pt] {1} (g); 28 | \draw (e) edge[-] node[above=2pt] {2} (g); 29 | \draw (e) edge[-] node[above=2pt] {7} (h); 30 | \draw (f) edge[-] node[above=2pt] {3} (h); 31 | \draw (f) edge[-] node[above=2pt] {2} (i); 32 | \draw (g) edge[-] node[above=2pt] {3} (j); 33 | \draw (h) edge[-] node[above=2pt] {2} (j); 34 | \draw (i) edge[-] node[above=2pt] {1} (k); 35 | \end{tikzpicture} 36 | \end{document} -------------------------------------------------------------------------------- /docs/_static/logo_B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/docs/_static/logo_B.png -------------------------------------------------------------------------------- /docs/_static/logo_B.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/docs/_static/logo_B.xcf -------------------------------------------------------------------------------- /docs/_static/logo_W.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/docs/_static/logo_W.png -------------------------------------------------------------------------------- /docs/_static/logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/docs/_static/logo_white.png -------------------------------------------------------------------------------- /docs/example_dijkstra.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from tryalgo.dijkstra import dijkstra 4 | from tryalgo.graph import listlist_and_matrix_to_listdict 5 | 6 | graph = [[1, 3], 7 | [0, 2, 3], 8 | [1, 4, 5], 9 | [0, 1, 5, 6], 10 | [2, 7], 11 | [2, 3, 7, 8], 12 | [3, 8, 9], 13 | [4, 5, 10], 14 | [5, 6, 10], 15 | [6, 11], 16 | [7, 8], 17 | [9]] 18 | 19 | _ = None 20 | # 0 1 2 3 4 5 6 7 8 9 10 11 21 | weights = [[_, 1, _, 4, _, _, _, _, _, _, _, _], # 0 22 | [1, _, 1, 3, _, _, _, _, _, _, _, _], # 1 23 | [_, 1, _, _, 3, 8, _, _, _, _, _, _], # 2 24 | [4, 3, _, _, _, 2, 2, _, _, _, _, _], # 3 25 | [_, _, 3, _, _, _, _, 1, _, _, _, _], # 4 26 | [_, _, 8, 2, _, _, _, 2, 7, _, _, _], # 5 27 | [_, _, _, 2, _, _, _, _, 3, 2, _, _], # 6 28 | [_, _, _, _, 1, 2, _, _, _, _, 3, _], # 7 29 | [_, _, _, _, _, 7, 3, _, _, _, 2, _], # 8 30 | [_, _, _, _, _, _, 2, _, _, _, _, 1], # 9 31 | [_, _, _, _, _, _, _, 3, 2, _, _, _], #10 32 | [_, _, _, _, _, _, _, _, _, 1, _, _]] #11 33 | 34 | dist, prec = dijkstra(graph, weights, source=0) 35 | 36 | print(dist[10]) 37 | print("%i %i %i %i %i %i" % (10, prec[10], prec[prec[10]], prec[prec[prec[10]]], 38 | prec[prec[prec[prec[10]]]], prec[prec[prec[prec[prec[10]]]]])) 39 | 40 | sparse_graph = listlist_and_matrix_to_listdict(weights) 41 | 42 | 43 | # provides the same behavior 44 | 45 | dist, prec = dijkstra(graph, weights, source=0) 46 | 47 | print(dist[10]) 48 | print("%i %i %i %i %i %i" % (10, prec[10], prec[prec[10]], prec[prec[prec[10]]], 49 | prec[prec[prec[prec[10]]]], prec[prec[prec[prec[prec[10]]]]])) 50 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. tryalgo documentation master file, created by 2 | sphinx-quickstart on Sat Nov 7 13:03:23 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | 8 | Welcome to tryalgo's documentation! 9 | =================================== 10 | 11 | This Python library implements various algorithms and data structures such as graph, string and computational geometry problems. 12 | 13 | Explanations for most of these algorithms are available in the book: `Programmation efficace : les 128 algorithmes qu'il faut avoir compris et codés en Python au cours de sa vie `_ and its translations. 14 | 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | install 21 | quickstart 22 | content 23 | auto_examples/index 24 | tryalgo/modules 25 | 26 | * :ref:`modindex` 27 | * :ref:`genindex` 28 | * `GitHub Project `_ 29 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ------------ 3 | 4 | With PIP 5 | :::::::: 6 | 7 | You can install the tryalgo package using pip: :: 8 | 9 | $ pip3 install tryalgo 10 | 11 | From PyPI 12 | ::::::::: 13 | 14 | Download the `tar.gz file from PyPI `_ and extract it. The library consists of a directory named `tryalgo` containing several Python modules. 15 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quick start 2 | ----------- 3 | 4 | Coin change 5 | ::::::::::: 6 | 7 | Here is an dynamic programming example, with coin change (`6 lines of source code <_modules/tryalgo/subsetsum.html#coin_change>`__): :: 8 | 9 | #!/usr/bin/env python3 10 | 11 | from tryalgo import coin_change 12 | 13 | print(coin_change([3, 5, 11], 29)) 14 | 15 | Which should print :code:`True` because 29 can be expressed as the linear combination 6*3 + 0*5 + 1*11. 16 | 17 | Longest palindrome substring of a string 18 | :::::::::::::::::::::::::::::::::::::::: 19 | 20 | In order to find the longest palindrome substring of a string, you can use the implementation of Manacher's algorithm (`source <_modules/tryalgo/manacher.html#manacher>`__) as follows: :: 21 | 22 | from tryalgo import manacher 23 | print(manacher("babcbabcbaccba")) 24 | 25 | which will print (1,10). Indeed, the substring from index 1 to index 10 (excluding position 10) is the palindrome "abcbabcba". 26 | 27 | Pathfinding 28 | ::::::::::: 29 | 30 | Now, suppose you want to compute the shortest paths in the following graph starting at vertex 0. 31 | 32 | .. image:: _static/example_dijkstra.png 33 | :width: 400 px 34 | 35 | 36 | First, we need to encode this graph with a an adjacency list data structure :code:`graph`, which format we call *listlist*, where :code:`graph[u]` is the list of neighbors of vertex :code:`u`. The edge weights are simply encoded in a squared matrix: :: 37 | 38 | graph = [[1, 3], 39 | [0, 2, 3], 40 | [1, 4, 5], 41 | [0, 1, 5, 6], 42 | [2, 7], 43 | [2, 3, 7, 8], 44 | [3, 8, 9], 45 | [4, 5, 10], 46 | [5, 6, 10], 47 | [6, 11], 48 | [7, 8], 49 | [9]] 50 | 51 | _ = None 52 | # 0 1 2 3 4 5 6 7 8 9 10 11 53 | weights = [[_, 1, _, 4, _, _, _, _, _, _, _, _], # 0 54 | [1, _, 1, 3, _, _, _, _, _, _, _, _], # 1 55 | [_, 1, _, _, 3, 8, _, _, _, _, _, _], # 2 56 | [4, 3, _, _, _, 2, 2, _, _, _, _, _], # 3 57 | [_, _, 3, _, _, _, _, 1, _, _, _, _], # 4 58 | [_, _, 8, 2, _, _, _, 2, 7, _, _, _], # 5 59 | [_, _, _, 2, _, _, _, _, 3, 2, _, _], # 6 60 | [_, _, _, _, 1, 2, _, _, _, _, 3, _], # 7 61 | [_, _, _, _, _, 7, 3, _, _, _, 2, _], # 8 62 | [_, _, _, _, _, _, 2, _, _, _, _, 1], # 9 63 | [_, _, _, _, _, _, _, 3, 2, _, _, _], #10 64 | [_, _, _, _, _, _, _, _, _, 1, _, _]] #11 65 | 66 | 67 | The shortest path can be computed using Dijkstra's algorithm, also known as *lowest-cost search*. Our implementation returns the table of distances from the source and a predecessor table describing the shortest path tree: :: 68 | 69 | from tryalgo import dijkstra 70 | 71 | dist, prec = dijkstra(graph, weights, source=0) 72 | 73 | node = 10 74 | print(dist[10]) # Will print 9, the distance from node 0 to node 10 75 | path = [node] 76 | while prec[node] is not None: 77 | node = prec[node] 78 | path.append(node) 79 | print(path[::-1]) # Will print [0, 1, 2, 4, 7, 10], a shortest path from 0 to 10 80 | 81 | If your graph is sparse (contains few arcs), then you might want to represent it using dictionaries. Formally, the sparse graph representation is a list of dictionaries :code:`sparse` such that :code:`v` belongs to :code:`sparse[u]` if there is an arc :code:`(u,v)` of weight :code:`sparse[u][v]`. 82 | We call this graph format *listdict*. For example, the above graph would be represented as: :: 83 | 84 | [{1: 1, 3: 4}, 85 | {0: 1, 2: 1, 3: 3}, 86 | {1: 1, 4: 3, 5: 8}, 87 | {0: 4, 1: 3, 5: 2, 6: 2}, 88 | {2: 3, 7: 1}, 89 | {2: 8, 3: 2, 7: 2, 8: 7}, 90 | {3: 2, 8: 3, 9: 2}, 91 | {4: 1, 5: 2, 10: 3}, 92 | {5: 7, 6: 3, 10: 2}, 93 | {6: 2, 11: 1}, 94 | {7: 3, 8: 2}, 95 | {9: 1}] 96 | 97 | This data structure encodes both the graph and the arc weights, hence it is possible to invoke the function the following way: :: 98 | 99 | dist, prec = dijkstra(sparse, sparse, source=0) 100 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-gallery 3 | Pillow 4 | git+https://github.com/pydata/pydata-sphinx-theme # Waiting for 0.13 5 | -------------------------------------------------------------------------------- /docs/tryalgo/modules.rst: -------------------------------------------------------------------------------- 1 | tryalgo 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | tryalgo 8 | -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Gallery of examples 2 | ------------------- 3 | 4 | Here are some examples from the tryalgo package. 5 | -------------------------------------------------------------------------------- /examples/arithm_expr_target.py: -------------------------------------------------------------------------------- 1 | """ 2 | Countdown 3 | ========= 4 | """ 5 | 6 | from tryalgo.arithm_expr_target import arithm_expr_target 7 | 8 | arithm_expr_target([25, 50, 75, 100, 3, 6], 952) 9 | 10 | # %% 11 | # Returns :code:`((((75*3)*(100+6))-50)/25)=952`. 12 | # 13 | # See on our blog the `original Countdown video `_ behind this example. 14 | -------------------------------------------------------------------------------- /examples/coin_change.py: -------------------------------------------------------------------------------- 1 | """ 2 | Coin change 3 | =========== 4 | """ 5 | 6 | from tryalgo import coin_change 7 | from tryalgo.subsetsum import coin_change 8 | 9 | 10 | print(coin_change([3, 5, 11], 29)) # True because 29 = 6 x 3 + 0 x 5 + 1 x 11 11 | 12 | # %% 13 | # An explanation of this code is given (in French) on our blog tryalgo.org by Clémence Réda: 14 | # 15 | # - `Rendu de monnaie, bases de programmation dynamique `_ 16 | -------------------------------------------------------------------------------- /examples/pathfinding_in_paris.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 75 | -------------------------------------------------------------------------------- /examples/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | tryalgo 3 | folium 4 | geopy 5 | matplotlib 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Setup file 5 | """ 6 | 7 | from pathlib import Path 8 | from setuptools import setup 9 | this_directory = Path(__file__).parent 10 | long_description = (this_directory / "README.md").read_text() 11 | 12 | 13 | setup( 14 | name='tryalgo', 15 | version='1.7.1', 16 | description=( 17 | 'Algorithms and data structures ' 18 | 'for preparing programming competitions' 19 | ), 20 | long_description=long_description, 21 | long_description_content_type='text/markdown', 22 | author='Jill-Jênn Vie and Christoph Dürr', 23 | author_email='christoph.durr@lip6.fr', 24 | license='MIT', 25 | url='https://jilljenn.github.io/tryalgo/', 26 | keywords='algorithms data-structures programming competition', 27 | packages=['tryalgo'], 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 31 | # Indicate who your project is intended for 32 | 'Intended Audience :: Developers', 33 | 'Topic :: Software Development :: Build Tools', 34 | 35 | # Pick your license as you wish (should match "license" above) 36 | 'License :: OSI Approved :: MIT License', 37 | 'Programming Language :: Python :: 3', 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Tryalgo is a python library of algorithms and data structures is implemented 4 | by Christoph Dürr (CNRS, UPMC, Paris 6) 5 | and Jill-Jênn Vie (Université Paris-Sud). 6 | It is documented in the book "Programmation efficace, 128 Algorithmes qu'il 7 | faut avoir compris et codés en Python", editor Ellipses. 8 | """ 9 | -------------------------------------------------------------------------------- /tests/test_arithm.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | # jill-jênn vie et christoph dürr - 2020 3 | 4 | # pylint: disable=missing-docstring 5 | import unittest 6 | from tryalgo.arithm import inv, pgcd, binom, binom_modulo 7 | 8 | 9 | class TestArithm(unittest.TestCase): 10 | 11 | def test_inv(self): 12 | self.assertEqual(inv(8, 17), 15) 13 | 14 | def test_pgcd(self): 15 | self.assertEqual(pgcd(12, 18), 6) 16 | 17 | def test_binom(self): 18 | self.assertEqual(binom(4, 2), 6) 19 | 20 | def test_binom_modulo(self): 21 | self.assertEqual(binom_modulo(5, 2, 3), 1) 22 | -------------------------------------------------------------------------------- /tests/test_freivalds.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | # jill-jênn vie et christoph dürr - 2020 3 | 4 | # pylint: disable=missing-docstring 5 | import unittest 6 | try: 7 | from unittest.mock import patch 8 | except ImportError: 9 | from mock import patch 10 | from tryalgo.freivalds import freivalds, readint, readmatrix, readarray 11 | 12 | 13 | class TestNextPermutation(unittest.TestCase): 14 | 15 | def test_freivalds(self): 16 | A = [[2, 3], [3, 4]] 17 | B = [[1, 0], [1, 2]] 18 | C = [[5, 6], [7, 8]] 19 | self.assertTrue(freivalds(A, B, C)) 20 | # [!] might fail with small probability 21 | 22 | def test_readint(self): 23 | with patch('sys.stdin.readline', return_value="1"): 24 | self.assertEqual(readint(), 1) 25 | 26 | def test_readarray(self): 27 | with patch('sys.stdin.readline', return_value="1 2 3"): 28 | self.assertEqual(readarray(int), [1, 2, 3]) 29 | 30 | def test_readmatrix(self): 31 | with patch('sys.stdin.readline', return_value="1 2 3"): 32 | self.assertEqual(readmatrix(3), [[1, 2, 3], [1, 2, 3], [1, 2, 3]]) 33 | -------------------------------------------------------------------------------- /tests/test_interval_cover.py: -------------------------------------------------------------------------------- 1 | # jill-jênn vie et christoph dürr - 2020 2 | # coding=utf8 3 | 4 | # pylint: disable=missing-docstring 5 | import unittest 6 | from tryalgo.interval_cover import _solve, interval_cover 7 | 8 | 9 | class TestIntervalCover(unittest.TestCase): 10 | 11 | def test_solve(self): 12 | self.assertEqual(_solve([(0, 0), (5, 0)], 3), 1) 13 | 14 | def test_interval_cover(self): 15 | L = [([(0, 1)], 1), 16 | ([(0, 3), (1, 2)], 1), 17 | ([(0, 2), (1, 3)], 1), 18 | ([(0, 2), (2, 3)], 1), 19 | ([(0, 2), (3, 4)], 2), 20 | ([(0, 4), (1, 3), (2, 6), (5, 8), (7, 9), (9, 10)], 3)] 21 | for instance, res in L: 22 | self.assertEqual(len(interval_cover(instance)), res) 23 | -------------------------------------------------------------------------------- /tests/test_next_permutation.py: -------------------------------------------------------------------------------- 1 | # jill-jênn vie et christoph dürr - 2020 2 | # coding=utf8 3 | 4 | # pylint: disable=missing-docstring 5 | import unittest 6 | from tryalgo.next_permutation import next_permutation, solve_word_addition 7 | 8 | 9 | class TestNextPermutation(unittest.TestCase): 10 | 11 | def test_solve_word_addition(self): 12 | self.assertEqual(solve_word_addition(["A", "B", "C"]), 32) 13 | self.assertEqual(solve_word_addition(["A", "B", "A"]), 0) 14 | 15 | def test_next_permutation(self): 16 | L = [2, 2, 0, 0, 1, 1, 0] 17 | self.assertEqual(next_permutation(L), True) 18 | self.assertEqual(L, [2, 2, 0, 1, 0, 0, 1]) 19 | L = [2, 2, 1, 1, 0, 0, 0] 20 | self.assertEqual(next_permutation(L), False) 21 | L = [2] 22 | self.assertEqual(next_permutation(L), False) 23 | L = [] 24 | self.assertEqual(next_permutation(L), False) 25 | -------------------------------------------------------------------------------- /tests/test_our_std.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | # jill-jênn vie et christoph dürr - 2020 3 | 4 | # pylint: disable=missing-docstring 5 | import unittest 6 | try: 7 | from unittest.mock import patch 8 | except ImportError: 9 | from mock import patch 10 | from tryalgo.our_std import readint, readstr, readmatrix, readarray 11 | 12 | 13 | class TestOurStd(unittest.TestCase): 14 | 15 | def test_readint(self): 16 | with patch('sys.stdin.readline', return_value="1"): 17 | self.assertEqual(readint(), 1) 18 | 19 | def test_readstr(self): 20 | with patch('sys.stdin.readline', return_value=" 1 2 "): 21 | self.assertEqual(readstr(), "1 2") 22 | 23 | def test_readarray(self): 24 | with patch('sys.stdin.readline', return_value="1 2 3"): 25 | self.assertEqual(readarray(int), [1, 2, 3]) 26 | 27 | def test_readmatrix(self): 28 | with patch('sys.stdin.readline', return_value="1 2 3"): 29 | self.assertEqual(readmatrix(3), [[1, 2, 3], [1, 2, 3], [1, 2, 3]]) 30 | -------------------------------------------------------------------------------- /tryalgo/ABOUT.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/tryalgo/ABOUT.txt -------------------------------------------------------------------------------- /tryalgo/README.rst: -------------------------------------------------------------------------------- 1 | Tryalgo library 2 | =============== 3 | 4 | This Python library implements different classical and original algorithm and data-structures covering graph problems, string problems and computational geometry problems for example. 5 | 6 | It is implemented by Christoph Dürr and Jill-Jênn Vie. 7 | 8 | ---- 9 | 10 | It is licensed under the MIT Licence (http://opensource.org/licenses/MIT): 11 | 12 | Copyright (c) 2015-2018, Christoph Dürr and Jill-Jênn Vie 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /tryalgo/Sequence.py: -------------------------------------------------------------------------------- 1 | """Sequence.py 2 | 3 | Doubly-linked circular list for maintaining a sequence of items 4 | subject to insertions and deletions. 5 | 6 | Copyright D. Eppstein, November 2003. Under the MIT licence. 7 | 8 | Modified by 9 | Rami Benelmir, Christoph Dürr, Erisa Kohansal - 2023 10 | 11 | we adapted the data structure to store only objects and to permit to replace objects easily. 12 | we changed the name of the function "append" to "add" for simplifying our code. 13 | """ 14 | 15 | import math 16 | import sys 17 | 18 | class SequenceError(Exception): pass 19 | 20 | class Sequence: 21 | """Maintain a sequence of items subject to insertions and removals. 22 | All sequence operations take constant time except indexing, which 23 | takes time proportional to the index. 24 | """ 25 | 26 | def __init__(self, iterable=[], key=id): 27 | """We represent the sequence as a doubly-linked circular linked list, 28 | stored in two dictionaries, self._next and self._prev. We also store 29 | a pointer self._first to the first item in the sequence. If key is 30 | supplied, key(x) is used in place of x to look up item positions; 31 | e.g. using key=id allows sequences of lists or sets. 32 | """ 33 | self._key = key 34 | self._items = {} 35 | self._next = {} 36 | self._prev = {} 37 | self._first = None 38 | for x in iterable: 39 | self.add(x) 40 | 41 | def __iter__(self): 42 | """Iterate through the objects in the sequence. 43 | May give unpredictable results if sequence changes mid-iteration. 44 | """ 45 | item = self._first 46 | while self._next: 47 | yield self._items.get(item,item) 48 | item = self._next[item] 49 | if item == self._first: 50 | return 51 | 52 | def __getitem__(self,i): 53 | """Return the ith item in the sequence.""" 54 | item = self._first 55 | while i: 56 | item = self._next[item] 57 | if item == self._first: 58 | raise IndexError("Index out of range") 59 | i -= 1 60 | return self._items.get(item,item) 61 | 62 | def __len__(self): 63 | """Number of items in the sequence.""" 64 | return len(self._next) 65 | 66 | def __repr__(self): 67 | """Printable representation of the sequence.""" 68 | output = [] 69 | for x in self: 70 | output.append(repr(x)) 71 | return 'Sequence([' + ','.join(output) + '])' 72 | 73 | def key(self,x): 74 | """Apply supplied key function.""" 75 | if not self._key: 76 | return x 77 | key = self._key(x) 78 | self._items[key] = x 79 | return key 80 | 81 | def _insafter(self,x,y): 82 | """Unkeyed version of insertAfter.""" 83 | if y in self._next: 84 | raise SequenceError("Item already in sequence: "+repr(y)) 85 | self._next[y] = z = self._next[x] 86 | self._next[x] = self._prev[z] = y 87 | self._prev[y] = x 88 | 89 | def add(self,x): 90 | """Add x to the end of the sequence.""" 91 | x = self.key(x) 92 | if not self._next: # add to empty sequence 93 | self._next = {x:x} 94 | self._prev = {x:x} 95 | self._first = x 96 | else: 97 | self._insafter(self._prev[self._first],x) 98 | 99 | 100 | def remove(self,x): 101 | """Remove x from the sequence.""" 102 | x = self.key(x) 103 | prev = self._prev[x] 104 | self._next[prev] = next = self._next[x] 105 | self._prev[next] = prev 106 | if x == self._first: 107 | self._first = next 108 | del self._next[x], self._prev[x] 109 | 110 | def insertAfter(self, x, y): 111 | """Add y after x in the sequence.""" 112 | y = self.key(y) 113 | x = self.key(x) 114 | self._insafter(x,y) 115 | 116 | def insertBefore(self, x, y): 117 | """Add y before x in the sequence.""" 118 | y = self.key(y) 119 | x = self.key(x) 120 | self._insafter(self._prev[x],y) 121 | if self._first == x: 122 | self._first = y 123 | 124 | def predecessor(self,x): 125 | """Find the previous element in the sequence.""" 126 | x = self.key(x) 127 | prev = self._prev[x] 128 | return self._items.get(prev,prev) 129 | 130 | def successor(self,x): 131 | """Find the next element in the sequence.""" 132 | x = self.key(x) 133 | next = self._next[x] 134 | return self._items.get(next,next) 135 | 136 | def replace(self, old, new): 137 | """ Replace an object by another one, preserving the position in the sequence 138 | """ 139 | self.insertAfter(old, new) 140 | self.remove(old) 141 | 142 | -------------------------------------------------------------------------------- /tryalgo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilljenn/tryalgo/86445e6abb423ad3f1b0164d99f329fc3e9a3cd9/tryalgo/__init__.py -------------------------------------------------------------------------------- /tryalgo/a_star.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Shortest Path algorithm A*. 5 | 6 | jill-jênn vie et christoph dürr - 2023 7 | """ 8 | 9 | from heapq import heappop, heappush 10 | 11 | 12 | def a_star(graph, start, lower_bound): 13 | """single source shortest path by A* on an unweighted graph 14 | 15 | :param graph: iterator on adjacent vertices 16 | :param source: source vertex 17 | :param lower_bound: lb function on distance to target, 18 | must return 0 on target and only there. 19 | 20 | :returns: distance or -1 (target unreachable) 21 | :complexity: `O(|V| + |E|log|V|)` 22 | """ 23 | closedset = set() 24 | openset = set([start]) 25 | g = {start: 0 } 26 | Q = [(lower_bound(start), start)] 27 | while Q: 28 | (val, x) = heappop(Q) 29 | if lower_bound(x) == 0: 30 | return g[x] 31 | closedset.add(x) 32 | for y in graph(x): 33 | if not y in closedset and not y in openset: 34 | g[y] = g[x] + 1 35 | val = g[y] + lower_bound(y) 36 | heappush(Q, (val, y)) 37 | openset.add(y) 38 | return -1 39 | -------------------------------------------------------------------------------- /tryalgo/anagrams.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Anagrams 5 | christoph dürr - jill-jênn vie - 2013-2019 6 | """ 7 | 8 | 9 | # snip{ 10 | # pylint: disable=anomalous-backslash-in-string 11 | def anagrams(S): # S is a set of strings 12 | """group a set of words into anagrams 13 | 14 | :param S: set of strings 15 | :returns: list of lists of strings 16 | 17 | :complexity: 18 | :math:`O(n k log k)` in average, for n words of length at most k. 19 | :math:`O(n^2 k log k)` in worst case due to the usage of a dictionary. 20 | """ 21 | d = {} # maps s to list of words with signature s 22 | for word in S: # group words according to the signature 23 | s = ''.join(sorted(word)) # calculate the signature 24 | if s in d: 25 | d[s].append(word) # append a word to an existing signature 26 | else: 27 | d[s] = [word] # add a new signature and its first word 28 | # -- extract anagrams, ingoring anagram groups of size 1 29 | return [d[s] for s in d if len(d[s]) > 1] 30 | # snip} 31 | -------------------------------------------------------------------------------- /tryalgo/arithm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | arithmetic functions 5 | 6 | christoph dürr - jill-jênn vie - 2013-2019 7 | """ 8 | # pylint: disable=anomalous-backslash-in-string 9 | 10 | 11 | # snip{ pgcd 12 | def pgcd(a, b): 13 | """Greatest common divisor for a and b 14 | 15 | :param a,b: non-negative integers 16 | :complexity: O(log a + log b) 17 | """ 18 | return a if b == 0 else pgcd(b, a % b) 19 | # snip} 20 | 21 | 22 | # snip{ bezout 23 | def bezout(a, b): 24 | """Bézout coefficients for a and b 25 | 26 | :param a,b: non-negative integers 27 | :complexity: O(log a + log b) 28 | """ 29 | if b == 0: 30 | return (1, 0) 31 | u, v = bezout(b, a % b) 32 | return (v, u - (a // b) * v) 33 | 34 | 35 | def inv(a, p): 36 | """Inverse of a in :math:`{mathbb Z}_p` 37 | 38 | :param a,p: non-negative integers 39 | :complexity: O(log a + log p) 40 | """ 41 | return bezout(a, p)[0] % p 42 | # snip} 43 | 44 | 45 | # snip{ binom 46 | def binom(n, k): 47 | """Binomial coefficients for :math:`n choose k` 48 | 49 | :param n,k: non-negative integers 50 | :complexity: O(k) 51 | """ 52 | prod = 1 53 | for i in range(k): 54 | prod = (prod * (n - i)) // (i + 1) 55 | return prod 56 | # snip} 57 | 58 | 59 | # snip{ binom_modulo 60 | def binom_modulo(n, k, p): 61 | """Binomial coefficients for :math:`n choose k`, modulo p 62 | 63 | :param n,k: non-negative integers 64 | :complexity: O(k) 65 | """ 66 | prod = 1 67 | for i in range(k): 68 | prod = (prod * (n - i) * inv(i + 1, p)) % p 69 | return prod 70 | # snip} 71 | -------------------------------------------------------------------------------- /tryalgo/arithm_expr_eval.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Evaluate an arithmetic expression 5 | 6 | jill-jenn vie et christoph durr - 2014-2020 7 | """ 8 | # IPCELLS 9 | # http://www.spoj.com/problems/IPCELLS/ 10 | 11 | 12 | from tryalgo.our_std import readint, readstr 13 | 14 | 15 | # snip{ arithm_expr_eval 16 | # pylint: disable=redefined-outer-name 17 | # pylint: disable=inconsistent-return-statements 18 | def arithm_expr_eval(cell, expr): 19 | """Evaluates a given expression 20 | 21 | :param expr: expression 22 | :param cell: dictionary variable name -> expression 23 | 24 | :returns: numerical value of expression 25 | 26 | :complexity: linear 27 | """ 28 | if isinstance(expr, tuple): 29 | (left, operand, right) = expr 30 | lval = arithm_expr_eval(cell, left) 31 | rval = arithm_expr_eval(cell, right) 32 | if operand == '+': 33 | return lval + rval 34 | if operand == '-': 35 | return lval - rval 36 | if operand == '*': 37 | return lval * rval 38 | if operand == '/': 39 | return lval // rval 40 | elif isinstance(expr, int): 41 | return expr 42 | else: 43 | cell[expr] = arithm_expr_eval(cell, cell[expr]) 44 | return cell[expr] 45 | # snip} 46 | 47 | 48 | # snip{ arithm_expr_parse 49 | PRIORITY = {';': 0, '(': 1, ')': 2, '-': 3, '+': 3, '*': 4, '/': 4} 50 | 51 | 52 | # pylint: disable=redefined-outer-name 53 | def arithm_expr_parse(line_tokens): 54 | """Constructs an arithmetic expression tree 55 | 56 | :param line_tokens: list of token strings containing the expression 57 | :returns: expression tree 58 | 59 | :complexity: linear 60 | """ 61 | vals = [] 62 | ops = [] 63 | for tok in line_tokens + [';']: 64 | if tok in PRIORITY: # tok is an operator 65 | while (tok != '(' and ops and 66 | PRIORITY[ops[-1]] >= PRIORITY[tok]): 67 | right = vals.pop() 68 | left = vals.pop() 69 | vals.append((left, ops.pop(), right)) 70 | if tok == ')': 71 | ops.pop() # this is the corresponding '(' 72 | else: 73 | ops.append(tok) 74 | elif tok.isdigit(): # tok is an integer 75 | vals.append(int(tok)) 76 | else: # tok is an identifier 77 | vals.append(tok) 78 | return vals.pop() 79 | # snip} 80 | 81 | 82 | if __name__ == "__main__": 83 | # this main program is here to be tested on the online judge 84 | for test in range(readint()): 85 | cell = {} 86 | readstr() # consume the empty line 87 | for _ in range(readint()): 88 | line = readstr() 89 | cell[line[0]] = arithm_expr_parse(line[2:]) 90 | for lhs in sorted(cell.keys()): 91 | print("%s = %i" % (lhs, arithm_expr_eval(cell, lhs))) 92 | print() 93 | -------------------------------------------------------------------------------- /tryalgo/arithm_expr_target.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Create arithmetic expression approaching target value 5 | 6 | jill-jênn vie, christoph dürr et jean-christophe filliâtre - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=too-many-locals, too-many-nested-blocks, unnecessary-pass 12 | # pylint: disable=inconsistent-return-statements, too-many-branches 13 | def arithm_expr_target(x, target): 14 | """ Create arithmetic expression approaching target value 15 | 16 | :param x: allowed constants 17 | :param target: target value 18 | :returns: string in form 'expression=value' 19 | :complexity: huge 20 | """ 21 | n = len(x) 22 | expr = [{} for _ in range(1 << n)] 23 | # expr[S][val] 24 | # = string solely composed of values in set S that evaluates to val 25 | for i in range(n): 26 | expr[1 << i] = {x[i]: str(x[i])} # store singletons 27 | all_ = (1 << n) - 1 28 | for S in range(3, all_ + 1): # 3: first num that isn't a power of 2 29 | if expr[S] != {}: 30 | continue # in that case S is a power of 2 31 | for L in range(1, S): # decompose set S into non-empty sets L, R 32 | if L & S == L: 33 | R = S ^ L 34 | for vL in expr[L]: # combine expressions from L 35 | for vR in expr[R]: # with expressions from R 36 | eL = expr[L][vL] 37 | eR = expr[R][vR] 38 | expr[S][vL] = eL 39 | if vL > vR: # difference cannot become negative 40 | expr[S][vL - vR] = "(%s-%s)" % (eL, eR) 41 | if L < R: # break symmetry 42 | expr[S][vL + vR] = "(%s+%s)" % (eL, eR) 43 | expr[S][vL * vR] = "(%s*%s)" % (eL, eR) 44 | if vR != 0 and vL % vR == 0: # only integer div 45 | expr[S][vL // vR] = "(%s/%s)" % (eL, eR) 46 | # look for the closest expression from the target 47 | for dist in range(target + 1): 48 | for sign in [-1, +1]: 49 | val = target + sign * dist 50 | if val in expr[all_]: 51 | return "%s=%i" % (expr[all_][val], val) 52 | # never reaches here if x contains integers between 0 and target 53 | pass 54 | # snip} 55 | -------------------------------------------------------------------------------- /tryalgo/bellman_ford.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Single source shortest paths by Bellman-Ford 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=unused-variable 12 | def bellman_ford(graph, weight, source=0): 13 | """ Single source shortest paths by Bellman-Ford 14 | 15 | :param graph: directed graph in listlist or listdict format 16 | :param weight: can be negative. 17 | in matrix format or same listdict graph 18 | :returns: distance table, precedence table, bool 19 | :explanation: bool is True if a negative circuit is 20 | reachable from the source, circuits 21 | can have length 2. 22 | :complexity: `O(|V|*|E|)` 23 | """ 24 | n = len(graph) 25 | dist = [float('inf')] * n 26 | prec = [None] * n 27 | dist[source] = 0 28 | for nb_iterations in range(n): 29 | changed = False 30 | for node in range(n): 31 | for neighbor in graph[node]: 32 | alt = dist[node] + weight[node][neighbor] 33 | if alt < dist[neighbor]: 34 | dist[neighbor] = alt 35 | prec[neighbor] = node 36 | changed = True 37 | if not changed: # fixed point 38 | return dist, prec, False 39 | return dist, prec, True 40 | # snip} 41 | 42 | 43 | def bellman_ford2(graph, weight, source): 44 | """ Single source shortest paths by Bellman-Ford 45 | 46 | :param graph: directed graph in listlist or listdict format 47 | :param weight: can be negative. 48 | in matrix format or same listdict graph 49 | :returns: distance table, precedence table, bool 50 | :explanation: bool is true if there is a negative cycle 51 | reachable from the source. 52 | distance[v] is +inf if vertex v is not reachable 53 | from source and -inf if there are paths from the 54 | source to v of arbitrary small weight. 55 | :complexity: `O(|V|*|E|)` 56 | """ 57 | n = len(graph) 58 | dist = [float('inf')] * n 59 | prec = [None] * n 60 | dist[source] = 0 61 | 62 | def relax(): 63 | for nb_iterations in range(n-1): 64 | for node in range(n): 65 | for neighbor in graph[node]: 66 | alt = dist[node] + weight[node][neighbor] 67 | if alt < dist[neighbor]: 68 | dist[neighbor] = alt 69 | prec[neighbor] = node 70 | 71 | relax() 72 | intermediate = dist[:] # is fixpoint in absence of neg cycles 73 | relax() 74 | for node in range(n): 75 | if dist[node] < intermediate[node]: 76 | dist[node] = float('-inf') 77 | return dist, prec, min(dist) == float('-inf') 78 | -------------------------------------------------------------------------------- /tryalgo/bfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Breadth-first search (bfs) 5 | 6 | christoph dürr - jill-jênn vie - 2015-2019, 2023 7 | """ 8 | 9 | # snip{ 10 | from collections import deque 11 | 12 | 13 | def bfs(graph, start=0): 14 | """Shortest path in unweighted graph by BFS 15 | 16 | :param graph: directed graph in listlist or listdict format 17 | :param int start: source vertex 18 | :returns: distance table, precedence table 19 | :complexity: `O(|V|+|E|)` 20 | """ 21 | to_visit = deque() 22 | dist = [float('inf')] * len(graph) 23 | prec = [None] * len(graph) 24 | dist[start] = 0 25 | to_visit.appendleft(start) 26 | while to_visit: # an empty queue is considered False 27 | node = to_visit.pop() 28 | for neighbor in graph[node]: 29 | if dist[neighbor] == float('inf'): 30 | dist[neighbor] = dist[node] + 1 31 | prec[neighbor] = node 32 | to_visit.appendleft(neighbor) 33 | return dist, prec 34 | # snip} 35 | 36 | 37 | def bfs_implicit(graph, start, end): 38 | """Shortest path by BFS in an implicitly directed graph 39 | 40 | :param graph: function mapping a given vertex to 41 | an iterator over the neighboring vertices 42 | :param start: source vertex 43 | :param end: target vertex 44 | :returns: list of vertices of a shortest path or None 45 | :complexity: `O(|V|+|E|)` 46 | """ 47 | to_visit = deque() 48 | prec = {start: None} # predecessor on shortest path 49 | to_visit.appendleft(start) 50 | while to_visit: 51 | node = to_visit.pop() 52 | for neighbor in graph(node): 53 | if neighbor not in prec: # not yet discovered 54 | prec[neighbor] = node 55 | to_visit.appendleft(neighbor) 56 | if end in prec: 57 | # solution found 58 | L = [end] # backtrack the shortest path 59 | while prec[L[-1]] is not None: 60 | L.append(prec[L[-1]]) 61 | return L[::-1] # return in start to end order 62 | return None 63 | -------------------------------------------------------------------------------- /tryalgo/binary_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Binary search 5 | 6 | jill-jênn vie, christoph dürr et louis abraham - 2014-2020 7 | """ 8 | 9 | # pylint: disable=redefined-outer-name 10 | 11 | from tryalgo.our_std import readint, readarray 12 | 13 | __all__ = ["discrete_binary_search", "continuous_binary_search", 14 | "optimized_binary_search_lower", "optimized_binary_search", 15 | "ternary_search"] 16 | 17 | # Fill the Cisterns 18 | # http://www.spoj.com/problems/CISTFILL/ 19 | # [!] python3 is too slow for this problem 20 | 21 | # snip{ discrete_binary_search 22 | 23 | 24 | def discrete_binary_search(tab, lo, hi): 25 | """Binary search in a table 26 | 27 | :param tab: boolean monotone table with tab[hi] = True 28 | :param int lo: 29 | :param int hi: with hi >= lo 30 | :returns: first index i in [lo,hi] such that tab[i] 31 | :complexity: `O(log(hi-lo))` 32 | """ 33 | while lo < hi: 34 | mid = lo + (hi - lo) // 2 35 | if tab[mid]: 36 | hi = mid 37 | else: 38 | lo = mid + 1 39 | return lo 40 | # snip} 41 | 42 | 43 | # snip{ continuous_binary_search 44 | def continuous_binary_search(f, lo, hi, gap=1e-4): 45 | """Binary search for a function 46 | 47 | :param f: boolean monotone function with f(hi) = True 48 | :param int lo: 49 | :param int hi: with hi >= lo 50 | :param float gap: 51 | :returns: first value x in [lo,hi] such that f(x), 52 | x is computed up to some precision 53 | :complexity: `O(log((hi-lo)/gap))` 54 | """ 55 | while hi - lo > gap: 56 | mid = (lo + hi) / 2.0 57 | if f(mid): 58 | hi = mid 59 | else: 60 | lo = mid 61 | return lo 62 | # snip} 63 | 64 | 65 | def optimized_binary_search_lower(tab, logsize): 66 | """Binary search in a table using bit operations 67 | 68 | :param tab: boolean monotone table 69 | of size :math:`2^\\textrm{logsize}` 70 | with tab[0] = False 71 | :param int logsize: 72 | :returns: last i such that not tab[i] 73 | :complexity: O(logsize) 74 | """ 75 | lo = 0 76 | intervalsize = (1 << logsize) >> 1 77 | while intervalsize > 0: 78 | if not tab[lo | intervalsize]: 79 | lo |= intervalsize 80 | intervalsize >>= 1 81 | return lo 82 | 83 | 84 | # snip{ optimized_binary_search 85 | def optimized_binary_search(tab, logsize): 86 | """Binary search in a table using bit operations 87 | 88 | :param tab: boolean monotone table 89 | of size :math:`2^\\textrm{logsize}` 90 | with tab[hi] = True 91 | :param int logsize: 92 | :returns: first i such that tab[i] 93 | :complexity: O(logsize) 94 | """ 95 | hi = (1 << logsize) - 1 96 | intervalsize = (1 << logsize) >> 1 97 | while intervalsize > 0: 98 | if tab[hi ^ intervalsize]: 99 | hi ^= intervalsize 100 | intervalsize >>= 1 101 | return hi 102 | # snip} 103 | 104 | 105 | def ternary_search(f, lo, hi, gap=1e-10): 106 | """Ternary maximum search for a bitonic function 107 | 108 | :param f: boolean bitonic function (increasing then decreasing, not necessarily strictly) 109 | :param int lo: 110 | :param int hi: with hi >= lo 111 | :param float gap: 112 | :returns: value x in [lo,hi] maximizing f(x), 113 | x is computed up to some precision 114 | :complexity: `O(log((hi-lo)/gap))` 115 | """ 116 | while hi - lo > gap: 117 | step = (hi - lo) / 3. 118 | if f(lo + step) < f(lo + 2 * step): 119 | lo += step 120 | else: 121 | hi -= step 122 | return lo 123 | 124 | 125 | # pylint: disable=cell-var-from-loop 126 | if __name__ == "__main__": 127 | def volume(level): 128 | """ 129 | Computes the volume of a set of cuboids. 130 | """ 131 | vol = 0 132 | for base, height, ground in rect: 133 | if base < level: 134 | vol += ground * min(level - base, height) 135 | return vol 136 | 137 | for test in range(readint()): 138 | n = readint() 139 | rect = [] 140 | for _ in range(n): 141 | x, y, w, h = readarray(int) 142 | rect.append((x, y, w * h)) 143 | V = readint() 144 | hi = 1e6 + 40000 145 | if volume(hi) < V: 146 | print("OVERFLOW") 147 | else: 148 | print("%.02f" % 149 | continuous_binary_search(lambda x: volume(x) >= V, 150 | 0, hi)) 151 | -------------------------------------------------------------------------------- /tryalgo/bipartite_matching.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Bipartie maximum matching 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | __all__ = ["max_bipartite_matching"] 10 | 11 | 12 | 13 | # snip{ 14 | def augment(u, bigraph, visit, timestamp, match): 15 | """ find augmenting path starting from u, by recursive DFS """ 16 | for v in bigraph[u]: 17 | if visit[v] < timestamp: 18 | visit[v] = timestamp 19 | if match[v] is None or augment(match[v], bigraph, 20 | visit, timestamp, match): 21 | match[v] = u # found an augmenting path 22 | return True 23 | return False 24 | 25 | 26 | def max_bipartite_matching(bigraph): 27 | """Bipartie maximum matching 28 | 29 | :param bigraph: adjacency list, index = vertex in U, 30 | value = neighbor list in V 31 | :comment: U and V can have different cardinalities 32 | :returns: matching list, match[v] == u iff (u, v) in matching 33 | :complexity: `O(|V|*|E|)` 34 | """ 35 | nU = len(bigraph) # nU = cardinality of U, nV = card. of V 36 | nV = max(max(adjlist, default=-1) for adjlist in bigraph) + 1 37 | match = [None] * nV 38 | visit = [-1] * nV # timestamp of last visit 39 | for u in range(nU): 40 | augment(u, bigraph, visit, u, match) 41 | return match 42 | # snip} 43 | -------------------------------------------------------------------------------- /tryalgo/bipartite_vertex_cover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Bipartie vertex cover 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | from tryalgo.bipartite_matching import max_bipartite_matching 11 | 12 | 13 | def _alternate(u, bigraph, visitU, visitV, matchV): 14 | """extend alternating tree from free vertex u. 15 | visitU, visitV marks all vertices covered by the tree. 16 | """ 17 | visitU[u] = True 18 | for v in bigraph[u]: 19 | if not visitV[v]: 20 | visitV[v] = True 21 | assert matchV[v] is not None # otherwise match is not maximum 22 | _alternate(matchV[v], bigraph, visitU, visitV, matchV) 23 | 24 | 25 | def bipartite_vertex_cover(bigraph): 26 | """Bipartite minimum vertex cover by Koenig's theorem 27 | 28 | :param bigraph: adjacency list, index = vertex in U, 29 | value = neighbor list in V 30 | :assumption: U = V = {0, 1, 2, ..., n - 1} for n = len(bigraph) 31 | :returns: boolean table for U, boolean table for V 32 | :comment: selected vertices form a minimum vertex cover, 33 | i.e. every edge is adjacent to at least one selected vertex 34 | and number of selected vertices is minimum 35 | :complexity: `O(|V|*|E|)` 36 | """ 37 | V = range(len(bigraph)) 38 | matchV = max_bipartite_matching(bigraph) 39 | matchU = [None for u in V] 40 | for v in V: # -- build the mapping from U to V 41 | if matchV[v] is not None: 42 | matchU[matchV[v]] = v 43 | visitU = [False for u in V] # -- build max alternating forest 44 | visitV = [False for v in V] 45 | for u in V: 46 | if matchU[u] is None: # -- starting with free vertices in U 47 | _alternate(u, bigraph, visitU, visitV, matchV) 48 | inverse = [not b for b in visitU] 49 | return (inverse, visitV) 50 | -------------------------------------------------------------------------------- /tryalgo/closest_points.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Closest pair of points 5 | trouver la paire de points la plus proche 6 | 7 | jill-jênn vie, christoph dürr et louis abraham - 2014-2019 8 | """ 9 | # pylint: disable=missing-docstring, redefined-outer-name, redefined-outer-name 10 | # pylint: disable=no-name-in-module, ungrouped-imports 11 | 12 | 13 | from random import randint 14 | 15 | 16 | # snip{ 17 | from math import hypot # hypot(dx, dy) = sqrt(dx * dx + dy * dy) 18 | from math import floor 19 | from random import shuffle 20 | 21 | # snip} 22 | __all__ = ["closest_points"] 23 | # snip{ 24 | 25 | 26 | def dist(p, q): 27 | return hypot(p[0] - q[0], p[1] - q[1]) # Euclidean dist. 28 | 29 | 30 | def cell(point, size): 31 | """ returns the grid cell coordinates containing the given point. 32 | 33 | :param point: couple of coordinates 34 | :param size: floating point number, side length of a grid cell 35 | """ 36 | x, y = point 37 | return (floor(x / size), floor(y / size)) 38 | 39 | 40 | def improve(S, d): 41 | G = {} # maps grid cell to its point 42 | for p in S: # for every point 43 | a, b = cell(p, d / 2) # determine its grid cell 44 | for a1 in range(a - 2, a + 3): 45 | for b1 in range(b - 2, b + 3): 46 | if (a1, b1) in G: # compare with points 47 | q = G[a1, b1] # in surrounding cells 48 | pq = dist(p, q) 49 | if pq < d: # improvement found 50 | return pq, p, q 51 | G[a, b] = p 52 | return None 53 | 54 | 55 | def closest_points(S): 56 | """Closest pair of points 57 | 58 | :param S: list of points 59 | :requires: size of S at least 2 60 | :modifies: changes the order in S 61 | :returns: pair of points p,q from S with minimum Euclidean distance 62 | :complexity: expected linear time 63 | """ 64 | shuffle(S) 65 | assert len(S) >= 2 66 | p = S[0] # start with distance between 67 | q = S[1] # first two points 68 | d = dist(p, q) 69 | while d > 0: # distance 0 cannot be improved 70 | r = improve(S, d) 71 | if r: # distance improved 72 | d, p, q = r 73 | else: # r is None: could not improve 74 | break 75 | return p, q 76 | # snip} 77 | 78 | 79 | if __name__ == "__main__": 80 | # generates the figure for the book 81 | 82 | def tikz_points(S): 83 | for p in S: 84 | print("\\filldraw[black] (%f, %f) circle (1pt);" % p) 85 | 86 | def tikz_polygone(S): 87 | for i, _ in enumerate(S): 88 | print('\\draw (%f, %f) -- (%f, %f);' % (S[i - 1] + S[i])) 89 | 90 | S = [(randint(0, 400) / 100, randint(0, 400) / 100) for _ in range(32)] 91 | tikz_points(S) 92 | tikz_polygone(closest_points(S)) 93 | -------------------------------------------------------------------------------- /tryalgo/closest_values.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Closest values 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=unused-variable 12 | def closest_values(L): 13 | """Closest values 14 | 15 | :param L: list of values 16 | :returns: two values from L with minimal distance 17 | :modifies: the order of L 18 | :complexity: O(n log n), for n=len(L) 19 | """ 20 | assert len(L) >= 2 21 | L.sort() 22 | valmin, argmin = min((L[i] - L[i - 1], i) for i in range(1, len(L))) 23 | return L[argmin - 1], L[argmin] 24 | # snip} 25 | -------------------------------------------------------------------------------- /tryalgo/convex_hull.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Convex hull by Andrew 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | # pylint: disable=redefined-outer-name 10 | 11 | from random import randint 12 | 13 | 14 | __all__ = ["andrew"] 15 | 16 | 17 | # snip{ left-turn 18 | def left_turn(a, b, c): 19 | """function left-turn""" 20 | return ((a[0] - c[0]) * (b[1] - c[1]) - 21 | (a[1] - c[1]) * (b[0] - c[0]) > 0) 22 | # snip} 23 | 24 | 25 | # snip{ 26 | def andrew(S): 27 | """Convex hull by Andrew 28 | 29 | :param S: list of points as coordinate pairs 30 | :requires: S has at least 2 points 31 | :returns: list of points of the convex hull 32 | :complexity: `O(n log n)` 33 | """ 34 | S.sort() 35 | top = [] 36 | bot = [] 37 | for p in S: 38 | while len(top) >= 2 and not left_turn(p, top[-1], top[-2]): 39 | top.pop() 40 | top.append(p) 41 | while len(bot) >= 2 and not left_turn(bot[-2], bot[-1], p): 42 | bot.pop() 43 | bot.append(p) 44 | return bot[:-1] + top[:0:-1] 45 | # snip} 46 | 47 | 48 | # pylint: disable=missing-docstring 49 | if __name__ == "__main__": 50 | 51 | def gnuplot(L): 52 | for x, y in L: 53 | print(x, y) 54 | 55 | def tikz_points(S): 56 | for p in S: 57 | print('\\filldraw[black] (%f, %f) circle (1pt);' % p) 58 | 59 | def tikz_polygone(S): 60 | for i, _ in enumerate(S): 61 | print('\\draw[blue] (%f, %f) -- (%f, %f);' % (S[i - 1] + S[i])) 62 | 63 | S = [(randint(0, 25)/10., randint(0, 25)/10.) for _ in range(32)] 64 | tikz_points(S) 65 | tikz_polygone(andrew(S)) 66 | -------------------------------------------------------------------------------- /tryalgo/dancing_links.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Exact set cover by the dancing links algorithm 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | __all__ = ["dancing_links"] 10 | 11 | 12 | # snip{ liens-dansants-cell 13 | # pylint: disable=missing-docstring 14 | class Cell: 15 | def __init__(self, horiz, verti, S, C): 16 | self.S = S 17 | self.C = C 18 | if horiz: 19 | self.L = horiz.L 20 | self.R = horiz 21 | self.L.R = self 22 | self.R.L = self 23 | else: 24 | self.L = self 25 | self.R = self 26 | if verti: 27 | self.U = verti.U 28 | self.D = verti 29 | self.U.D = self 30 | self.D.U = self 31 | else: 32 | self.U = self 33 | self.D = self 34 | 35 | def hide_verti(self): 36 | self.U.D = self.D 37 | self.D.U = self.U 38 | 39 | def unhide_verti(self): 40 | self.D.U = self 41 | self.U.D = self 42 | 43 | def hide_horiz(self): 44 | self.L.R = self.R 45 | self.R.L = self.L 46 | 47 | def unhide_horiz(self): 48 | self.R.L = self 49 | self.L.R = self 50 | # snip} 51 | 52 | 53 | # snip{ liens-dansants-cover 54 | def cover(c): # c = heading cell of the column to cover 55 | assert c.C is None # must be a heading cell 56 | c.hide_horiz() 57 | i = c.D 58 | while i != c: 59 | j = i.R 60 | while j != i: 61 | j.hide_verti() 62 | j.C.S -= 1 # one fewer entry in this column 63 | j = j.R 64 | i = i.D 65 | 66 | 67 | def uncover(c): 68 | assert c.C is None 69 | i = c.U 70 | while i != c: 71 | j = i.L 72 | while j != i: 73 | j.C.S += 1 # one more entry in this column 74 | j.unhide_verti() 75 | j = j.L 76 | i = i.U 77 | c.unhide_horiz() 78 | # snip} 79 | 80 | 81 | # snip{ liens-dansants-exploration 82 | def dancing_links(size_universe, sets): 83 | """Exact set cover by the dancing links algorithm 84 | 85 | :param size_universe: universe = {0, 1, ..., size_universe - 1} 86 | :param sets: list of sets 87 | :returns: list of set indices partitioning the universe, or None 88 | :complexity: huge 89 | """ 90 | header = Cell(None, None, 0, None) # building the cell structure 91 | col = [] 92 | for j in range(size_universe): 93 | col.append(Cell(header, None, 0, None)) 94 | for i, _ in enumerate(sets): 95 | row = None 96 | for j in sets[i]: 97 | col[j].S += 1 # one more entry in this column 98 | row = Cell(row, col[j], i, col[j]) 99 | sol = [] 100 | if solve(header, sol): 101 | return sol 102 | return None 103 | 104 | 105 | # pylint: disable=missing-docstring 106 | def solve(header, sol): 107 | if header.R == header: # the instance is empty => solution found 108 | return True 109 | c = None # find the least covered column 110 | j = header.R 111 | while j != header: 112 | if c is None or j.S < c.S: 113 | c = j 114 | j = j.R 115 | cover(c) # cover this column 116 | r = c.D # try every row 117 | while r != c: 118 | sol.append(r.S) 119 | j = r.R # cover elements in set r 120 | while j != r: 121 | cover(j.C) 122 | j = j.R 123 | if solve(header, sol): 124 | return True 125 | j = r.L # uncover 126 | while j != r: 127 | uncover(j.C) 128 | j = j.L 129 | sol.pop() 130 | r = r.D 131 | uncover(c) 132 | return False 133 | # snip} 134 | -------------------------------------------------------------------------------- /tryalgo/dijkstra.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Shortest paths by Dijkstra 5 | 6 | jill-jênn vie et christoph dürr - 2015-2018 7 | """ 8 | # pylint: disable=wrong-import-position 9 | 10 | from heapq import heappop, heappush 11 | from tryalgo.our_heap import OurHeap 12 | 13 | # snip{ 14 | 15 | 16 | def dijkstra(graph, weight, source=0, target=None): 17 | """single source shortest paths by Dijkstra 18 | 19 | :param graph: directed graph in listlist or listdict format 20 | :param weight: in matrix format or same listdict graph 21 | :assumes: weights are non-negative 22 | :param source: source vertex 23 | :type source: int 24 | :param target: if given, stops once distance to target found 25 | :type target: int 26 | 27 | :returns: distance table, precedence table 28 | :complexity: `O(|V| + |E|log|V|)` 29 | """ 30 | n = len(graph) 31 | assert all(weight[u][v] >= 0 for u in range(n) for v in graph[u]) 32 | prec = [None] * n 33 | black = [False] * n 34 | dist = [float('inf')] * n 35 | dist[source] = 0 36 | heap = [(0, source)] 37 | while heap: 38 | dist_node, node = heappop(heap) # Closest node from source 39 | if not black[node]: 40 | black[node] = True 41 | if node == target: 42 | break 43 | for neighbor in graph[node]: 44 | dist_neighbor = dist_node + weight[node][neighbor] 45 | if dist_neighbor < dist[neighbor]: 46 | dist[neighbor] = dist_neighbor 47 | prec[neighbor] = node 48 | heappush(heap, (dist_neighbor, neighbor)) 49 | return dist, prec 50 | # snip} 51 | 52 | 53 | # snip{ dijkstra_update_heap 54 | 55 | 56 | # snip} 57 | 58 | # snip{ dijkstra_update_heap 59 | def dijkstra_update_heap(graph, weight, source=0, target=None): 60 | """single source shortest paths by Dijkstra 61 | with a heap implementing item updates 62 | 63 | :param graph: adjacency list or adjacency dictionary of a directed graph 64 | :param weight: matrix or adjacency dictionary 65 | :assumes: weights are non-negatif and weights are infinite for non edges 66 | :param source: source vertex 67 | :type source: int 68 | :param target: if given, stops once distance to target found 69 | :type target: int 70 | :returns: distance table, precedence table 71 | :complexity: `O(|V| + |E|log|V|)` 72 | """ 73 | n = len(graph) 74 | assert all(weight[u][v] >= 0 for u in range(n) for v in graph[u]) 75 | prec = [None] * n 76 | dist = [float('inf')] * n 77 | dist[source] = 0 78 | heap = OurHeap([(dist[node], node) for node in range(n)]) 79 | while heap: 80 | dist_node, node = heap.pop() # Closest node from source 81 | if node == target: 82 | break 83 | for neighbor in graph[node]: 84 | old = dist[neighbor] 85 | new = dist_node + weight[node][neighbor] 86 | if new < old: 87 | dist[neighbor] = new 88 | prec[neighbor] = node 89 | heap.update((old, neighbor), (new, neighbor)) 90 | return dist, prec 91 | # snip} 92 | -------------------------------------------------------------------------------- /tryalgo/dilworth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Decompose DAG into a minimum number of chains 5 | 6 | jill-jenn vie et christoph durr - 2015-2018 7 | """ 8 | 9 | from tryalgo.bipartite_matching import max_bipartite_matching 10 | 11 | 12 | # snip{ 13 | def dilworth(graph): 14 | """Decompose a DAG into a minimum number of chains by Dilworth 15 | 16 | :param graph: directed graph in listlist or listdict format 17 | :assumes: graph is acyclic 18 | :returns: table giving for each vertex the number of its chains 19 | :complexity: same as matching 20 | """ 21 | n = len(graph) 22 | match = max_bipartite_matching(graph) # maximum matching 23 | part = [None] * n # partition into chains 24 | nb_chains = 0 25 | for v in range(n - 1, -1, -1): # in inverse topological order 26 | if part[v] is None: # start of chain 27 | u = v 28 | while u is not None: # follow the chain 29 | part[u] = nb_chains # mark 30 | u = match[u] 31 | nb_chains += 1 32 | return part 33 | # snip} 34 | -------------------------------------------------------------------------------- /tryalgo/dinic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Maximum flow by Dinic 5 | 6 | jill-jênn vie et christoph dürr - 2015-2018 7 | """ 8 | 9 | from collections import deque 10 | from sys import setrecursionlimit 11 | from tryalgo.graph import add_reverse_arcs 12 | 13 | 14 | setrecursionlimit(5010) # necessary for big graphs 15 | 16 | 17 | # snip{ 18 | def dinic(graph, capacity, source, target): 19 | """Maximum flow by Dinic 20 | 21 | :param graph: directed graph in listlist or listdict format 22 | :param capacity: in matrix format or same listdict graph 23 | :param int source: vertex 24 | :param int target: vertex 25 | :returns: skew symmetric flow matrix, flow value 26 | :complexity: :math:`O(|V|^2 |E|)` 27 | """ 28 | assert source != target 29 | add_reverse_arcs(graph, capacity) 30 | Q = deque() 31 | total = 0 32 | n = len(graph) 33 | flow = [[0] * n for u in range(n)] # flow initially empty 34 | while True: # repeat while we can increase 35 | Q.appendleft(source) 36 | level = [None] * n # build levels, None = inaccessible 37 | level[source] = 0 # by BFS 38 | while Q: 39 | u = Q.pop() 40 | for v in graph[u]: 41 | if level[v] is None and capacity[u][v] > flow[u][v]: 42 | level[v] = level[u] + 1 43 | Q.appendleft(v) 44 | 45 | if level[target] is None: # stop if sink is not reachable 46 | return flow, total 47 | up_bound = sum(capacity[source][v] for v in graph[source]) - total 48 | total += _dinic_step(graph, capacity, level, flow, source, target, 49 | up_bound) 50 | 51 | 52 | # pylint: disable=too-many-arguments 53 | def _dinic_step(graph, capacity, level, flow, u, target, limit): 54 | """ tenter de pousser le plus de flot de u à target, sans dépasser limit 55 | """ 56 | if limit <= 0: 57 | return 0 58 | if u == target: 59 | return limit 60 | val = 0 61 | for v in graph[u]: 62 | residual = capacity[u][v] - flow[u][v] 63 | if level[v] == level[u] + 1 and residual > 0: 64 | z = min(limit, residual) 65 | aug = _dinic_step(graph, capacity, level, flow, v, target, z) 66 | flow[u][v] += aug 67 | flow[v][u] -= aug 68 | val += aug 69 | limit -= aug 70 | if val == 0: 71 | level[u] = None # remove unreachable node 72 | return val 73 | # snip} 74 | -------------------------------------------------------------------------------- /tryalgo/dist_grid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Distances in a grid 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | from collections import deque 10 | 11 | 12 | # snip{ 13 | def dist_grid(grid, source, target=None): 14 | """Distances in a grid by BFS 15 | 16 | :param grid: matrix with 4-neighborhood 17 | :param (int,int) source: pair of row, column ind_ices 18 | :param (int,int) target: exploration stops if target is reached 19 | :complexity: linear in grid size 20 | """ 21 | rows = len(grid) 22 | cols = len(grid[0]) 23 | i, j = source 24 | grid[i][j] = 's' 25 | Q = deque() 26 | Q.append(source) 27 | while Q: 28 | i1, j1 = Q.popleft() 29 | for di, dj, symbol in [(0, +1, '>'), 30 | (0, -1, '<'), 31 | (+1, 0, 'v'), 32 | (-1, 0, '^')]: # explore all directions 33 | i2 = i1 + di 34 | j2 = j1 + dj 35 | if not (0 <= i2 < rows and 0 <= j2 < cols): 36 | continue # reached the bounds of the grid 37 | if grid[i2][j2] != ' ': # inaccessible or already visited 38 | continue 39 | grid[i2][j2] = symbol # mark visit 40 | if (i2, j2) == target: 41 | grid[i2][j2] = 't' # goal is reached 42 | return 43 | Q.append((i2, j2)) 44 | # snip} 45 | -------------------------------------------------------------------------------- /tryalgo/dyn_prog_tricks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Dynamic Programming speedup tricks 5 | 6 | christoph dürr - jill-jênn vie - 2022 7 | """ 8 | 9 | import sys 10 | 11 | 12 | def dyn_prog_Monge(W): 13 | """ Solves the following dynamic program for 0 <= i < j < n 14 | 15 | C[i,i] = 0 16 | C[i,j] = W[i,j] + min over i < k <= j of (C[i,k-1] + C[k,j]) 17 | K[i,j] = minimizer of above 18 | 19 | :param W: matrix of dimension n times n 20 | :assumes: W satisfies the Monge property (a.k.a. quadrangle inequality) and monotonicity in the lattice of intervals 21 | :returns: C[0,n-1] and the matrix K with the minimizers 22 | :complexity: O(n^2) 23 | """ 24 | n = len(W) 25 | C = [[W[i][i] for j in range(n)] for i in range(n)] # initially C[i,i]=W[i][i] 26 | K = [[j for j in range(n)] for i in range(n)] # initially K[i,i]=i 27 | 28 | # recursion 29 | for j_i in range(1, n): # difference between j and i 30 | for i in range(n - j_i): 31 | j = i + j_i 32 | argmin = None 33 | valmin = float('+inf') 34 | for k in range(max(i + 1, K[i][j - 1]), K[i + 1][j] + 1): 35 | alt = C[i][k - 1] + C[k][j] 36 | if alt < valmin: 37 | valmin = alt 38 | argmin = k 39 | C[i][j] = W[i][j] + valmin 40 | K[i][j] = argmin 41 | return C[0][n-1], K 42 | 43 | 44 | def _decode(i, j, R, level, current): 45 | """is used by decode_root_matrix_to_level for recursive decoding.""" 46 | if i >= j: 47 | return # nothing to do 48 | root = R[i][j] 49 | level[root] = current 50 | _decode(i, root-1, R, level, current + 1) 51 | _decode(root, j, R, level, current + 1) 52 | 53 | 54 | def decode_root_matrix_to_level(R): 55 | """Decodes a binary search tree encoded in the root matrix R into a level array 56 | :returns: the level array 57 | :complexity: linear 58 | """ 59 | n = len(R) 60 | level = [0] * n 61 | _decode(0, n - 1, R, level, 0) 62 | return level[1:] 63 | 64 | 65 | def opt_bin_search_tree2(success, failure): 66 | """ Optimal binary search tree on elements from 1 to n 67 | 68 | :param success: n+1 dimensional array with frequency of every element i. 69 | success[0] is ignored 70 | :param failure: n+1 dimensional array with frequency between the elements, 71 | failure[i] is frequency of a query strictly between element i and i+1. 72 | These arrays do not have to be normalized. 73 | :returns: The value of an optimal search tree and the matrix with the roots for each 74 | subproblem, encoding the actual tree. 75 | :complexity: O(n^2) 76 | """ 77 | n = len(failure) 78 | N = range(n) 79 | W = [[failure[i] for j in N] for i in N] 80 | for i in N: 81 | for j in range(i+1, n): 82 | W[i][j] = W[i][j - 1] + failure[j] + success[j] 83 | return dyn_prog_Monge(W) 84 | 85 | 86 | def opt_bin_search_tree1(freq): 87 | """ Optimal binary search tree on elements from 0 to n-1 88 | 89 | :param freq: n dimensional array with frequency of every element i. 90 | :returns: The value of an optimal search tree and the matrix with the roots for each 91 | subproblem, encoding the actual tree. 92 | :complexity: O(n^2) 93 | """ 94 | n = len(freq) 95 | return opt_bin_search_tree2([0] + freq, [0] * (n + 1)) 96 | 97 | if __name__ == "__main__": 98 | 99 | def readint(): return int(sys.stdin.readline()) 100 | def readstr(): return sys.stdin.readline().strip() 101 | def readfloats(): return list(map(float, readstr().split())) 102 | 103 | n = readint() 104 | beta = [0] + readfloats() 105 | alpha = readfloats() 106 | print(opt_bin_search_tree2(beta, alpha)[0]) 107 | -------------------------------------------------------------------------------- /tryalgo/edmonds_karp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Maximum flow by Edmonds-Karp 5 | 6 | jill-jênn vie et christoph dürr - 2015-2019 7 | """ 8 | 9 | from collections import deque 10 | from tryalgo.graph import add_reverse_arcs 11 | 12 | 13 | # snip{ 14 | def _augment(graph, capacity, flow, source, target): 15 | """find a shortest augmenting path 16 | """ 17 | n = len(graph) 18 | A = [0] * n # A[v] = min residual cap. on path source->v 19 | augm_path = [None] * n # None = node was not visited yet 20 | Q = deque() # BFS 21 | Q.append(source) 22 | augm_path[source] = source 23 | A[source] = float('inf') 24 | while Q: 25 | u = Q.popleft() 26 | for v in graph[u]: 27 | cuv = capacity[u][v] 28 | residual = cuv - flow[u][v] 29 | if residual > 0 and augm_path[v] is None: 30 | augm_path[v] = u # store predecessor 31 | A[v] = min(A[u], residual) 32 | if v == target: 33 | break 34 | Q.append(v) 35 | return (augm_path, A[target]) # augmenting path, min residual cap. 36 | 37 | 38 | def edmonds_karp(graph, capacity, source, target): 39 | """Maximum flow by Edmonds-Karp 40 | 41 | :param graph: directed graph in listlist or listdict format 42 | :param capacity: in matrix format or same listdict graph 43 | :param int source: vertex 44 | :param int target: vertex 45 | :returns: flow matrix, flow value 46 | :complexity: :math:`O(|V|*|E|^2)` 47 | """ 48 | add_reverse_arcs(graph, capacity) 49 | V = range(len(graph)) 50 | flow = [[0 for v in V] for u in V] 51 | while True: 52 | augm_path, delta = _augment(graph, capacity, flow, source, target) 53 | if delta == 0: 54 | break 55 | v = target # go back to source 56 | while v != source: 57 | u = augm_path[v] # augment flow 58 | flow[u][v] += delta 59 | flow[v][u] -= delta 60 | v = u 61 | return (flow, sum(flow[source])) # flow network, amount of flow 62 | # snip} 63 | -------------------------------------------------------------------------------- /tryalgo/fast_exponentiation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Fast Exponentiation 5 | 6 | jill-jenn vie et christoph durr and louis abraham - 2014-2018 7 | """ 8 | 9 | 10 | def fast_exponentiation2(a, b, q): 11 | """Compute (a pow b) % q 12 | 13 | :param int a b: non negative 14 | :param int q: positive 15 | :complexity: O(log b) 16 | """ 17 | assert a >= 0 and b >= 0 and q >= 1 18 | p = 0 # only for documentation 19 | p2 = 1 # 2 ** p 20 | ap2 = a % q # a ** (2 ** p) 21 | result = 1 22 | while b > 0: 23 | if p2 & b > 0: # b's binary decomposition contains 2 ** p 24 | b -= p2 25 | result = (result * ap2) % q 26 | p += 1 27 | p2 *= 2 28 | ap2 = (ap2 * ap2) % q 29 | return result 30 | 31 | 32 | # snip{ 33 | def fast_exponentiation(a, b, q): 34 | """Compute (a pow b) % q, alternative shorter implementation 35 | 36 | :param int a b: non negative 37 | :param int q: positive 38 | :complexity: O(log b) 39 | """ 40 | assert a >= 0 and b >= 0 and q >= 1 41 | result = 1 42 | while b: 43 | if b % 2 == 1: 44 | result = (result * a) % q 45 | a = (a * a) % q 46 | b >>= 1 47 | return result 48 | # snip} 49 | -------------------------------------------------------------------------------- /tryalgo/fenwick.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Fenwick tree 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | class Fenwick: 12 | """maintains a tree to allow quick updates and queries 13 | """ 14 | def __init__(self, t): 15 | """stores a table t and allows updates and queries 16 | of prefix sums in logarithmic time. 17 | 18 | :param array t: with numerical values 19 | """ 20 | self.s = [0] * (len(t) + 1) # create internal storage 21 | for a, v in enumerate(t): 22 | self.add(a, v) # initialize 23 | 24 | # pylint: disable=redefined-builtin 25 | def prefixSum(self, a): 26 | """ 27 | :param int a: index in t, negative a will return 0 28 | :returns: t[0] + ... + t[a] 29 | """ 30 | i = a + 1 # internal index starts at 1 31 | total = 0 32 | while i > 0: # loops over neighbors 33 | total += self.s[i] # cumulative sum 34 | i -= (i & -i) # left neighbor 35 | return total 36 | 37 | def intervalSum(self, a, b): 38 | """ 39 | :param int a b: with 0 <= a <= b 40 | :returns: t[a] + ... + t[b] 41 | """ 42 | return self.prefixSum(b) - self.prefixSum(a-1) 43 | 44 | def add(self, a, val): 45 | """ 46 | :param int a: index in t 47 | :modifies: adds val to t[a] 48 | """ 49 | i = a + 1 # internal index starts at 1 50 | while i < len(self.s): # loops over parents 51 | self.s[i] += val # update node 52 | i += (i & -i) # parent 53 | 54 | # variante: 55 | # pylint: disable=bad-whitespace 56 | def intervalAdd(self, a, b, val): 57 | """Variant, adds val to t[a], to t[a + 1] ... and to t[b] 58 | 59 | :param int a b: with 0 <= a <= b < len(t) 60 | """ 61 | self.add(a, +val) 62 | self.add(b + 1, -val) 63 | 64 | def get(self, a): 65 | """Variant, reads t[a] 66 | 67 | :param int i: negative a will return 0 68 | """ 69 | return self.prefixSum(a) 70 | # snip} 71 | 72 | class FenwickMin: 73 | """maintains a tree to allow quick updates and queries 74 | of a virtual table t 75 | """ 76 | def __init__(self, size): 77 | """stores a table t and allows updates and queries 78 | of prefix sums in logarithmic time. 79 | 80 | :param size: length of the table 81 | """ 82 | self.s = [float('+inf')] * (size + 1) # create internal storage 83 | 84 | def prefixMin(self, a): 85 | """ 86 | :param int a: index in t, negative a will return infinity 87 | :returns: min(t[0], ... ,t[a]) 88 | """ 89 | i = a + 1 # internal index starts at 1 90 | retval = float('+inf') 91 | while i > 0: # loops over neighbors 92 | retval = min(retval, self.s[i]) 93 | i -= (i & -i) # left neighbor 94 | return retval 95 | 96 | def update(self, a, val): 97 | """ 98 | :param int a: index in t 99 | :param val: a value 100 | :modifies: sets t[a] to the minimum of t[a] and val 101 | """ 102 | i = a + 1 # internal index starts at 1 103 | while i < len(self.s): # loops over parents 104 | self.s[i] = min(self.s[i], val) # update node 105 | i += (i & -i) # parent 106 | -------------------------------------------------------------------------------- /tryalgo/fft.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Fast Fourier Transformation 5 | 6 | christoph dürr - jill-jênn vie - 2022 7 | """ 8 | # http://www.cs.toronto.edu/~denisp/csc373/docs/tutorial3-adv-writeup.pdf 9 | # https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm 10 | 11 | 12 | import math # for pi 13 | import cmath # for exp 14 | 15 | 16 | def pad(x): 17 | """ pad array x with zeros to make its length a power of two 18 | :complexity: linear 19 | """ 20 | n = 1 21 | while n < len(x): 22 | n <<= 1 23 | x += [0] * (n - len(x)) 24 | 25 | 26 | def fft(x): 27 | """ Fast Fourier Transformation 28 | :input x: list of coefficients. Length n has to be a power of 2. 29 | :returns: list of sample values. 30 | :complexity: O(n log n). 31 | """ 32 | n2 = len(x) // 2 33 | if n2 == 0: 34 | return x 35 | assert(2 * n2 == len(x)) # need to split evenly 36 | even = fft(x[0::2]) 37 | odd = fft(x[1::2]) 38 | T = [cmath.exp(-1j * math.pi * k / n2) * odd[k] for k in range(n2)] 39 | return [even[k] + T[k] for k in range(n2)] + \ 40 | [even[k] - T[k] for k in range(n2)] 41 | 42 | 43 | def inv_fft(y): 44 | """ Inverse Fast Fourier Transformation 45 | :input y: list of sample values. Length n has to be a power of 2. 46 | :returns: list of coefficients. 47 | :complexity: O(n log n). 48 | """ 49 | n = len(y) 50 | p = fft([yi.conjugate() for yi in y]) 51 | return [pi.conjugate() / n for pi in p] 52 | 53 | 54 | def mul_poly_fft(p, q): 55 | """Multiply two polynomials in integer coefficient representation 56 | :complexity: O(n log n) 57 | """ 58 | n = (len(p) + len(q)) # make them of same and enough large size 59 | p += [0] * (n - len(p)) 60 | q += [0] * (n - len(q)) 61 | pad(p) 62 | y = fft(p) 63 | pad(q) 64 | z = fft(q) 65 | n = len(y) # the padding might have increased the size n 66 | r = [y[i] * z[i] for i in range(n)] 67 | R = inv_fft(r) 68 | return [int(round(ri.real)) for ri in R] 69 | -------------------------------------------------------------------------------- /tryalgo/floyd_warshall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | All pairs shortest paths by Floyd-Warshall 5 | 6 | jill-jênn vie, christoph dürr et pascal ortiz - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def floyd_warshall(weight): 12 | """All pairs shortest paths by Floyd-Warshall 13 | 14 | :param weight: edge weight matrix 15 | :modifies: weight matrix to contain distances in graph 16 | :returns: True if there are negative cycles 17 | :complexity: :math:`O(|V|^3)` 18 | """ 19 | V = range(len(weight)) 20 | for k in V: 21 | for u in V: 22 | for v in V: 23 | weight[u][v] = min(weight[u][v], 24 | weight[u][k] + weight[k][v]) 25 | for v in V: 26 | if weight[v][v] < 0: # negative cycle found 27 | return True 28 | return False 29 | # snip} 30 | 31 | 32 | def floyd_warshall2(weight): 33 | """All pairs shortest paths by Floyd-Warshall. 34 | An improved implementation by Pascal-Ortiz 35 | 36 | :param weight: edge weight matrix 37 | :modifies: weight matrix to contain distances in graph 38 | :returns: True if there are negative cycles 39 | :complexity: :math:`O(|V|^3)` 40 | """ 41 | for k, Wk in enumerate(weight): 42 | for _, Wu in enumerate(weight): 43 | for v, Wuv in enumerate(Wu): 44 | alt = Wu[k] + Wk[v] 45 | if alt < Wuv: 46 | Wu[v] = alt 47 | for v, Wv in enumerate(weight): 48 | if Wv[v] < 0: # negative cycle found 49 | return True 50 | return False 51 | -------------------------------------------------------------------------------- /tryalgo/ford_fulkerson.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Maximum flow by Ford-Fulkerson 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | from tryalgo.graph import add_reverse_arcs 11 | 12 | 13 | # snip{ 14 | # pylint: disable=too-many-arguments 15 | def _augment(graph, capacity, flow, val, u, target, visit, timestamp): 16 | """Find an augmenting path from u to target with value at most val""" 17 | visit[u] = timestamp 18 | if u == target: 19 | return val 20 | for v in graph[u]: 21 | cuv = capacity[u][v] 22 | if visit[v] < timestamp and cuv > flow[u][v]: # reachable arc 23 | res = min(val, cuv - flow[u][v]) 24 | delta = _augment(graph, capacity, flow, res, v, target, visit, timestamp) 25 | if delta > 0: 26 | flow[u][v] += delta # augment flow 27 | flow[v][u] -= delta 28 | return delta 29 | return 0 30 | 31 | 32 | def ford_fulkerson(graph, capacity, s, t): 33 | """Maximum flow by Ford-Fulkerson 34 | 35 | :param graph: directed graph in listlist or listdict format 36 | :param capacity: in matrix format or same listdict graph 37 | :param int s: source vertex 38 | :param int t: target vertex 39 | 40 | :returns: flow matrix, flow value 41 | :complexity: `O(|V|*|E|*max_capacity)` 42 | """ 43 | add_reverse_arcs(graph, capacity) 44 | n = len(graph) 45 | flow = [[0] * n for _ in range(n)] 46 | INF = float('inf') 47 | visit = [-1] * n 48 | timestamp = 0 49 | while _augment(graph, capacity, flow, INF, s, t, visit, timestamp) > 0: 50 | timestamp += 1 51 | return (flow, sum(flow[s])) # flow network, amount of flow 52 | # snip} 53 | -------------------------------------------------------------------------------- /tryalgo/freivalds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Test matrix product AB = C by Freivalds 5 | 6 | jill-jênn vie et christoph dürr - 2015-2020 7 | """ 8 | 9 | # snip{ 10 | from random import randint 11 | from sys import stdin 12 | 13 | # snip} 14 | __all__ = ["freivalds"] 15 | # snip{ 16 | 17 | 18 | def readint(): 19 | """ 20 | function to read an integer from stdin 21 | """ 22 | return int(stdin.readline()) 23 | 24 | 25 | def readarray(typ): 26 | """ 27 | function to read an array 28 | """ 29 | return list(map(typ, stdin.readline().split())) 30 | 31 | 32 | # pylint: disable=redefined-outer-name 33 | def readmatrix(n): 34 | """ 35 | function to read a matrix 36 | """ 37 | M = [] 38 | for _ in range(n): 39 | row = readarray(int) 40 | assert len(row) == n 41 | M.append(row) 42 | return M 43 | 44 | 45 | # pylint: disable=redefined-outer-name 46 | def mult(M, v): 47 | """ 48 | function to multiply a matrix times a vector 49 | """ 50 | n = len(M) 51 | return [sum(M[i][j] * v[j] for j in range(n)) for i in range(n)] 52 | 53 | 54 | # pylint: disable=redefined-outer-name 55 | def freivalds(A, B, C): 56 | """Tests matrix product AB=C by Freivalds 57 | 58 | :param A: n by n numerical matrix 59 | :param B: same 60 | :param C: same 61 | :returns: False with high probability if AB != C 62 | 63 | :complexity: 64 | :math:`O(n^2)` 65 | """ 66 | n = len(A) 67 | x = [randint(0, 1000000) for j in range(n)] 68 | return mult(A, mult(B, x)) == mult(C, x) 69 | 70 | 71 | # pylint: disable=redefined-outer-name 72 | if __name__ == "__main__": 73 | n = readint() 74 | A = readmatrix(n) 75 | B = readmatrix(n) 76 | C = readmatrix(n) 77 | print(freivalds(A, B, C)) 78 | # snip} 79 | -------------------------------------------------------------------------------- /tryalgo/gale_shapley.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Stable matching by Gale-Shapley 5 | 6 | jill-jênn vie et christoph durr - 2014-2019 7 | """ 8 | 9 | # snip{ 10 | from collections import deque 11 | 12 | 13 | # pylint: disable=no-member 14 | def gale_shapley(men, women): 15 | """Stable matching by Gale-Shapley 16 | 17 | :param men: table of size n, men[i] is preference list of women for men i 18 | :param women: similar 19 | :returns: matching table, from women to men 20 | :complexity: :math:`O(n^2)` 21 | """ 22 | n = len(men) 23 | assert n == len(women) 24 | current_suitor = [0] * n # nb of matchings so far 25 | spouse = [None] * n # current matching 26 | rank = [[0] * n for j in range(n)] # build rank 27 | for j in range(n): 28 | for r in range(n): 29 | rank[j][women[j][r]] = r 30 | singles = list(range(n)) # initially all men are single 31 | while singles: 32 | i = singles.pop() 33 | j = men[i][current_suitor[i]] # propose matching (i,j) 34 | current_suitor[i] += 1 35 | if spouse[j] is None: 36 | spouse[j] = i 37 | elif rank[j][spouse[j]] < rank[j][i]: 38 | singles.append(i) 39 | else: 40 | singles.append(spouse[j]) # sorry for spouse[j] 41 | spouse[j] = i 42 | return spouse 43 | # snip} 44 | -------------------------------------------------------------------------------- /tryalgo/gauss_jordan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Linear equation system Ax=b by Gauss-Jordan 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | __all__ = ["gauss_jordan", "GJ_ZERO_SOLUTIONS", "GJ_SINGLE_SOLUTION", 10 | "GJ_SEVERAL_SOLUTIONS"] 11 | 12 | 13 | # snip{ 14 | # pylint: disable=chained-comparison 15 | def is_zero(x): # tolerance 16 | """error tolerant zero test 17 | """ 18 | return -1e-6 < x and x < 1e-6 19 | # replace with x == 0 si we are handling Fraction elements 20 | 21 | 22 | GJ_ZERO_SOLUTIONS = 0 23 | GJ_SINGLE_SOLUTION = 1 24 | GJ_SEVERAL_SOLUTIONS = 2 25 | 26 | 27 | def gauss_jordan(A, x, b): 28 | """Linear equation system Ax=b by Gauss-Jordan 29 | 30 | :param A: m by n matrix 31 | :param x: table of size n 32 | :param b: table of size m 33 | :modifies: x will contain solution if any 34 | :returns int: 35 | 0 if no solution, 36 | 1 if solution unique, 37 | 2 otherwise 38 | :complexity: :math:`O(n^2m)` 39 | """ 40 | n = len(x) 41 | m = len(b) 42 | assert len(A) == m and len(A[0]) == n 43 | S = [] # put linear system in a single matrix S 44 | for i in range(m): 45 | S.append(A[i][:] + [b[i]]) 46 | S.append(list(range(n))) # indices in x 47 | k = diagonalize(S, n, m) 48 | if k < m: 49 | for i in range(k, m): 50 | if not is_zero(S[i][n]): 51 | return GJ_ZERO_SOLUTIONS 52 | for j in range(k): 53 | x[S[m][j]] = S[j][n] 54 | if k < n: 55 | for j in range(k, n): 56 | x[S[m][j]] = 0 57 | return GJ_SEVERAL_SOLUTIONS 58 | return GJ_SINGLE_SOLUTION 59 | 60 | 61 | def diagonalize(S, n, m): 62 | """diagonalize """ 63 | for k in range(min(n, m)): 64 | val, i, j = max((abs(S[i][j]), i, j) 65 | for i in range(k, m) for j in range(k, n)) 66 | if is_zero(val): 67 | return k 68 | S[i], S[k] = S[k], S[i] # swap lines k, i 69 | for r in range(m + 1): # swap columns k, j 70 | S[r][j], S[r][k] = S[r][k], S[r][j] 71 | pivot = float(S[k][k]) # without float if Fraction elements 72 | for j in range(k, n + 1): 73 | S[k][j] /= pivot # divide line k by pivot 74 | for i in range(m): # remove line k scaled by line i 75 | if i != k: 76 | fact = S[i][k] 77 | for j in range(k, n + 1): 78 | S[i][j] -= fact * S[k][j] 79 | return min(n, m) 80 | # snip} 81 | -------------------------------------------------------------------------------- /tryalgo/graph01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Shortest path in a 0,1 weighted graph 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | from collections import deque 10 | 11 | 12 | # snip{ 13 | def dist01(graph, weight, source=0, target=None): 14 | """Shortest path in a 0,1 weighted graph 15 | 16 | :param graph: directed graph in listlist or listdict format 17 | :param weight: matrix or adjacency dictionary 18 | :param int source: vertex 19 | :param target: exploration stops once distance to target is found 20 | :returns: distance table, predecessor table 21 | :complexity: `O(|V|+|E|)` 22 | """ 23 | n = len(graph) 24 | dist = [float('inf')] * n 25 | prec = [None] * n 26 | black = [False] * n 27 | dist[source] = 0 28 | gray = deque([source]) 29 | while gray: 30 | node = gray.pop() 31 | if black[node]: 32 | continue 33 | black[node] = True 34 | if node == target: 35 | break 36 | for neighbor in graph[node]: 37 | ell = dist[node] + weight[node][neighbor] 38 | if black[neighbor] or dist[neighbor] <= ell: 39 | continue 40 | dist[neighbor] = ell 41 | prec[neighbor] = node 42 | if weight[node][neighbor] == 0: 43 | gray.append(neighbor) 44 | else: 45 | gray.appendleft(neighbor) 46 | return dist, prec 47 | # snip} 48 | -------------------------------------------------------------------------------- /tryalgo/hamiltonian_cycle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Hamiltonian Cycle 5 | 6 | jill-jenn vie et christoph durr - 2023 7 | """ 8 | 9 | 10 | # snip{ 11 | def hamiltonian_cycle(weight): 12 | """Hamiltonian Cycle 13 | 14 | :param weight: matrix of edge weights of a complete graph 15 | :returns: minimum weight of a Hamiltonian cycle 16 | :complexity: O(n^2 2^n) 17 | """ 18 | n = len(weight) 19 | # A[S][v] = minimum weight path from vertex n-1, 20 | # then visiting all vertices in S exactly once, 21 | # and finishing in v (which is not in S) 22 | A = [[float('inf')] * n for _ in range(1 << (n - 1))] 23 | for v in range(n): # base case 24 | A[0][v] = weight[n - 1][v] 25 | for S in range(1, 1 << (n - 1)): 26 | for v in range(n): 27 | if not ((1 << v) & S): # v not in S 28 | for u in range(n - 1): 29 | U = 1 << u 30 | if U & S: # u in S 31 | alt = A[S ^ U][u] + weight[u][v] 32 | if alt < A[S][v]: 33 | A[S][v] = alt 34 | S = (1 << (n - 1)) - 1 # {0, 1, ..., n - 2} 35 | return A[S][n - 1] 36 | # snip} 37 | 38 | -------------------------------------------------------------------------------- /tryalgo/horn_sat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Solving Horn SAT 5 | 6 | christoph dürr - 2016-2020 7 | 8 | clauses are numbered starting from 0 9 | variables are strings (identifier) 10 | 11 | solution : set of variables that are set to true 12 | posvar_in_clause : maps clause to the unique positive variable in clause 13 | (or None) 14 | clause_with_negvar : maps variable v to all clauses that contain not(v) 15 | 16 | every clause has a score: number of its negative variables 17 | that are not in solution sol 18 | pool : maps score to clauses of that score 19 | """ 20 | 21 | from collections import defaultdict 22 | import sys 23 | 24 | 25 | # To mock it, https://stackoverflow.com/a/44677646/827989 26 | def read(filename): 27 | """ reads a Horn SAT formula from a text file 28 | 29 | :file format: 30 | # comment 31 | A # clause with unique positive literal 32 | :- A # clause with unique negative literal 33 | A :- B, C, D # clause where A is positive and B,C,D negative 34 | # variables are strings without spaces 35 | """ 36 | formula = [] 37 | for line in open(filename, 'r'): 38 | line = line.strip() 39 | if line[0] == "#": 40 | continue 41 | lit = line.split(":-") 42 | if len(lit) == 1: 43 | posvar = lit[0] 44 | negvars = [] 45 | else: 46 | assert len(lit) == 2 47 | posvar = lit[0].strip() 48 | if posvar == '': 49 | posvar = None 50 | negvars = lit[1].split(',') 51 | for i, _ in enumerate(negvars): 52 | negvars[i] = negvars[i].strip() 53 | formula.append((posvar, negvars)) 54 | return formula 55 | 56 | 57 | # pylint: disable=line-too-long 58 | def horn_sat(formula): 59 | """ Solving a HORN Sat formula 60 | 61 | :param formula: list of couple(posvar, negvars). 62 | negvars is a list of the negative variables (can be empty) 63 | posvar is the positive variable (can be None) 64 | Variables can be any hashable objects: integers, strings... 65 | :returns: None if formula is not satisfiable, else a minimal set of vars 66 | that have to be set to true in order to satisfy the formula. 67 | :complexity: linear 68 | """ 69 | # --- construct data structures 70 | CLAUSES = range(len(formula)) 71 | score = [0 for c in CLAUSES] # number of neg vars not yet in solution 72 | # the unique positive variable of a clause (if any) 73 | posvar_in_clause = [None for c in CLAUSES] 74 | # all clauses where a variable appears negatively 75 | clauses_with_negvar = defaultdict(set) 76 | for c in CLAUSES: 77 | posvar, negvars = formula[c] 78 | score[c] = len(set(negvars)) # do not count twice negative variables 79 | posvar_in_clause[c] = posvar 80 | for v in negvars: 81 | clauses_with_negvar[v].add(c) 82 | pool = [set() for s in range(max(score) + 1)] # create the pool 83 | for c in CLAUSES: 84 | pool[score[c]].add(c) # pool[s] = set of clauses with score s 85 | 86 | # --- solve Horn SAT formula 87 | solution = set() # contains all variables set to True 88 | while pool[0]: 89 | curr = pool[0].pop() # arbitrary zero score clause 90 | v = posvar_in_clause[curr] 91 | if v is None: # formula is not satisfiable 92 | return None 93 | if v in solution or curr in clauses_with_negvar[v]: 94 | continue # clause is already satisfied 95 | solution.add(v) 96 | for c in clauses_with_negvar[v]: # update score 97 | pool[score[c]].remove(c) 98 | score[c] -= 1 99 | pool[score[c]].add(c) # change c to lower score in pool 100 | return solution 101 | 102 | 103 | if __name__ == "__main__": 104 | F = read(sys.argv[1]) 105 | sol = horn_sat(F) 106 | if sol is None: 107 | print("No solution") 108 | else: 109 | print("Minimal solution:") 110 | for x in sorted(sol): 111 | print(x) 112 | -------------------------------------------------------------------------------- /tryalgo/huffman.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Huffman code 5 | 6 | jill-jenn vie et christoph durr - 2014-2022 7 | """ 8 | 9 | from heapq import heappush, heappop 10 | 11 | 12 | # snip{ 13 | def huffman(freq): 14 | """Huffman code 15 | 16 | :param freq: dictionary with frequencies for each item 17 | :returns: dictionary with binary code string for each item 18 | :complexity: O(n log n), where n = len(freq) 19 | """ 20 | forest = [] # is a heap with (frequence, tree_index) pairs 21 | trees = [] # list of trees, tree is a letter or a list of two trees 22 | for item in freq: 23 | heappush(forest, (freq[item], len(trees))) 24 | trees.append(item) # this item will be at index len(trees) 25 | while len(forest) > 1: 26 | (f_l, left) = heappop(forest) # merge two trees 27 | (f_r, right) = heappop(forest) 28 | heappush(forest, (f_l + f_r, len(trees))) 29 | trees.append([trees[left], trees[right]]) 30 | code = {} # build code from unique tree 31 | extract(code, trees[forest[0][1]], []) 32 | return code 33 | 34 | 35 | def extract(code, tree, prefix): 36 | """Extract Huffman code from a Huffman tree 37 | 38 | :param code: a dictionary that will contain the constructed code. 39 | should initially be empty. 40 | :param tree: a node of the tree. 41 | Leafs are of the form (frequency, symbol). 42 | Inner nodes are of the form [left_sub_tree, right_sub_tree]. 43 | :param prefix: a list with the 01 characters encoding the path from 44 | the root to the node `tree` 45 | :complexity: O(n), where n = number of nodes in tree 46 | """ 47 | if isinstance(tree, list): # inner node 48 | left, right = tree 49 | prefix.append('0') 50 | extract(code, left, prefix) # build code recursively 51 | prefix.pop() 52 | prefix.append('1') 53 | extract(code, right, prefix) # ... on both subtrees 54 | prefix.pop() 55 | else: 56 | code[tree] = ''.join(prefix) # extract codeword from prefix 57 | # snip} 58 | -------------------------------------------------------------------------------- /tryalgo/interval_cover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Minimum interval cover 5 | 6 | jill-jênn vie et christoph dürr - 2014-2020 7 | """ 8 | 9 | from sys import stdin 10 | from math import sqrt 11 | from tryalgo.our_std import readarray 12 | 13 | # pylint: disable=redefined-outer-name 14 | 15 | 16 | def _solve(iles, rayon): 17 | II = [] 18 | for x, y in iles: 19 | if y > rayon: 20 | return -1 # island is too far 21 | z = sqrt(rayon * rayon - y * y) # find the interval 22 | II.append((x + z, x - z)) 23 | II.sort() # sort by right side 24 | sol = 0 25 | last = float('-inf') 26 | for right, left in II: 27 | if last < left: # uncovered interval 28 | sol += 1 29 | last = right # put an antenna 30 | return sol 31 | 32 | 33 | # snip{ 34 | def interval_cover(I): 35 | """Minimum interval cover 36 | 37 | :param I: list of closed intervals 38 | :returns: minimum list of points covering all intervals 39 | :complexity: O(n log n) 40 | """ 41 | S = [] 42 | # sort by right endpoints 43 | for start, end in sorted(I, key=lambda v: v[1]): 44 | if not S or S[-1] < start: 45 | S.append(end) 46 | return S 47 | # snip} 48 | 49 | 50 | if __name__ == "__main__": 51 | # http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1360 52 | testCase = 1 53 | while True: 54 | n, rayon = readarray(int) # n=#islands, d=radius 55 | if n == 0: 56 | break # end of instances 57 | iles = [] 58 | for _ in range(n): 59 | x, y = readarray(int) 60 | iles.append((x, y)) 61 | stdin.readline() # consume empty line 62 | print("Case %i: %i" % (testCase, _solve(iles, rayon))) 63 | testCase += 1 64 | -------------------------------------------------------------------------------- /tryalgo/interval_tree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Interval tree 5 | 6 | christoph dürr - jill-jênn vie - 2013-2018 7 | """ 8 | 9 | from bisect import bisect_right 10 | 11 | 12 | # snip{ 13 | # pylint: disable=too-many-arguments, too-few-public-methods 14 | class _Node: 15 | def __init__(self, center, by_low, by_high, left, right): 16 | self.center = center 17 | self.by_low = by_low 18 | self.by_high = by_high 19 | self.left = left 20 | self.right = right 21 | 22 | 23 | def interval_tree(intervals): 24 | """Construct an interval tree 25 | 26 | :param intervals: list of half-open intervals 27 | encoded as value pairs *[left, right)* 28 | :assumes: intervals are lexicographically ordered 29 | ``>>> assert intervals == sorted(intervals)`` 30 | :returns: the root of the interval tree 31 | :complexity: :math:`O(n)` 32 | """ 33 | if intervals == []: 34 | return None 35 | center = intervals[len(intervals) // 2][0] 36 | L = [] 37 | R = [] 38 | C = [] 39 | for I in intervals: 40 | if I[1] <= center: 41 | L.append(I) 42 | elif center < I[0]: 43 | R.append(I) 44 | else: 45 | C.append(I) 46 | by_low = sorted((I[0], I) for I in C) 47 | by_high = sorted((I[1], I) for I in C) 48 | IL = interval_tree(L) 49 | IR = interval_tree(R) 50 | return _Node(center, by_low, by_high, IL, IR) 51 | 52 | 53 | def intervals_containing(t, p): 54 | """Query the interval tree 55 | 56 | :param t: root of the interval tree 57 | :param p: value 58 | :returns: a list of intervals containing p 59 | :complexity: O(log n + m), where n is the number of intervals in t, 60 | and m the length of the returned list 61 | """ 62 | INF = float('inf') 63 | if t is None: 64 | return [] 65 | if p < t.center: 66 | retval = intervals_containing(t.left, p) 67 | j = bisect_right(t.by_low, (p, (INF, INF))) 68 | for i in range(j): 69 | retval.append(t.by_low[i][1]) 70 | else: 71 | retval = intervals_containing(t.right, p) 72 | i = bisect_right(t.by_high, (p, (INF, INF))) 73 | for j in range(i, len(t.by_high)): 74 | retval.append(t.by_high[j][1]) 75 | return retval 76 | # snip} 77 | -------------------------------------------------------------------------------- /tryalgo/intervals_union.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Union of intervals 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def intervals_union(S): 12 | """Union of intervals 13 | 14 | :param S: list of pairs (low, high) defining intervals [low, high) 15 | :returns: ordered list of disjoint intervals with the same union as S 16 | :complexity: O(n log n) 17 | """ 18 | E = [(low, -1) for (low, high) in S] 19 | E += [(high, +1) for (low, high) in S] 20 | nb_open = 0 21 | last = None 22 | retval = [] 23 | for x, _dir in sorted(E): 24 | if _dir == -1: 25 | if nb_open == 0: 26 | last = x 27 | nb_open += 1 28 | else: 29 | nb_open -= 1 30 | if nb_open == 0: 31 | retval.append((last, x)) 32 | return retval 33 | # snip} 34 | -------------------------------------------------------------------------------- /tryalgo/karatsuba.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Karatsuba multiplication of polynomials 5 | 6 | christoph dürr - jill-jênn vie - 2022 7 | """ 8 | 9 | 10 | # snip{ 11 | def eval_poly(P, x): 12 | """evaluate a polynomial in x. 13 | :param P: an array representing the polynomial 14 | :returns: the value of P(x) 15 | :complexity: linear in the size of P. 16 | """ 17 | # use Horner's rule 18 | retval = 0 19 | for pi in reversed(P): 20 | retval = retval * x + pi 21 | return retval 22 | # snip} 23 | 24 | # snip{ 25 | def add_poly(P, Q): 26 | """ Add two polynomials represented by their coefficients. 27 | :param P, Q: two vectors representing polynomials 28 | :returns: a vector representing the addition 29 | :complexity: linear in the size of P and Q. 30 | """ 31 | if len(P) < len(Q): 32 | P, Q = Q, P # add the shorter to the longer vector 33 | R = P[::] # make a copy 34 | for i, qi in enumerate(Q): 35 | R[i] += qi # cumulate Q into R 36 | return R 37 | # snip} 38 | 39 | # snip{ 40 | def sub_poly(P, Q): 41 | """ Subtruct two polynomials represented by their coefficients. 42 | :param P, Q: two vectrs representing polynomials 43 | :returns: a vector representing the difference 44 | :complexity: linear in the size of P and Q. 45 | """ 46 | return add_poly(P, [-qi for qi in Q]) 47 | # snip} 48 | 49 | # snip{ 50 | def mul_poly(P, Q): 51 | """ Karatsuba's algorithm. 52 | Multiply two polynomials represented by their coefficients. 53 | i.e. P(x) = sum P[i] x**i. 54 | :param P, Q: two vectors representing polynomials 55 | :returns: a vector representing the product 56 | :complexity: $O(n^{\log_2 3})=O(n^{1.585})$, where n is total degree of P and Q. 57 | """ 58 | if not P or not Q: # case one of P, Q is the constant zero 59 | return [] 60 | if len(P) == 1: 61 | return [qi * P[0] for qi in Q] 62 | elif len(Q) == 1: 63 | return [pi * Q[0] for pi in P] 64 | k = max(len(P), len(Q)) // 2 65 | xk = [0] * k 66 | a = P[:k] # split: P = a + b * x**k 67 | b = P[k:] 68 | c = Q[:k] # split: Q = c + d * x**k 69 | d = Q[k:] 70 | a_b = sub_poly(a, b) 71 | c_d = sub_poly(c, d) 72 | ac = mul_poly(a, c) 73 | bd = mul_poly(b, d) 74 | abcd = mul_poly(a_b, c_d) 75 | ad_bc = sub_poly(add_poly(ac, bd), abcd) # = ad - bc 76 | # result is ac + [ac + bd - (a - b)(c - d)]*x**k + bd*x**(2k) 77 | return add_poly(ac, xk + add_poly(ad_bc, xk + bd)) 78 | # snip} 79 | -------------------------------------------------------------------------------- /tryalgo/knapsack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Knapsack 5 | 6 | jill-jênn vie et christoph dürr - 2015-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def knapsack(p, v, cmax): 12 | """Knapsack problem: select maximum value set of items if total size not 13 | more than capacity 14 | 15 | :param p: table with size of items 16 | :param v: table with value of items 17 | :param cmax: capacity of bag 18 | :requires: number of items non-zero 19 | :returns: value optimal solution, list of item indexes in solution 20 | :complexity: O(n * cmax), for n = number of items 21 | """ 22 | n = len(p) 23 | opt = [[0] * (cmax + 1) for _ in range(n + 1)] 24 | sel = [[False] * (cmax + 1) for _ in range(n + 1)] 25 | # --- basic case 26 | for cap in range(p[0], cmax + 1): 27 | opt[0][cap] = v[0] 28 | sel[0][cap] = True 29 | # --- induction case 30 | for i in range(1, n): 31 | for cap in range(cmax + 1): 32 | if cap >= p[i] and opt[i-1][cap - p[i]] + v[i] > opt[i-1][cap]: 33 | opt[i][cap] = opt[i-1][cap - p[i]] + v[i] 34 | sel[i][cap] = True 35 | else: 36 | opt[i][cap] = opt[i-1][cap] 37 | sel[i][cap] = False 38 | # --- reading solution 39 | cap = cmax 40 | solution = [] 41 | for i in range(n-1, -1, -1): 42 | if sel[i][cap]: 43 | solution.append(i) 44 | cap -= p[i] 45 | return (opt[n - 1][cmax], solution) 46 | # snip} 47 | 48 | 49 | def knapsack2(p, v, cmax): 50 | """Knapsack problem: select maximum value set of items if total size not 51 | more than capacity. 52 | alternative implementation with same behavior. 53 | 54 | :param p: table with size of items 55 | :param v: table with value of items 56 | :param cmax: capacity of bag 57 | :requires: number of items non-zero 58 | :returns: value optimal solution, list of item indexes in solution 59 | :complexity: O(n * cmax), for n = number of items 60 | """ 61 | n = len(p) 62 | # Plus grande valeur obtenable avec objets ≤ i et capacité c 63 | pgv = [[0] * (cmax + 1) for _ in range(n)] 64 | for c in range(cmax + 1): # Initialisation 65 | pgv[0][c] = v[0] if c >= p[0] else 0 66 | pred = {} # Prédécesseurs pour mémoriser les choix faits 67 | for i in range(1, n): 68 | for c in range(cmax + 1): 69 | pgv[i][c] = pgv[i - 1][c] # Si on ne prend pas l'objet i 70 | pred[(i, c)] = (i - 1, c) 71 | # Est-ce que prendre l'objet i est préférable ? 72 | if c >= p[i] and pgv[i - 1][c - p[i]] + v[i] > pgv[i][c]: 73 | pgv[i][c] = pgv[i - 1][c - p[i]] + v[i] 74 | pred[(i, c)] = (i - 1, c - p[i]) # On marque le prédécesseur 75 | # On pourrait s'arrêter là, mais si on veut un sous-ensemble d'objets 76 | # optimal, il faut remonter les marquages 77 | cursor = (n - 1, cmax) 78 | chosen = [] 79 | while cursor in pred: 80 | # Si la case prédécesseur a une capacité inférieure 81 | if pred[cursor][1] < cursor[1]: 82 | # C'est qu'on a ramassé l'objet sur le chemin 83 | chosen.append(cursor[0]) 84 | cursor = pred[cursor] 85 | if cursor[1] > 0: # A-t-on pris le premier objet ? 86 | # (La première ligne n'a pas de prédécesseur.) 87 | chosen.append(cursor[0]) 88 | return pgv[n - 1][cmax], chosen 89 | -------------------------------------------------------------------------------- /tryalgo/knuth_morris_pratt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Find the length of maximal borders by Knuth-Morris-Pratt 5 | 6 | jill-jênn vie, christoph dürr et louis abraham - 2014-2019 7 | inspired from a practical lesson (TP) from Yves Lemaire 8 | """ 9 | # pylint: disable=undefined-variable, unused-argument 10 | 11 | 12 | # snip{ maximum_border_length 13 | def maximum_border_length(w): 14 | """Maximum string borders by Knuth-Morris-Pratt 15 | 16 | :param w: string 17 | :returns: table f such that f[i] is the longest border length of w[:i + 1] 18 | :complexity: linear 19 | """ 20 | n = len(w) 21 | f = [0] * n # init f[0] = 0 22 | k = 0 # current longest border length 23 | for i in range(1, n): # compute f[i] 24 | while w[k] != w[i] and k > 0: 25 | k = f[k - 1] # mismatch: try the next border 26 | if w[k] == w[i]: # last characters match 27 | k += 1 # we can increment the border length 28 | f[i] = k # we found the maximal border of w[:i + 1] 29 | return f 30 | # snip} 31 | 32 | 33 | # snip{ knuth_morris_pratt 34 | def knuth_morris_pratt(s, t): 35 | """Find a substring by Knuth-Morris-Pratt 36 | 37 | :param s: the haystack string 38 | :param t: the needle string 39 | :returns: index i such that s[i: i + len(t)] == t, or -1 40 | :complexity: O(len(s) + len(t)) 41 | """ 42 | sep = '\x00' # special unused character 43 | assert sep not in t and sep not in s 44 | f = maximum_border_length(t + sep + s) 45 | n = len(t) 46 | for i, fi in enumerate(f): 47 | if fi == n: # found a border of the length of t 48 | return i - 2 * n # beginning of the border in s 49 | return -1 50 | # snip} 51 | 52 | 53 | # snip{ powerstring_by_border 54 | def powerstring_by_border(u): 55 | """Power string by Knuth-Morris-Pratt 56 | 57 | :param u: string 58 | :returns: largest k such that there is a string y with u = y^k 59 | :complexity: O(len(u)) 60 | """ 61 | f = maximum_border_length(u) 62 | n = len(u) 63 | if n % (n - f[-1]) == 0: # does the alignment shift divide n ? 64 | return n // (n - f[-1]) # we found a power decomposition 65 | return 1 66 | # snip} 67 | 68 | 69 | # snip{ powerstring_by_find 70 | def powerstring_by_find(u): 71 | """Power string using the python find method 72 | 73 | :param u: string 74 | :returns: largest k such that there is a string y with u = y^k 75 | :complexity: O(len(u)^2), this is due to the naive implementation of string.find 76 | """ 77 | return len(u) // (u + u).find(u, 1) 78 | # snip} 79 | -------------------------------------------------------------------------------- /tryalgo/kruskal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Minimum spanning tree by kruskal 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | from math import sqrt 10 | import random 11 | 12 | 13 | # snip{ union-find 14 | class UnionFind: 15 | """Maintains a partition of {0, ..., n-1} 16 | """ 17 | def __init__(self, n): 18 | self.up_bound = list(range(n)) 19 | self.rank = [0] * n 20 | 21 | def find(self, x_index): 22 | """ 23 | :returns: identifier of part containing x_index 24 | :complex_indexity: O(inverse_ackerman(n)) 25 | """ 26 | if self.up_bound[x_index] == x_index: 27 | return x_index 28 | self.up_bound[x_index] = self.find(self.up_bound[x_index]) 29 | return self.up_bound[x_index] 30 | 31 | def union(self, x_index, y_index): 32 | """ 33 | Merges part that contain x and part containing y 34 | :returns: False if x_index, y_index are already in same part 35 | :complexity: O(inverse_ackerman(n)) 36 | """ 37 | repr_x = self.find(x_index) 38 | repr_y = self.find(y_index) 39 | if repr_x == repr_y: # already in the same component 40 | return False 41 | if self.rank[repr_x] == self.rank[repr_y]: 42 | self.rank[repr_x] += 1 43 | self.up_bound[repr_y] = repr_x 44 | elif self.rank[repr_x] > self.rank[repr_y]: 45 | self.up_bound[repr_y] = repr_x 46 | else: 47 | self.up_bound[repr_x] = repr_y 48 | return True 49 | # snip} 50 | 51 | 52 | # snip{ kruskal 53 | # pylint: disable=redefined-outer-name, unused-variable 54 | def kruskal(graph, weight): 55 | """Minimum spanning tree by Kruskal 56 | 57 | :param graph: undirected graph in listlist or listdict format 58 | :param weight: in matrix format or same listdict graph 59 | :returns: list of edges of the tree 60 | :complexity: ``O(|E|log|E|)`` 61 | """ 62 | u_f = UnionFind(len(graph)) 63 | edges = [] 64 | for u, _ in enumerate(graph): 65 | for v in graph[u]: 66 | edges.append((weight[u][v], u, v)) 67 | edges.sort() 68 | min_span_tree = [] 69 | for w_idx, u_idx, v_idx in edges: 70 | if u_f.union(u_idx, v_idx): 71 | min_span_tree.append((u_idx, v_idx)) 72 | return min_span_tree 73 | # snip} 74 | 75 | 76 | def dist(a, b): 77 | """ 78 | distance between point a and point b 79 | """ 80 | return sqrt(sum([(a[i] - b[i]) * (a[i] - b[i]) 81 | for i in range(len(a))])) 82 | 83 | 84 | # pylint: disable=pointless-string-statement 85 | if __name__ == "__main__": 86 | """ 87 | main function 88 | """ 89 | N = 256 90 | points = [[random.random() * 5, random.random() * 5] for _ in range(N)] 91 | weight = [[dist(points[i], points[j]) for j in range(N)] 92 | for i in range(N)] 93 | graph = [[j for j in range(N) if i != j] for i in range(N)] 94 | 95 | with open('../data/kruskal-points.tex', 'w') as infile: 96 | min_span_tree = kruskal(graph, weight) 97 | val = 0 98 | for u_idx, v_idx in min_span_tree: 99 | val += weight[u_idx][v_idx] 100 | infile.write('\\draw[blue] (%f, %f) -- (%f, %f);\n' 101 | % tuple(points[u_idx] + points[v_idx])) 102 | for point in points: 103 | infile.write('\\filldraw[black] (%f, %f) circle (1pt);\n' 104 | % tuple(point)) 105 | print(val) 106 | -------------------------------------------------------------------------------- /tryalgo/kuhn_munkres.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Maximum profit bipartite matching by Kuhn-Munkres 5 | 6 | jill-jenn vie, christoph durr and samuel tardieu - 2014-2019 7 | 8 | primal LP 9 | 10 | max sum_{u,v} w[u,v] * x[u,v] 11 | 12 | such that 13 | for all u in U: sum_v x[u,v] == 1 (l[u]) 14 | 15 | for all v in V: sum_u x[u,v] <= 1 (l[v]) 16 | 17 | for all u,v: x[u,v] >= 0 18 | 19 | 20 | dual LP 21 | 22 | min sum_u l[u] + sum_v l[v] 23 | 24 | such that 25 | for all u,v: l[u] + l[v] >= w[u,v] (*) 26 | 27 | for all u in U: l[u] is arbitrary (free variable) 28 | for all v in V: l[v] >= 0 29 | 30 | 31 | primal-dual algorithm: 32 | 33 | Start with trivial solution l for dual and with trivial 34 | non-solution x for primal. 35 | 36 | Iteratively improve primal or dual solution, maintaining complementary 37 | slackness conditions. 38 | 39 | """ 40 | 41 | 42 | # snip{ 43 | # pylint: disable=too-many-locals, too-many-branches 44 | def kuhn_munkres(G, TOLERANCE=1e-6): 45 | """Maximum profit bipartite matching by Kuhn-Munkres 46 | 47 | :param G: weight matrix where G[u][v] is the weight of edge (u,v), 48 | :param TOLERANCE: a value with absolute value below tolerance 49 | is considered as being zero. 50 | If G consists of integer or fractional values 51 | then TOLERANCE can be chosen 0. 52 | :requires: graph (U,V,E) is complete bi-partite graph with len(U) <= len(V) 53 | float('-inf') or float('inf') entries in G 54 | are allowed but not None. 55 | :returns: matching table from U to V, value of matching 56 | :complexity: :math:`O(|U|^2 |V|)` 57 | """ 58 | nU = len(G) 59 | U = range(nU) 60 | nV = len(G[0]) 61 | V = range(nV) 62 | assert nU <= nV 63 | mu = [None] * nU # empty matching 64 | mv = [None] * nV 65 | lu = [max(row) for row in G] # trivial labels 66 | lv = [0] * nV 67 | for root in U: # build an alternate tree 68 | au = [False] * nU # au, av mark nodes... 69 | au[root] = True # ... covered by the tree 70 | Av = [None] * nV # Av[v] successor of v in the tree 71 | # for every vertex u, slack[u] := (val, v) such that 72 | # val is the smallest slack on the constraints (*) 73 | # with fixed u and v being the corresponding vertex 74 | slack = [(lu[root] + lv[v] - G[root][v], root) for v in V] 75 | while True: 76 | (delta, u), v = min((slack[v], v) for v in V if Av[v] is None) 77 | assert au[u] 78 | if delta > TOLERANCE: # tree is full 79 | for u0 in U: # improve labels 80 | if au[u0]: 81 | lu[u0] -= delta 82 | for v0 in V: 83 | if Av[v0] is not None: 84 | lv[v0] += delta 85 | else: 86 | (val, arg) = slack[v0] 87 | slack[v0] = (val - delta, arg) 88 | assert abs(lu[u] + lv[v] - G[u][v]) <= TOLERANCE # equality 89 | Av[v] = u # add (u, v) to A 90 | if mv[v] is None: 91 | break # alternating path found 92 | u1 = mv[v] 93 | assert not au[u1] 94 | au[u1] = True # add (u1, v) to A 95 | for v1 in V: 96 | if Av[v1] is None: # update margins 97 | alt = (lu[u1] + lv[v1] - G[u1][v1], u1) 98 | if slack[v1] > alt: 99 | slack[v1] = alt 100 | while v is not None: # ... alternating path found 101 | u = Av[v] # along path to root 102 | prec = mu[u] 103 | mv[v] = u # augment matching 104 | mu[u] = v 105 | v = prec 106 | return (mu, sum(lu) + sum(lv)) 107 | # snip} 108 | -------------------------------------------------------------------------------- /tryalgo/kuhn_munkres_n4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | kuhn_munkres_n4 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | __all__ = ["kuhn_munkres"] 10 | 11 | 12 | # snip{ 13 | # pylint: disable=too-many-arguments 14 | def improve_matching(G, u, mu, mv, au, av, lu, lv): 15 | """improve matching""" 16 | assert not au[u] 17 | au[u] = True 18 | for v in range(len(G)): 19 | if not av[v] and G[u][v] == lu[u] + lv[v]: 20 | av[v] = True 21 | if mv[v] is None or \ 22 | improve_matching(G, mv[v], mu, mv, au, av, lu, lv): 23 | mv[v] = u 24 | mu[u] = v 25 | return True 26 | return False 27 | 28 | 29 | # pylint: disable=bad-continuation, superfluous-parens 30 | def improve_labels(G, au, av, lu, lv): 31 | """improve labels""" 32 | U = V = range(len(G)) 33 | delta = min(min(lu[u] + lv[v] - G[u][v] 34 | for v in V if not av[v]) for u in U if au[u]) 35 | for u in U: 36 | if (au[u]): 37 | lu[u] -= delta 38 | for v in V: 39 | if (av[v]): 40 | lv[v] += delta 41 | 42 | 43 | def kuhn_munkres(G): # maximum profit bipartite matching in O(n^4) 44 | """Maximum profit perfect matching 45 | 46 | for minimum cost perfect matching just inverse the weights 47 | 48 | :param G: squared weight matrix of a complete bipartite graph 49 | :complexity: :math:`O(n^4)` 50 | """ 51 | assert len(G) == len(G[0]) 52 | n = len(G) 53 | mu = [None] * n # Empty matching 54 | mv = [None] * n 55 | lu = [max(row) for row in G] # Trivial labels 56 | lv = [0] * n 57 | for u0 in range(n): 58 | if mu[u0] is None: # Free node 59 | while True: 60 | au = [False] * n # Empty alternating tree 61 | av = [False] * n 62 | if improve_matching(G, u0, mu, mv, au, av, lu, lv): 63 | break 64 | improve_labels(G, au, av, lu, lv) 65 | return (mu, sum(lu) + sum(lv)) 66 | # snip} 67 | -------------------------------------------------------------------------------- /tryalgo/laser_mirrors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Orienting mirrors to allow connectivity by a laser beam 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | # snip{ laser-miroir-preparation 10 | # directions 11 | UP = 0 12 | LEFT = 1 13 | DOWN = 2 14 | RIGHT = 3 15 | # orientations None:? 0:/ 1:\ 16 | 17 | # destination UP LEFT DOWN RIGHT 18 | reflex = [[RIGHT, LEFT], [DOWN, UP], [LEFT, RIGHT], [UP, DOWN]] 19 | 20 | 21 | # pylint: disable=unused-variable, unused-argument 22 | def laser_mirrors(rows, cols, mir): 23 | """Orienting mirrors to allow reachability by laser beam 24 | 25 | :param int rows: 26 | :param int cols: rows and cols are the dimension of the grid 27 | :param mir: list of mirror coordinates, except 28 | mir[0]= laser entrance, 29 | mir[-1]= laser exit. 30 | :complexity: :math:`O(2^n)` 31 | """ 32 | # build structures 33 | n = len(mir) 34 | orien = [None] * (n + 2) 35 | orien[n] = 0 # arbitrary orientations 36 | orien[n + 1] = 0 37 | succ = [[None for direc in range(4)] for i in range(n + 2)] 38 | L = [(mir[i][0], mir[i][1], i) for i in range(n)] 39 | L.append((0, -1, n)) # enter 40 | L.append((0, cols, n + 1)) # exit 41 | last_r, last_i = None, None 42 | for (r, c, i) in sorted(L): # sweep by row 43 | if last_r == r: 44 | succ[i][LEFT] = last_i 45 | succ[last_i][RIGHT] = i 46 | last_r, last_i = r, i 47 | last_c = None 48 | for (r, c, i) in sorted(L, key=lambda rci: (rci[1], rci[0])): 49 | if last_c == c: # sweep by column 50 | succ[i][UP] = last_i 51 | succ[last_i][DOWN] = i 52 | last_c, last_i = c, i 53 | if solve(succ, orien, n, RIGHT): # exploration 54 | return orien[:n] 55 | return None 56 | # snip} 57 | 58 | 59 | # snip{ laser-miroir-exploration 60 | def solve(succ, orien, i, direc): 61 | """Can a laser leaving mirror i in direction direc reach exit ? 62 | 63 | :param i: mirror index 64 | :param direc: direction leaving mirror i 65 | :param orient: orient[i]=orientation of mirror i 66 | :param succ: succ[i][direc]=succ mirror reached 67 | when leaving i in direction direc 68 | """ 69 | assert orien[i] is not None 70 | j = succ[i][direc] 71 | if j is None: # basic case 72 | return False 73 | if j == len(orien) - 1: 74 | return True 75 | if orien[j] is None: # try both orientations 76 | for x in [0, 1]: 77 | orien[j] = x 78 | if solve(succ, orien, j, reflex[direc][x]): 79 | return True 80 | orien[j] = None 81 | return False 82 | return solve(succ, orien, j, reflex[direc][orien[j]]) 83 | # snip} 84 | -------------------------------------------------------------------------------- /tryalgo/left_right_inversions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Left and right inversions in a table 5 | 6 | christoph dürr - 2016-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=too-many-arguments, missing-docstring 12 | def _merge_sort(tab, tmp, rank, left, right, lo, hi): 13 | if hi <= lo + 1: # interval is empty or singleton 14 | return # nothing to do 15 | mid = lo + (hi - lo) // 2 # divide interval into [lo:mid], [mid:hi] 16 | _merge_sort(tab, tmp, rank, left, right, lo, mid) 17 | _merge_sort(tab, tmp, rank, left, right, mid, hi) 18 | i = lo # merge both lists 19 | j = mid 20 | k = lo 21 | while k < hi: 22 | if i < mid and (j == hi or tab[rank[i]] <= tab[rank[j]]): 23 | tmp[k] = rank[i] 24 | right[rank[i]] += j - mid 25 | i += 1 26 | else: 27 | tmp[k] = rank[j] 28 | left[rank[j]] += mid - i 29 | j += 1 30 | k += 1 31 | for k in range(lo, hi): # copy sorted segment into original table 32 | rank[k] = tmp[k] 33 | 34 | 35 | # pylint: disable=anomalous-backslash-in-string 36 | def left_right_inversions(tab): 37 | """ Compute left and right inversions of each element of a table. 38 | 39 | :param tab: list with comparable elements 40 | :returns: lists left and right. left[j] = the number of 41 | i tab[j]. 42 | right[i] = the number of i tab[j]. 43 | :complexity: `O(n log n)` 44 | """ 45 | n = len(tab) 46 | left = [0] * n 47 | right = [0] * n 48 | tmp = [None] * n # temporary table 49 | rank = list(range(n)) 50 | _merge_sort(tab, tmp, rank, left, right, 0, n) 51 | return left, right 52 | # snip} 53 | -------------------------------------------------------------------------------- /tryalgo/levenshtein.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Levenshtein edit distance 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | def levenshtein(x, y): 12 | """Levenshtein edit distance 13 | 14 | :param x: 15 | :param y: strings 16 | :returns: distance 17 | :complexity: `O(|x|*|y|)` 18 | """ 19 | n = len(x) 20 | m = len(y) 21 | # Create the table A 22 | # Row 0 and column 0 are initialized as required 23 | # The remaining entries will be overwritten during the computation 24 | A = [[i + j for j in range(m + 1)] for i in range(n + 1)] 25 | for i in range(n): 26 | for j in range(m): 27 | A[i + 1][j + 1] = min(A[i][j + 1] + 1, # insert 28 | A[i + 1][j] + 1, # delete 29 | A[i][j] + int(x[i] != y[j])) # subst. 30 | return A[n][m] 31 | # snip} 32 | -------------------------------------------------------------------------------- /tryalgo/longest_common_subsequence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Longest increasing subsequence 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | # pylint: disable=bad-whitespace 9 | 10 | 11 | # snip{ 12 | def longest_common_subsequence(x, y): 13 | """Longest common subsequence 14 | 15 | Dynamic programming 16 | 17 | :param x: 18 | :param y: x, y are lists or strings 19 | :returns: longest common subsequence in form of a string 20 | :complexity: `O(|x|*|y|)` 21 | """ 22 | n = len(x) 23 | m = len(y) 24 | 25 | # -- compute optimal length 26 | A = [[0 for j in range(m + 1)] for i in range(n + 1)] 27 | for i in range(n): 28 | for j in range(m): 29 | if x[i] == y[j]: 30 | A[i + 1][j + 1] = A[i][j] + 1 31 | else: 32 | A[i + 1][j + 1] = max(A[i][j + 1], A[i + 1][j]) 33 | 34 | # -- extract solution in reverse order 35 | sol = [] 36 | i, j = n, m 37 | while A[i][j] > 0: 38 | if A[i][j] == A[i - 1][j]: 39 | i -= 1 40 | elif A[i][j] == A[i][j - 1]: 41 | j -= 1 42 | else: 43 | i -= 1 44 | j -= 1 45 | sol.append(x[i]) 46 | return ''.join(sol[::-1]) # reverse the list to obtain the solution 47 | # snip} 48 | -------------------------------------------------------------------------------- /tryalgo/longest_increasing_subsequence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Longest increasing subsequence 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | # snip{ 10 | from bisect import bisect_left 11 | 12 | 13 | def longest_increasing_subsequence(x): 14 | """Longest increasing subsequence 15 | 16 | :param x: sequence 17 | :returns: longest strictly increasing subsequence y 18 | :complexity: `O(|x|*log(|y|))` 19 | """ 20 | n = len(x) 21 | p = [None] * n 22 | h = [None] 23 | b = [float('-inf')] # - infinity 24 | for i in range(n): 25 | if x[i] > b[-1]: 26 | p[i] = h[-1] 27 | h.append(i) 28 | b.append(x[i]) 29 | else: 30 | # -- binary search: b[k - 1] < x[i] <= b[k] 31 | k = bisect_left(b, x[i]) 32 | h[k] = i 33 | b[k] = x[i] 34 | p[i] = h[k - 1] 35 | # extract solution in reverse order 36 | q = h[-1] 37 | s = [] 38 | while q is not None: 39 | s.append(x[q]) 40 | q = p[q] 41 | return s[::-1] # reverse the list to obtain the solution 42 | # snip} 43 | -------------------------------------------------------------------------------- /tryalgo/lowest_common_ancestor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Lowest common ancestor 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | # http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html 9 | 10 | from tryalgo.range_minimum_query import RangeMinQuery 11 | 12 | 13 | def log2floor(n): 14 | """ log of n in base 2 rounded down """ 15 | k = -1 16 | assert n >= 0 17 | while n: 18 | k += 1 19 | n >>= 1 20 | return k 21 | 22 | 23 | def log2ceil(n): 24 | """ log of n in base 2 rounded up """ 25 | return log2floor(n - 1) + 1 26 | 27 | 28 | # snip{ lowest_common_ancestor_by_shortcuts 29 | # pylint: disable=too-few-public-methods 30 | class LowestCommonAncestorShortcuts: 31 | """Lowest common ancestor data structure using shortcuts to ancestors 32 | """ 33 | def __init__(self, prec): 34 | """builds the structure from a given tree 35 | 36 | :param prec: father for every node, with prec[0] = 0 37 | :assumes: prec[node] < node 38 | :complexity: O(n log n), with n = len(nodes) 39 | """ 40 | n = len(prec) 41 | self.level = [None] * n # build levels 42 | self.level[0] = 0 43 | for u in range(1, n): 44 | self.level[u] = 1 + self.level[prec[u]] 45 | depth = log2ceil(max(self.level[u] for u in range(n))) + 1 46 | self.anc = [[0] * n for _ in range(depth)] 47 | for u in range(n): 48 | self.anc[0][u] = prec[u] 49 | for k in range(1, depth): 50 | for u in range(n): 51 | self.anc[k][u] = self.anc[k - 1][self.anc[k - 1][u]] 52 | 53 | def query(self, u, v): 54 | """:returns: the lowest common ancestor of u and v 55 | :complexity: O(log n) 56 | """ 57 | # -- assume w.l.o.g. that v is not higher than u in the tree 58 | if self.level[u] > self.level[v]: 59 | u, v = v, u 60 | # -- put v at the same level as u 61 | depth = len(self.anc) 62 | for k in range(depth-1, -1, -1): 63 | if self.level[u] <= self.level[v] - (1 << k): 64 | v = self.anc[k][v] 65 | assert self.level[u] == self.level[v] 66 | if u == v: 67 | return u 68 | # -- climb until the lowest common ancestor 69 | for k in range(depth-1, -1, -1): 70 | if self.anc[k][u] != self.anc[k][v]: 71 | u = self.anc[k][u] 72 | v = self.anc[k][v] 73 | assert self.anc[0][u] == self.anc[0][v] 74 | return self.anc[0][u] 75 | # snip} 76 | 77 | 78 | # snip{ lowest_common_ancestor_by_rmq 79 | class LowestCommonAncestorRMQ: 80 | """Lowest common ancestor data structure using a reduction to 81 | range minimum query 82 | """ 83 | def __init__(self, graph): 84 | """builds the structure from a given tree 85 | 86 | :param graph: adjacency matrix of a tree 87 | :complexity: O(n log n), with n = len(graph) 88 | """ 89 | n = len(graph) 90 | dfs_trace = [] 91 | self.last = [None] * n 92 | to_visit = [(0, 0, None)] # node 0 is root 93 | succ = [0] * n 94 | while to_visit: 95 | level, node, father = to_visit[-1] 96 | self.last[node] = len(dfs_trace) 97 | dfs_trace.append((level, node)) 98 | if succ[node] < len(graph[node]) and \ 99 | graph[node][succ[node]] == father: 100 | succ[node] += 1 101 | if succ[node] == len(graph[node]): 102 | to_visit.pop() 103 | else: 104 | neighbor = graph[node][succ[node]] 105 | succ[node] += 1 106 | to_visit.append((level + 1, neighbor, node)) 107 | self.rmq = RangeMinQuery(dfs_trace, (float('inf'), None)) 108 | 109 | def query(self, u, v): 110 | """:returns: the lowest common ancestor of u and v 111 | :complexity: O(log n) 112 | """ 113 | lu = self.last[u] 114 | lv = self.last[v] 115 | if lu > lv: 116 | lu, lv = lv, lu 117 | return self.rmq.range_min(lu, lv + 1)[1] 118 | # snip} 119 | -------------------------------------------------------------------------------- /tryalgo/majority.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Majority 5 | 6 | jill-jenn vie et christoph durr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=unused-variable 12 | def majority(L): 13 | # snip} 14 | """Majority 15 | 16 | :param L: list of elements 17 | :returns: element that appears most in L, 18 | tie breaking with smallest element 19 | :complexity: :math:`O(nk)` in average, 20 | where n = len(L) and k = max(w for w in L) 21 | :math:`O(n^2k)` in worst case due to the use of a dictionary 22 | """ 23 | assert L # majority is undefined on the empty set 24 | # snip{ 25 | count = {} 26 | for word in L: 27 | if word in count: 28 | count[word] += 1 29 | else: 30 | count[word] = 1 31 | # Using min() like this gives the first word with 32 | # maximal count "for free" 33 | val_1st_max, arg_1st_max = min((-count[word], word) for word in count) 34 | return arg_1st_max 35 | # snip} 36 | -------------------------------------------------------------------------------- /tryalgo/manacher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Longest palindrome in a string by Manacher 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | 8 | http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html 9 | 10 | Algorithme de Manacher 11 | problème: plus long palindrome 12 | entrée: chaîne s 13 | sortie: indices i, j tel que s[i:j] est un palindrome 14 | et que j-i est maximal et i maximal 15 | complexité: temps linéaire 16 | 17 | tous les indices réfèrent à une chaîne fictive t 18 | de la forme "^#a#b#a#a#$" si s="abaa" 19 | invariant: pour chaque préfixe vu 20 | on maintient un palindrome centré en c et de bord droit r 21 | qui maximise r 22 | ainsi que p[i] = plus grand rayon d'un palindrome centré en i 23 | """ 24 | 25 | 26 | # snip{ 27 | def manacher(s): 28 | """Longest palindrome in a string by Manacher 29 | 30 | :param s: string 31 | :requires: s is not empty 32 | :returns: i,j such that s[i:j] is the longest palindrome in s 33 | :complexity: O(len(s)) 34 | """ 35 | assert set.isdisjoint({'$', '^', '#'}, s) # Forbidden letters 36 | if s == "": 37 | return (0, 0) # returns indices of empty string 38 | t = "^#" + "#".join(s) + "#$" 39 | c = 1 40 | d = 1 41 | p = [0] * len(t) 42 | for i in range(2, len(t) - 1): 43 | # -- reflect index i with respect to c 44 | mirror = 2 * c - i # = c - (i-c) 45 | p[i] = max(0, min(d - i, p[mirror])) 46 | # -- grow palindrome centered in i 47 | while t[i + 1 + p[i]] == t[i - 1 - p[i]]: 48 | p[i] += 1 49 | # -- adjust center if necessary 50 | if i + p[i] > d: 51 | c = i 52 | d = i + p[i] 53 | (k, i) = max((p[i], i) for i in range(1, len(t) - 1)) 54 | return ((i - k) // 2, (i + k) // 2) # extract solution 55 | # snip} 56 | -------------------------------------------------------------------------------- /tryalgo/matrix_chain_mult.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Matrix chain multiplication 5 | multiplication de matrices 6 | 7 | jill-jenn vie et christoph durr - 2014-2018 8 | """ 9 | 10 | 11 | # snip{ 12 | def matrix_mult_opt_order(M): 13 | """Matrix chain multiplication optimal order 14 | 15 | :param M: list of matrices 16 | :returns: matrices opt, arg, such that opt[i][j] is the optimal number of 17 | operations to compute M[i] * ... * M[j] when done in the order 18 | (M[i] * ... * M[k]) * (M[k + 1] * ... * M[j]) for k = arg[i][j] 19 | :complexity: :math:`O(n^3)` 20 | """ 21 | n = len(M) 22 | r = [len(Mi) for Mi in M] 23 | c = [len(Mi[0]) for Mi in M] 24 | opt = [[0 for j in range(n)] for i in range(n)] 25 | arg = [[None for j in range(n)] for i in range(n)] 26 | for j_i in range(1, n): # loop on i, j of increasing j - i = j_i 27 | for i in range(n - j_i): 28 | j = i + j_i 29 | opt[i][j] = float('inf') 30 | for k in range(i, j): 31 | alt = opt[i][k] + opt[k + 1][j] + r[i] * c[k] * c[j] 32 | if opt[i][j] > alt: 33 | opt[i][j] = alt 34 | arg[i][j] = k 35 | return opt, arg 36 | 37 | 38 | # pylint: disable=unused-variable 39 | def matrix_chain_mult(M): 40 | """Matrix chain multiplication 41 | 42 | :param M: list of matrices 43 | :returns: M[0] * ... * M[-1], computed in time optimal order 44 | :complexity: whatever is needed by the multiplications 45 | """ 46 | opt, arg = matrix_mult_opt_order(M) 47 | return _apply_order(M, arg, 0, len(M)-1) 48 | 49 | 50 | def _apply_order(M, arg, i, j): 51 | # --- multiply matrices from M[i] to M[j] included 52 | if i == j: 53 | return M[i] 54 | k = arg[i][j] # --- follow placement of parentheses 55 | A = _apply_order(M, arg, i, k) 56 | B = _apply_order(M, arg, k + 1, j) 57 | row_A = range(len(A)) 58 | row_B = range(len(B)) 59 | col_B = range(len(B[0])) 60 | return [[sum(A[a][b] * B[b][c] for b in row_B) 61 | for c in col_B] for a in row_A] 62 | # snip} 63 | -------------------------------------------------------------------------------- /tryalgo/max_interval_intersec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Sweepline algorithm technique 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=bad-whitespace 12 | def max_interval_intersec(S): 13 | """determine a value that is contained in a largest number 14 | of given intervals 15 | 16 | :param S: list of half open intervals 17 | :complexity: O(n log n), where n = len(S) 18 | """ 19 | B = ([(left, +1) for left, right in S] + 20 | [(right, -1) for left, right in S]) 21 | B.sort() 22 | c = 0 23 | best = (c, None) 24 | for x, d in B: 25 | c += d 26 | if best[0] < c: 27 | best = (c, x) 28 | return best 29 | # snip} 30 | -------------------------------------------------------------------------------- /tryalgo/merge_ordered_lists.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Merge two ordered lists 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def merge(x, y): 12 | """Merge two ordered lists 13 | 14 | :param x: 15 | :param y: x, y are nondecreasing ordered lists 16 | :returns: union of x and y in order 17 | :complexity: linear 18 | """ 19 | z = [] 20 | i = 0 21 | j = 0 22 | while i < len(x) or j < len(y): 23 | if j == len(y) or i < len(x) and x[i] <= y[j]: # priority on x 24 | z.append(x[i]) 25 | i += 1 26 | else: 27 | z.append(y[j]) # now switch to y 28 | j += 1 29 | return z 30 | # snip} 31 | -------------------------------------------------------------------------------- /tryalgo/min_mean_cycle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Minimum mean cycle by Karp 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=too-many-locals 12 | def min_mean_cycle(graph, weight, start=0): 13 | """Minimum mean cycle by Karp 14 | 15 | :param graph: directed graph in listlist or listdict format 16 | :param weight: in matrix format or same listdict graph 17 | :param int start: vertex that should be contained in cycle 18 | :returns: cycle as vertex list, average arc weights 19 | or None if there is no cycle from start 20 | :complexity: `O(|V|*|E|)` 21 | """ 22 | INF = float('inf') 23 | n = len(graph) # compute distances 24 | dist = [[INF] * n] 25 | prec = [[None] * n] 26 | dist[0][start] = 0 27 | for ell in range(1, n + 1): 28 | dist.append([INF] * n) 29 | prec.append([None] * n) 30 | for node in range(n): 31 | for neighbor in graph[node]: 32 | alt = dist[ell - 1][node] + weight[node][neighbor] 33 | if alt < dist[ell][neighbor]: 34 | dist[ell][neighbor] = alt 35 | prec[ell][neighbor] = node 36 | # -- find the optimal value 37 | valmin = INF 38 | argmin = None 39 | for node in range(n): 40 | valmax = -INF 41 | argmax = None 42 | for k in range(n): 43 | alt = (dist[n][node] - dist[k][node]) / float(n - k) 44 | # do not divide by float(n-k) => cycle of minimal total weight 45 | if alt >= valmax: # with >= we get simple cycles 46 | valmax = alt 47 | argmax = k 48 | if argmax is not None and valmax < valmin: 49 | valmin = valmax 50 | argmin = (node, argmax) 51 | # -- extract cycle 52 | if valmin == INF: # -- there is no cycle 53 | return None 54 | C = [] 55 | node, k = argmin 56 | for l in range(n, k, -1): 57 | C.append(node) 58 | node = prec[l][node] 59 | return C[::-1], valmin 60 | # snip} 61 | -------------------------------------------------------------------------------- /tryalgo/next_permutation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Next permutation 5 | prochaine permuation 6 | 7 | jill-jênn vie et christoph dürr - 2014-2020 8 | """ 9 | 10 | from sys import argv 11 | import sys 12 | from tryalgo.our_std import readint, readstr 13 | 14 | 15 | # snip{ 16 | # pylint: disable=redefined-outer-name 17 | def next_permutation(tab): 18 | """find the next permutation of tab in the lexicographical order 19 | 20 | :param tab: table with n elements from an ordered set 21 | :modifies: table to next permutation 22 | :returns: False if permutation is already lexicographical maximal 23 | :complexity: O(n) 24 | """ 25 | n = len(tab) 26 | pivot = None # find pivot 27 | for i in range(n - 1): 28 | if tab[i] < tab[i + 1]: 29 | pivot = i 30 | if pivot is None: # tab is already the last perm. 31 | return False 32 | for i in range(pivot + 1, n): # find the element to swap 33 | if tab[i] > tab[pivot]: 34 | swap = i 35 | tab[swap], tab[pivot] = tab[pivot], tab[swap] 36 | i = pivot + 1 37 | j = n - 1 # invert suffix 38 | while i < j: 39 | tab[i], tab[j] = tab[j], tab[i] 40 | i += 1 41 | j -= 1 42 | return True 43 | # snip} 44 | 45 | 46 | # snip{ word_addition 47 | # pylint: disable=redefined-outer-name 48 | def convert(word, ass): 49 | """ 50 | solves a cryptogram in the style SEND + MORE = MONEY 51 | """ 52 | retval = 0 53 | for x in word: 54 | retval = 10 * retval + ass[x] 55 | return retval 56 | 57 | 58 | # pylint: disable=redefined-outer-name 59 | def solve_word_addition(S): # returns number of solutions 60 | """returns number of solutions""" 61 | n = len(S) 62 | letters = sorted(list(set(''.join(S)))) 63 | not_zero = '' # letters that cannot be 0 64 | for word in S: 65 | not_zero += word[0] 66 | tab = ['@'] * (10 - len(letters)) + letters # minimal lex permutation 67 | count = 0 68 | while True: 69 | ass = {tab[i]: i for i in range(10)} # dict = associative array 70 | if tab[0] not in not_zero: 71 | difference = -convert(S[n - 1], ass) # do the addition 72 | for word in S[:n - 1]: 73 | difference += convert(word, ass) 74 | if difference == 0: # does it add up? 75 | count += 1 76 | if not next_permutation(tab): 77 | break 78 | return count 79 | # snip} 80 | 81 | 82 | if __name__ == "__main__": 83 | if len(argv) > 1: 84 | L = argv[1:] 85 | if len(L) == 1: 86 | L = list(L[0]) 87 | while True: 88 | print(L) 89 | if not next_permutation(L): 90 | sys.exit(0) 91 | 92 | n = readint() 93 | S = [readstr() for _ in range(n)] 94 | print(solve_word_addition(S)) 95 | -------------------------------------------------------------------------------- /tryalgo/our_heap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | A min heap 5 | 6 | christoph dürr et jill-jênn vie - 2015-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | class OurHeap: 12 | """ min heap 13 | 14 | * heap: is the actual heap, heap[1] = index of the smallest element 15 | * rank: inverse of heap with rank[x]=i iff heap[i]=x 16 | * n: size of the heap 17 | 18 | :complexity: init O(n log n), len O(1), 19 | other operations O(log n) in expectation 20 | and O(n) in worst case, due to the usage of a dictionary 21 | """ 22 | def __init__(self, items): 23 | self.heap = [None] # index 0 will be ignored 24 | self.rank = {} 25 | for x in items: 26 | self.push(x) 27 | 28 | def __len__(self): 29 | return len(self.heap) - 1 30 | 31 | def push(self, x): 32 | """Insert new element x in the heap. 33 | Assumption: x is not already in the heap""" 34 | assert x not in self.rank 35 | i = len(self.heap) 36 | self.heap.append(x) # add a new leaf 37 | self.rank[x] = i 38 | self.up(i) # maintain heap order 39 | 40 | def pop(self): 41 | """Remove and return smallest element""" 42 | root = self.heap[1] 43 | del self.rank[root] 44 | x = self.heap.pop() # remove last leaf 45 | if self: # if heap is not empty 46 | self.heap[1] = x # move the last leaf 47 | self.rank[x] = 1 # to the root 48 | self.down(1) # maintain heap order 49 | return root 50 | # snip} 51 | 52 | # snip{ our_heap_up_down 53 | def up(self, i): 54 | """The value of heap[i] has decreased. Maintain heap invariant.""" 55 | x = self.heap[i] 56 | while i > 1 and x < self.heap[i // 2]: 57 | self.heap[i] = self.heap[i // 2] 58 | self.rank[self.heap[i // 2]] = i 59 | i //= 2 60 | self.heap[i] = x # insertion index found 61 | self.rank[x] = i 62 | 63 | def down(self, i): 64 | """the value of heap[i] has increased. Maintain heap invariant.""" 65 | x = self.heap[i] 66 | n = len(self.heap) 67 | while True: 68 | left = 2 * i # climb down the tree 69 | right = left + 1 70 | if (right < n and self.heap[right] < x and 71 | self.heap[right] < self.heap[left]): 72 | self.heap[i] = self.heap[right] 73 | self.rank[self.heap[right]] = i # move right child up 74 | i = right 75 | elif left < n and self.heap[left] < x: 76 | self.heap[i] = self.heap[left] 77 | self.rank[self.heap[left]] = i # move left child up 78 | i = left 79 | else: 80 | self.heap[i] = x # insertion index found 81 | self.rank[x] = i 82 | return 83 | 84 | def update(self, old, new): 85 | """Replace an element in the heap 86 | """ 87 | i = self.rank[old] # change value at index i 88 | del self.rank[old] 89 | self.heap[i] = new 90 | self.rank[new] = i 91 | if old < new: # maintain heap order 92 | self.down(i) 93 | else: 94 | self.up(i) 95 | # snip} 96 | -------------------------------------------------------------------------------- /tryalgo/our_queue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | A FIFO queue 5 | 6 | christoph dürr - jill-jênn vie - 2015-2019 7 | """ 8 | 9 | 10 | # snip{ OurQueue 11 | # pylint: disable=missing-docstring 12 | class OurQueue: 13 | """A FIFO queue 14 | 15 | Complexity: 16 | all operators in amortized constant time, 17 | except __str__ which is linear 18 | """ 19 | def __init__(self): 20 | self.in_stack = [] # tail 21 | self.out_stack = [] # head 22 | 23 | def __len__(self): 24 | return len(self.in_stack) + len(self.out_stack) 25 | 26 | def push(self, obj): 27 | self.in_stack.append(obj) 28 | 29 | def pop(self): 30 | if not self.out_stack: # head is empty 31 | # Note that the in_stack is assigned to the out_stack 32 | # in reverse order. This is because the in_stack stores 33 | # elements from oldest to newest whereas the out_stack 34 | # needs to pop elements from newest to oldest 35 | self.out_stack = self.in_stack[::-1] 36 | self.in_stack = [] 37 | return self.out_stack.pop() 38 | # snip} 39 | 40 | def __str__(self): 41 | return str(self.out_stack[::-1] + self.in_stack) 42 | -------------------------------------------------------------------------------- /tryalgo/our_std.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf8 3 | """\ 4 | Our Standards 5 | 6 | Jill-Jênn Vie et Christoph Dürr - 2020 7 | """ 8 | from sys import stdin 9 | 10 | 11 | def readint(): 12 | """ 13 | function to read an integer from stdin 14 | """ 15 | return int(stdin.readline()) 16 | 17 | 18 | def readstr(): 19 | """ 20 | function to read a string from stdin 21 | """ 22 | return stdin.readline().strip() 23 | 24 | 25 | def readarray(typ): 26 | """ 27 | function to read an array 28 | """ 29 | return list(map(typ, stdin.readline().split())) 30 | 31 | 32 | # pylint: disable=redefined-outer-name 33 | def readmatrix(n): 34 | """ 35 | function to read a matrix 36 | """ 37 | M = [] 38 | for _ in range(n): 39 | row = readarray(int) 40 | assert len(row) == n 41 | M.append(row) 42 | return M 43 | -------------------------------------------------------------------------------- /tryalgo/pareto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Pareto sets 5 | 6 | jill-jenn vie et christoph durr - 2022 7 | """ 8 | from tryalgo.fenwick import FenwickMin 9 | 10 | def pareto2d(points): 11 | """ Compute the Pareto set of a given set of points in 2 dimensions 12 | 13 | :param points: list of tuples with the coordinates of the points. Can be floating point coordinates. 14 | :modifies: points will be sorted 15 | :returns: a list of non-dominated points 16 | :complexity: $O(n\\log n)$ 17 | """ 18 | pareto = [] 19 | points.sort() 20 | for p in points: 21 | x, y = p 22 | if pareto == [] or y < pareto[-1][1] or p == pareto[-1]: 23 | pareto.append(p) 24 | return pareto 25 | 26 | 27 | def pareto3d(points): 28 | """ Compute the Pareto set of a given set of points in 2 dimensions 29 | 30 | :param points: list of tuples with the coordinates of the points. Can be floating point coordinates. 31 | :modifies: points will be sorted 32 | :returns: a list of non-dominated points 33 | :complexity: $O(n\\log n)$ 34 | """ 35 | # compute the ranks, it is ok to have multple y-values in the list 36 | y_values = [y for x,y,z in points] 37 | y_values.sort() 38 | rank = {} 39 | for i, yi in enumerate(y_values): 40 | rank[yi] = i 41 | n = len(points) 42 | points.sort() # sort by rank in first competition 43 | pareto = [] 44 | R = FenwickMin(n) 45 | for p in points: 46 | x, y, z = p 47 | i = rank[y] 48 | if pareto == [] or R.prefixMin(i) > z or p == pareto[-1]: 49 | pareto.append(p) 50 | R.update(i, z) 51 | return pareto 52 | -------------------------------------------------------------------------------- /tryalgo/partition_refinement.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Partition refinement 5 | 6 | christoph dürr - 2016-2019 7 | 8 | log: 10/11/2016 modified to preserve class order after refinement 9 | 15/11/2016 this was nonsense, moved back 10 | """ 11 | 12 | 13 | __all__ = ["PartitionRefinement"] 14 | 15 | 16 | # pylint: disable=missing-docstring 17 | class DoubleLinkedListItem: 18 | """Item of a circular double linked list 19 | """ 20 | 21 | def __init__(self, anchor=None): 22 | """Create a new item to be inserted before item anchor. 23 | if anchor is None: create a single item circular double linked list 24 | """ 25 | if anchor: 26 | self.insert(anchor) 27 | else: 28 | self.prec = self 29 | self.succ = self 30 | 31 | def remove(self): 32 | self.prec.succ = self.succ 33 | self.succ.prec = self.prec 34 | 35 | def insert(self, anchor): 36 | """insert list item before anchor 37 | """ 38 | self.prec = anchor.prec # point to neighbors 39 | self.succ = anchor 40 | self.succ.prec = self # make neighbors point to item 41 | self.prec.succ = self 42 | 43 | def __iter__(self): 44 | """iterate trough circular list. 45 | warning: might end stuck in an infinite loop if chaining is not valid 46 | """ 47 | curr = self 48 | yield self 49 | while curr.succ != self: 50 | curr = curr.succ 51 | yield curr 52 | 53 | 54 | class PartitionClass(DoubleLinkedListItem): 55 | """A partition is a list of classes 56 | """ 57 | 58 | def __init__(self, anchor=None): 59 | DoubleLinkedListItem.__init__(self, anchor) 60 | self.items = None # empty list 61 | self.split = None # reference to split class 62 | 63 | def append(self, item): 64 | """add item to the end of the item list 65 | """ 66 | if not self.items: # was list empty ? 67 | self.items = item # then this is the new head 68 | item.insert(self.items) 69 | 70 | 71 | class PartitionItem(DoubleLinkedListItem): 72 | """A class is a list of items 73 | """ 74 | 75 | def __init__(self, val, theclass): 76 | DoubleLinkedListItem.__init__(self) 77 | self.val = val 78 | self.theclass = theclass 79 | theclass.append(self) 80 | 81 | def remove(self): 82 | """remove item from its class 83 | """ 84 | DoubleLinkedListItem.remove(self) # remove from double linked list 85 | if self.succ is self: # list was a singleton 86 | self.theclass.items = None # class is empty 87 | elif self.theclass.items is self: # oups we removed the head 88 | self.theclass.items = self.succ # head is now successor 89 | 90 | 91 | class PartitionRefinement: 92 | """This data structure implements an order preserving 93 | partition with refinements. 94 | """ 95 | 96 | def __init__(self, n): 97 | """Start with the partition consisting of the unique class {0,1,..,n-1} 98 | complexity: O(n) both in time and space 99 | """ 100 | c = PartitionClass() # initially there is a single class of size n 101 | self.classes = c # reference to first class in class list 102 | self.items = [PartitionItem(i, c) for i in range(n)] # value-ordered 103 | 104 | def refine(self, pivot): 105 | """Split every class C in the partition into C intersection pivot 106 | and C setminus pivot complexity: linear in size of pivot 107 | """ 108 | has_split = [] # remember which classes split 109 | for i in pivot: 110 | if 0 <= i < len(self.items): # ignore if outside of domain 111 | x = self.items[i] 112 | c = x.theclass # c = class of x 113 | if not c.split: # possibly create new split class 114 | c.split = PartitionClass(c) 115 | if self.classes is c: 116 | self.classes = c.split # always point to 1st class 117 | has_split.append(c) 118 | x.remove() # remove from its class 119 | x.theclass = c.split 120 | c.split.append(x) # append to the split class 121 | for c in has_split: # clean information about split classes 122 | c.split = None 123 | if not c.items: # delete class if it became empty 124 | c.remove() 125 | del c 126 | 127 | def tolist(self): 128 | """produce a list representation of the partition 129 | """ 130 | return [[x.val for x in theclass.items] for theclass in self.classes] 131 | 132 | def order(self): 133 | """Produce a flatten list of the partition, ordered by classes 134 | """ 135 | return [x.val for theclass in self.classes for x in theclass.items] 136 | -------------------------------------------------------------------------------- /tryalgo/permutation_rank.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Permutation rank 5 | 6 | christoph dürr - 2016-2019 7 | """ 8 | 9 | 10 | # pylint: disable=line-too-long 11 | def permutation_rank(p): 12 | """Given a permutation of {0,..,n-1} find its rank according to 13 | lexicographical order 14 | 15 | :param p: list of length n containing all integers from 0 to n-1 16 | :returns: rank between 0 and n! -1 17 | :beware: computation with big numbers 18 | :complexity: `O(n^2)` 19 | """ 20 | n = len(p) 21 | fact = 1 # compute (n-1) factorial 22 | for i in range(2, n): 23 | fact *= i 24 | r = 0 # compute rank of p 25 | digits = list(range(n)) # all yet unused digits 26 | for i in range(n-1): # for all digits except last one 27 | q = digits.index(p[i]) 28 | r += fact * q 29 | del digits[q] # remove this digit p[i] 30 | fact //= (n - 1 - i) # weight of next digit 31 | return r 32 | 33 | 34 | def rank_permutation(r, n): 35 | """Given r and n find the permutation of {0,..,n-1} with rank according to 36 | lexicographical order equal to r 37 | 38 | :param r n: integers with 0 ≤ r < n! 39 | :returns: permutation p as a list of n integers 40 | :beware: computation with big numbers 41 | :complexity: `O(n^2)` 42 | """ 43 | fact = 1 # compute (n-1) factorial 44 | for i in range(2, n): 45 | fact *= i 46 | digits = list(range(n)) # all yet unused digits 47 | p = [] # build permutation 48 | for i in range(n): 49 | q = r // fact # by decomposing r = q * fact + rest 50 | r %= fact 51 | p.append(digits[q]) 52 | del digits[q] # remove digit at position q 53 | if i != n - 1: 54 | fact //= (n - 1 - i) # weight of next digit 55 | return p 56 | -------------------------------------------------------------------------------- /tryalgo/polygon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Area of polygone 5 | mesures polygone 6 | 7 | jill-jenn vie et christoph durr - 2014-2018 8 | """ 9 | 10 | 11 | from tryalgo.range_minimum_query import RangeMinQuery 12 | 13 | 14 | # snip{ area 15 | def area(p): 16 | """Area of a polygone 17 | 18 | :param p: list of the points taken in any orientation, 19 | p[0] can differ from p[-1] 20 | :returns: area 21 | :complexity: linear 22 | """ 23 | A = 0 24 | for i, _ in enumerate(p): 25 | A += p[i - 1][0] * p[i][1] - p[i][0] * p[i - 1][1] 26 | return A / 2. 27 | # snip} 28 | 29 | 30 | # snip{ is_simple 31 | # pylint: disable=too-many-locals 32 | def is_simple(polygon): 33 | """Test if a rectilinear polygon is is_simple 34 | 35 | :param polygon: list of points as (x,y) pairs along the closed polygon 36 | :returns: True if the segements do not intersect 37 | :complexity: O(n log n) for n=len(polygon) 38 | """ 39 | n = len(polygon) 40 | order = list(range(n)) 41 | order.sort(key=lambda i: polygon[i]) # lexicographic order 42 | rank_to_y = list(set(p[1] for p in polygon)) 43 | rank_to_y.sort() 44 | y_to_rank = {y: rank for rank, y in enumerate(rank_to_y)} 45 | S = RangeMinQuery([0] * len(rank_to_y)) # sweep structure 46 | last_y = None 47 | for i in order: 48 | x, y = polygon[i] 49 | rank = y_to_rank[y] 50 | # -- type of point 51 | right_x = max(polygon[i - 1][0], polygon[(i + 1) % n][0]) 52 | left = x < right_x 53 | below_y = min(polygon[i - 1][1], polygon[(i + 1) % n][1]) 54 | high = y > below_y 55 | if left: # y does not need to be in S yet 56 | if S[rank]: 57 | return False # two horizontal segments intersect 58 | S[rank] = -1 # add y to S 59 | else: 60 | S[rank] = 0 # remove y from S 61 | if high: 62 | lo = y_to_rank[below_y] # check S between [lo + 1, rank - 1] 63 | if (below_y != last_y or last_y == y or 64 | rank - lo >= 2 and S.range_min(lo + 1, rank)): 65 | return False # horiz. & vert. segments intersect 66 | last_y = y # remember for next iteration 67 | return True 68 | # snip} 69 | 70 | 71 | # easter egg 72 | 73 | # \section*{Gâteau aux carottes} 74 | # (traduit de \textsc{Joy of Cooking}, de Rombauer, Becker et Becker) 75 | 76 | # Aux États-Unis le gâteau aux carottes est maintenant standard comme 77 | # la tarte aux pommes. Le nôtre est moyennement épicé, moelleux mais 78 | # pas lourd, et assez goûteux pour pouvoir être dégusté sans glaçage. 79 | 80 | # Ayez tous les ingrédients à votre disposition et à température 81 | # ambiante, 68 à 70 degrés. Préchauffez le four à 350 degrées. 82 | # Graissez et farinez deux moules ronds de 9 x 2 pouces ou deux moules 83 | # 8 x 8 pouces ou un moule 13 x 9 pouces ou déposez sur le fond un papier 84 | # ciré ou transparent. 85 | 86 | # Mélangez ensemble fermement dans un grand bol 87 | # \begin{verse} 88 | # $1 \frac 13$ tasses de farine\\ 89 | # $1$ tasse de sucre\\ 90 | # $1 \frac 12$ cuillères à café de levure\\ 91 | # $1$ cuillère à café de poudre à lever\\ 92 | # $1$ cuillère à café de cannelle\\ 93 | # $\frac12$ cuillère à café de clous de girofle\\ 94 | # $\frac12$ cuillère à café de noix de muscade fraîchement moulue 95 | # \\ 96 | # $\frac12$ cuillère à café de tout-épices\footnote{\emph{Allspice} ou 97 | # \emph{poivre de la Jamaïque} est une baie petite, ronde et d'un brun 98 | # rougeâtre, qui pousse sur un grand arbre vert toute l'année, dont les 99 | # plus fins se trouvent sur l'île de Jamaïque. Son goût ressemble 100 | # principalement aux clous de girofle avec une pincée de cannelle, noix 101 | # de muscade et de gingembre.} moulus 102 | # \\ 103 | # $\frac12$ cuillère à café de sel 104 | # \end{verse} 105 | # Ajoutez et mélangez avec une cuillère en caoutchouc ou battez à faible 106 | # vitesse : 107 | # \begin{verse} 108 | # $\frac23$ tasses d'huile végétale\\ 109 | # $3$ grands oeufs 110 | # \end{verse} 111 | # Incorporez~: 112 | # \begin{verse} 113 | # $1\frac12$ tasses de carottes épluchées et finement rapées\\ 114 | # $1$ tasse de noix finement hachée\\ 115 | # $1$ tasse de raisins secs jaunes (optionnel)\\ 116 | # $\frac 12$ tasse d'ananas écrasé, légèrement égoutté (optionnel) 117 | # \end{verse} 118 | # Versez la pâte dans le(s) moule(s) et étalez équitablement. Faites 119 | # cuire jusqu'à ce qu'un cure-dent en bois percé dans le centre ressorte 120 | # propre, 25 à 30 minutes dans des moules rectangulaires ou ronds, 35 121 | # minutes dans un moule 13 x 9 pouces. Laissez refroidir le(s) moule(s) 122 | # sur une grille pendant 10 minutes. Glissez un couteau autour du 123 | # gâteau pour le détacher du moule (des moules). Retournez-le et 124 | # retirez le papier s'il y en a. Laissez refroidir avec la bonne face vers 125 | # le haut sur une grille. Remplissez et recouvrez avec du \emph{glaçage de 126 | # fromage à tartiner}, du \emph{glaçage blanc rapide}, ou du 127 | # \emph{glaçage rapide brun au beurre}. 128 | 129 | # \begin{tabular}{|rcl|}\hline 130 | # $x$ degrées Fahrenheit &=&$5(x-32)/9$ degrées Celsius\\ 131 | # 1 pouce&=& 2,54 centimètres\\ 132 | # 1 tasse &=& 0,236 litre\\\hline 133 | # \end{tabular} 134 | -------------------------------------------------------------------------------- /tryalgo/predictive_text.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Predictive text for mobile phones 5 | 6 | jill-jenn vie et christoph durr and louis abraham - 2014-2019 7 | """ 8 | 9 | __all__ = ["predictive_text", "propose"] 10 | 11 | # snip{ 12 | t9 = "22233344455566677778889999" 13 | # abcdefghijklmnopqrstuvwxyz mapping on the phone 14 | 15 | 16 | def letter_to_digit(x): 17 | """:returns: the digit correspondence for letter x""" 18 | assert 'a' <= x <= 'z' 19 | return t9[ord(x) - ord('a')] 20 | 21 | 22 | def code_word(word): 23 | """:returns: the digit correspondence for given word""" 24 | return ''.join(map(letter_to_digit, word)) 25 | 26 | 27 | def predictive_text(dic): 28 | """Predictive text for mobile phones 29 | 30 | :param dic: associates weights to words from [a-z]* 31 | :returns: a dictionary associating to words from [2-9]* 32 | a corresponding word from the dictionary with highest weight 33 | :complexity: linear in total word length 34 | """ 35 | # total_weight[p] = total weight of words having prefix p 36 | total_weight = {} 37 | for word, weight in dic: 38 | prefix = "" 39 | for x in word: 40 | prefix += x 41 | if prefix in total_weight: 42 | total_weight[prefix] += weight 43 | else: 44 | total_weight[prefix] = weight 45 | # prop[s] = prefix to display for s 46 | prop = {} 47 | for prefix in total_weight: 48 | code = code_word(prefix) 49 | if (code not in prop 50 | or total_weight[prop[code]] < total_weight[prefix]): 51 | prop[code] = prefix 52 | return prop 53 | 54 | 55 | def propose(prop, seq): 56 | """wrapper to access a dictionary even for non-present keys""" 57 | if seq in prop: 58 | return prop[seq] 59 | return None 60 | # snip} 61 | -------------------------------------------------------------------------------- /tryalgo/primes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Prime numbers by Eratosthene 5 | nombre premiers 2 18 | :returns: list of prime numbers factor[x] or p * x >= n: 51 | break 52 | factor[p * x] = p # p is the smallest factor of p * x 53 | return primes, factor 54 | # snip} 55 | 56 | 57 | # pylint: disable=redefined-outer-name, missing-docstring 58 | if __name__ == "__main__": 59 | 60 | # compare the running times and show the ratio between the performances 61 | 62 | from time import time 63 | 64 | def test(f, n): 65 | start = time() 66 | for _ in range(10): 67 | f(n) 68 | return time() - start 69 | 70 | print("eratosthene\tgries_misra\tratio") 71 | n = 4 72 | for _ in range(30): 73 | E = test(eratosthene, n) 74 | G = test(gries_misra, n) 75 | print("%f\t%f\t%f" % (E, G, G / E)) 76 | n *= 2 77 | -------------------------------------------------------------------------------- /tryalgo/rabin_karp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Find substrings by Rabin-Karp 5 | 6 | jill-jenn vie et christoph durr - 2015-2019 7 | """ 8 | 9 | # http://www.wolframalpha.com/input/?i=nearest+prime+number+to+2**56 10 | # snip{ rabin_karp_roll_hash 11 | PRIME = 72057594037927931 # < 2^{56} 12 | DOMAIN = 128 13 | 14 | 15 | def roll_hash(old_val, out_digit, in_digit, last_pos): 16 | """roll_hash """ 17 | val = (old_val - out_digit * last_pos + DOMAIN * PRIME) % PRIME 18 | val = (val * DOMAIN) % PRIME 19 | return (val + in_digit) % PRIME 20 | # snip} 21 | 22 | 23 | # snip{ rabin_karp_matches 24 | def matches(s, t, i, j, k): 25 | """ 26 | Checks whether s[i:i + k] is equal to t[j:j + k]. 27 | We used a loop to ease the implementation in other languages. 28 | """ 29 | # tests if s[i:i + k] equals t[j:j + k] 30 | for d in range(k): 31 | if s[i + d] != t[j + d]: 32 | return False 33 | return True 34 | # snip} 35 | 36 | 37 | # snip{ rabin_karp_matching 38 | def rabin_karp_matching(s, t): 39 | """Find a substring by Rabin-Karp 40 | 41 | :param s: the haystack string 42 | :param t: the needle string 43 | 44 | :returns: index i such that s[i: i + len(t)] == t, or -1 45 | :complexity: O(len(s) + len(t)) in expected time, 46 | and O(len(s) * len(t)) in worst case 47 | """ 48 | hash_s = 0 49 | hash_t = 0 50 | len_s = len(s) 51 | len_t = len(t) 52 | last_pos = pow(DOMAIN, len_t - 1) % PRIME 53 | if len_s < len_t: # substring too long 54 | return -1 55 | for i in range(len_t): # preprocessing 56 | hash_s = (DOMAIN * hash_s + ord(s[i])) % PRIME 57 | hash_t = (DOMAIN * hash_t + ord(t[i])) % PRIME 58 | for i in range(len_s - len_t + 1): 59 | if hash_s == hash_t: # hashes match 60 | # check character by character 61 | if matches(s, t, i, 0, len_t): 62 | return i 63 | if i < len_s - len_t: 64 | # shift window and calculate new hash on s 65 | hash_s = roll_hash(hash_s, ord(s[i]), ord(s[i + len_t]), 66 | last_pos) 67 | return -1 # no match 68 | # snip} 69 | 70 | 71 | # snip{ rabin_karp_factor 72 | def rabin_karp_factor(s, t, k): 73 | """Find a common factor by Rabin-Karp 74 | 75 | :param string s: haystack 76 | :param string t: needle 77 | :param int k: factor length 78 | :returns: (i, j) such that s[i:i + k] == t[j:j + k] or None. 79 | In case of tie, lexicographical minimum (i, j) is returned 80 | :complexity: O(len(s) + len(t)) in expected time, 81 | and O(len(s) + len(t) * k) in worst case 82 | """ 83 | last_pos = pow(DOMAIN, k - 1) % PRIME 84 | pos = {} 85 | assert k > 0 86 | if len(s) < k or len(t) < k: 87 | return None 88 | hash_t = 0 89 | 90 | # First calculate hash values of factors of t 91 | for j in range(k): 92 | hash_t = (DOMAIN * hash_t + ord(t[j])) % PRIME 93 | for j in range(len(t) - k + 1): 94 | # store the start position with the hash value 95 | if hash_t in pos: 96 | pos[hash_t].append(j) 97 | else: 98 | pos[hash_t] = [j] 99 | if j < len(t) - k: 100 | hash_t = roll_hash(hash_t, ord(t[j]), ord(t[j + k]), last_pos) 101 | 102 | hash_s = 0 103 | # Now check for matching factors in s 104 | for i in range(k): # preprocessing 105 | hash_s = (DOMAIN * hash_s + ord(s[i])) % PRIME 106 | for i in range(len(s) - k + 1): 107 | if hash_s in pos: # is this signature in s? 108 | for j in pos[hash_s]: 109 | if matches(s, t, i, j, k): 110 | return (i, j) 111 | if i < len(s) - k: 112 | hash_s = roll_hash(hash_s, ord(s[i]), ord(s[i + k]), last_pos) 113 | return None 114 | # snip} 115 | -------------------------------------------------------------------------------- /tryalgo/rectangles_from_grid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Largest area rectangle in a binary matrix 5 | plus grand rectangle monochromatique 6 | 7 | jill-jenn vie et christoph durr - 2014-2018 8 | """ 9 | 10 | from tryalgo.rectangles_from_histogram import rectangles_from_histogram 11 | 12 | 13 | # snip{ 14 | def rectangles_from_grid(P, black=1): 15 | """Largest area rectangle in a binary matrix 16 | 17 | :param P: matrix 18 | :param black: search for rectangles filled with value black 19 | :returns: area, left, top, right, bottom of optimal rectangle 20 | consisting of all (i,j) with 21 | left <= j < right and top <= i <= bottom 22 | :complexity: linear 23 | """ 24 | rows = len(P) 25 | cols = len(P[0]) 26 | t = [0] * cols 27 | best = None 28 | for i in range(rows): 29 | for j in range(cols): 30 | if P[i][j] == black: 31 | t[j] += 1 32 | else: 33 | t[j] = 0 34 | (area, left, height, right) = rectangles_from_histogram(t) 35 | alt = (area, left, i, right, i-height) 36 | if best is None or alt > best: 37 | best = alt 38 | return best 39 | # snip} 40 | -------------------------------------------------------------------------------- /tryalgo/rectangles_from_histogram.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Largest Rectangular Area in a Histogram 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | # pylint: disable=len-as-condition 12 | def rectangles_from_histogram(H): 13 | """Largest Rectangular Area in a Histogram 14 | 15 | :param H: histogram table 16 | :returns: area, left, height, right, rect. is [0, height] * [left, right) 17 | :complexity: linear 18 | """ 19 | best = (float('-inf'), 0, 0, 0) 20 | S = [] 21 | H2 = H + [float('-inf')] # extra element to empty the queue 22 | for right, _ in enumerate(H2): 23 | x = H2[right] 24 | left = right 25 | while len(S) > 0 and S[-1][1] >= x: 26 | left, height = S.pop() 27 | # first element is area of candidate 28 | rect = (height * (right - left), left, height, right) 29 | if rect > best: 30 | best = rect 31 | S.append((left, x)) 32 | return best 33 | # snip} 34 | -------------------------------------------------------------------------------- /tryalgo/rectangles_from_points.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | How many rectangles can be formed from a set of points 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | def rectangles_from_points(S): 12 | """How many rectangles can be formed from a set of points 13 | 14 | :param S: list of points, as coordinate pairs 15 | :returns: the number of rectangles 16 | :complexity: :math:`O(n^2)` 17 | """ 18 | answ = 0 19 | pairs = {} 20 | for j, _ in enumerate(S): 21 | for i in range(j): # loop over point pairs (p,q) 22 | px, py = S[i] 23 | qx, qy = S[j] 24 | center = (px + qx, py + qy) 25 | dist = (px - qx) ** 2 + (py - qy) ** 2 26 | signature = (center, dist) 27 | if signature in pairs: 28 | answ += len(pairs[signature]) 29 | pairs[signature].append((i, j)) 30 | else: 31 | pairs[signature] = [(i, j)] 32 | return answ 33 | # snip} 34 | -------------------------------------------------------------------------------- /tryalgo/roman_numbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Evaluate an arithmetic expression 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | # convert roman numbers 10 | # pylint: disable=bad-whitespace 11 | roman = [['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'], 12 | ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'], 13 | ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'], 14 | ['', 'M', 'MM', 'M'*3, 'M'*4, 'M'*5, 'M'*6, 'M'*7, 'M'*8, 'M'*9]] 15 | 16 | 17 | def roman2int(s): 18 | """Decode roman number 19 | 20 | :param s: string representing a roman number between 1 and 9999 21 | :returns: the decoded roman number 22 | :complexity: linear (if that makes sense for constant bounded input size) 23 | """ 24 | val = 0 25 | pos10 = 1000 26 | beg = 0 27 | for pos in range(3, -1, -1): 28 | for digit in range(9, -1, -1): 29 | r = roman[pos][digit] 30 | if s.startswith(r, beg): # footnote 1 31 | beg += len(r) 32 | val += digit * pos10 33 | break 34 | pos10 //= 10 35 | return val 36 | 37 | # footnote 1: 38 | # in C one would write 39 | # 40 | # if (strncmp(s + beg, r, strlen(r)) == 0) 41 | # 42 | # in C++ the starts_with method does not allow a selection 43 | # of a substring in s, where the search should start. 44 | # so you need to write your own function, something like: 45 | # 46 | # bool starts_with(const string &s, const string &r, int beg) { 47 | # for (int i = 0; i < r.size(); i++) { 48 | # if (s[i + beg] != r[i]) 49 | # return false; 50 | # } 51 | # return true; 52 | # } 53 | 54 | 55 | def int2roman(val): 56 | """Code roman number 57 | 58 | :param val: integer between 1 and 9999 59 | :returns: the corresponding roman number 60 | :complexity: linear (if that makes sense for constant bounded input size) 61 | """ 62 | s = '' 63 | pos10 = 1000 64 | for pos in range(3, -1, -1): 65 | digit = val // pos10 66 | s += roman[pos][digit] 67 | val %= pos10 68 | pos10 //= 10 69 | return s 70 | -------------------------------------------------------------------------------- /tryalgo/scalar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Permute vector to minimize scalar product 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def min_scalar_prod(x, y): 12 | """Permute vector to minimize scalar product 13 | 14 | :param x: 15 | :param y: x, y are vectors of same size 16 | :returns: min sum x[i] * y[sigma[i]] over all permutations sigma 17 | :complexity: O(n log n) 18 | """ 19 | x1 = sorted(x) # make copies to preserve the input arguments 20 | y1 = sorted(y) 21 | return sum(x1[i] * y1[-i - 1] for i in range(len(x1))) 22 | # snip} 23 | -------------------------------------------------------------------------------- /tryalgo/shortest_cycle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """\ 3 | Find shortest simple cycle 4 | 5 | christoph durr, finn voelkel and louis abraham - 2016-2019 6 | 7 | O(V*E) 8 | footnote (1) here you can add parity check of cycle_len 9 | if only even cycles are requested 10 | """ 11 | # pylint: disable=bad-whitespace, missing-docstring, multiple-statements 12 | 13 | from collections import deque 14 | from sys import stdin 15 | from tryalgo.floyd_warshall import floyd_warshall 16 | 17 | 18 | def readstr(): return stdin.readline().strip() 19 | 20 | 21 | def readints(): return map(int, stdin.readline().split()) 22 | 23 | 24 | def readint(): return int(stdin.readline()) 25 | 26 | 27 | __all__ = ["shortest_cycle", "powergraph"] 28 | 29 | 30 | def bfs(graph, root, prune_level): 31 | """make a pruned BFS search of the graph starting at root. 32 | returns the BFS tree, and possibly a traversal edge (u,v) 33 | that with the tree forms a cycle of some length. 34 | 35 | :param graph: undirected graph in listlist or listdict format 36 | :param root: vertex where BFS exploration starts 37 | :param prune_level: exploration is done only up to 38 | the prune_level (included) 39 | :complexity: O(V + E) 40 | """ 41 | n = len(graph) 42 | level = [-1] * n # -1 == not seen 43 | tree = [None] * n # pointers to predecessors 44 | to_visit = deque([root]) # queue for BFS 45 | level[root] = 0 46 | tree[root] = root 47 | best_cycle = float('inf') # start with infinity 48 | best_u = None 49 | best_v = None 50 | while to_visit: 51 | u = to_visit.popleft() 52 | if level[u] > prune_level: 53 | break 54 | for v in graph[u]: 55 | if tree[u] == v: # ignore the tree edge 56 | continue 57 | if level[v] == -1: # new vertex - tree edge 58 | level[v] = level[u] + 1 59 | to_visit.append(v) 60 | tree[v] = u 61 | else: # vertex already seen - traversal edge 62 | prune_level = level[v] - 1 63 | cycle_len = level[u] + 1 + level[v] 64 | if cycle_len < best_cycle: # footnote (1) 65 | best_cycle = cycle_len 66 | best_u = u 67 | best_v = v 68 | return tree, best_cycle, best_u, best_v 69 | 70 | 71 | def path(tree, v): 72 | """returns the path in the tree from v to the root 73 | Complexity: O(V) 74 | """ 75 | P = [] 76 | while not P or P[-1] != v: 77 | P.append(v) 78 | v = tree[v] 79 | return P 80 | 81 | 82 | def shortest_cycle(graph): 83 | """ Finding a shortest cycle in an undirected unweighted graph 84 | 85 | :param graph: undirected graph in listlist or listdict format 86 | :returns: None or a list C describing a shortest cycle 87 | :complexity: `O(|V|*|E|)` 88 | """ 89 | best_cycle = float('inf') 90 | best_u = None 91 | best_v = None 92 | best_tree = None 93 | V = list(range(len(graph))) 94 | for root in V: 95 | tree, cycle_len, u, v = bfs(graph, root, best_cycle // 2) 96 | if cycle_len < best_cycle: 97 | best_cycle = cycle_len 98 | best_u = u 99 | best_v = v 100 | best_tree = tree 101 | if best_cycle == float('inf'): 102 | return None # no cycle found 103 | Pu = path(best_tree, best_u) # combine path to make a cycle 104 | Pv = path(best_tree, best_v) 105 | cycle = Pu[::-1] + Pv # last vertex equals first vertex 106 | return cycle[1:] # remove duplicate vertex 107 | 108 | 109 | def powergraph(graph, k): 110 | """Compute the k-th 111 | `powergraph `_ 112 | which has an edge u,v for every vertex pair 113 | of distance at most k in the given graph. 114 | 115 | :param graph: undirected graph in listlist or listdict format 116 | :param k: non-negative integer. 117 | :complexity: O(V^3) 118 | """ 119 | V = range(len(graph)) 120 | # create weight matrix for paths of length 1 121 | M = [[float('inf') for v in V] for u in V] 122 | for u in V: 123 | for v in graph[u]: 124 | M[u][v] = M[v][u] = 1 125 | M[u][u] = 0 126 | floyd_warshall(M) 127 | return [[v for v in V if M[u][v] <= k] for u in V] 128 | -------------------------------------------------------------------------------- /tryalgo/subsetsum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | subsetsum 5 | 6 | jill-jenn vie et christoph durr - 2015-2018 7 | """ 8 | 9 | 10 | # snip{ subsetsum 11 | def subset_sum(x, R): 12 | """Subsetsum 13 | 14 | :param x: table of non negative values 15 | :param R: target value 16 | :returns bool: True if a subset of x sums to R 17 | :complexity: O(n*R) 18 | """ 19 | b = [False] * (R + 1) 20 | b[0] = True 21 | for xi in x: 22 | for s in range(R, xi - 1, -1): 23 | b[s] |= b[s - xi] 24 | return b[R] 25 | # snip} 26 | 27 | 28 | # snip{ coinchange 29 | def coin_change(x, R): 30 | """Coin change 31 | 32 | :param x: table of non negative values 33 | :param R: target value 34 | :returns bool: True if there is a non negative linear combination 35 | of x that has value R 36 | :complexity: O(n*R) 37 | """ 38 | b = [False] * (R + 1) 39 | b[0] = True 40 | for xi in x: 41 | for s in range(xi, R + 1): 42 | b[s] |= b[s - xi] 43 | return b[R] 44 | # snip} 45 | -------------------------------------------------------------------------------- /tryalgo/subsetsum_divide.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Subsetsum by splitting 5 | 6 | christoph dürr et jill-jênn vie - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def part_sum(x_table, i=0): 12 | """All subsetsums from x_table[i:] 13 | 14 | :param x_table: table of values 15 | :param int i: index_table defining suffix_table of x_table to be considered 16 | :iterates: over all values, in arbitrary order 17 | :complexity: :math:`O(2^{len(x_table)-i})` 18 | """ 19 | if i == len(x_table): 20 | yield 0 21 | else: 22 | for s_idx in part_sum(x_table, i + 1): 23 | yield s_idx 24 | yield s_idx + x_table[i] 25 | 26 | 27 | def subset_sum(x_table, r_target): 28 | """Subsetsum by splitting 29 | 30 | :param x_table: table of values 31 | :param r_target: target value 32 | :returns bool: if there is a subsequence of x_table with total sum r_target 33 | :complexity: :math:`O(n^{\\lceil n/2 \\rceil})` 34 | """ 35 | k = len(x_table) // 2 # divide input 36 | y_value = list(part_sum(x_table[:k])) 37 | z_value = [r_target - v for v in part_sum(x_table[k:])] 38 | y_value.sort() # test of intersection between y_value and z_value 39 | z_value.sort() 40 | i = 0 41 | j = 0 42 | while i < len(y_value) and j < len(z_value): 43 | if y_value[i] == z_value[j]: 44 | return True 45 | if y_value[i] < z_value[j]: # increment index of smallest element 46 | i += 1 47 | else: 48 | j += 1 49 | return False 50 | # snip} 51 | 52 | 53 | # snip{ subset_sum2 54 | def part_sum2(x_table): 55 | """All subsetsums from a list x 56 | 57 | :param x_table: list of values 58 | :complexity: :math:`O(2^{len(x)})` 59 | """ 60 | answer = set([0]) # 0 = value of empty set 61 | for xi in x_table: 62 | answer |= set(value + xi for value in answer) 63 | return answer 64 | 65 | 66 | def subset_sum2(x_table, r_target): 67 | """Subsetsum by splitting 68 | 69 | :param x_table: table of values 70 | :param r_target: target value 71 | :returns bool: if there is a subsequence of x_table with total sum r_target 72 | :complexity: :math:`O(n^{\\lceil n/2 \\rceil})` 73 | """ 74 | k = len(x_table) // 2 # divide input 75 | y_set = part_sum2(x_table[:k]) 76 | z_set = set(r_target - value for value in part_sum2(x_table[k:])) 77 | return len(y_set & z_set) > 0 # test intersection 78 | # snip} 79 | -------------------------------------------------------------------------------- /tryalgo/sudoku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Solving Sudoku (nanpure) 5 | 6 | jill-jenn vie et christoph durr - 2014-2019 7 | """ 8 | # pylint: disable=missing-docstring, multiple-statements, global-statement 9 | 10 | from tryalgo.dancing_links import dancing_links 11 | 12 | 13 | __all__ = ["sudoku"] 14 | 15 | # snip{ 16 | N = 3 # global constants 17 | N2 = N * N 18 | N4 = N2 * N2 19 | 20 | 21 | # sets 22 | def assignment(r, c, v): return r * N4 + c * N2 + v 23 | 24 | 25 | def row(a): return a // N4 26 | 27 | 28 | def col(a): return (a // N2) % N2 29 | 30 | 31 | def val(a): return a % N2 32 | 33 | 34 | def blk(a): return (row(a) // N) * N + col(a) // N 35 | 36 | 37 | # elements to cover 38 | def rc(a): return row(a) * N2 + col(a) 39 | 40 | 41 | def rv(a): return row(a) * N2 + val(a) + N4 42 | 43 | 44 | def cv(a): return col(a) * N2 + val(a) + 2 * N4 45 | 46 | 47 | def bv(a): return blk(a) * N2 + val(a) + 3 * N4 48 | 49 | 50 | def sudoku(G): 51 | """Solving Sudoku 52 | 53 | :param G: integer matrix with 0 at empty cells 54 | :returns bool: True if grid could be solved 55 | :modifies: G will contain the solution 56 | :complexity: huge, but linear for usual published 9x9 grids 57 | """ 58 | global N, N2, N4 59 | if len(G) == 16: # for a 16 x 16 sudoku grid 60 | N, N2, N4 = 4, 16, 256 61 | e = N * N4 62 | universe = e + 1 63 | S = [[rc(a), rv(a), cv(a), bv(a)] for a in range(N4 * N2)] 64 | A = [e] 65 | for r in range(N2): 66 | for c in range(N2): 67 | if G[r][c] != 0: 68 | a = assignment(r, c, G[r][c] - 1) 69 | A += S[a] 70 | sol = dancing_links(universe, S + [A]) 71 | if sol: 72 | for a in sol: 73 | if a < len(S): 74 | G[row(a)][col(a)] = val(a) + 1 75 | return True 76 | return False 77 | # snip} 78 | -------------------------------------------------------------------------------- /tryalgo/suffix_array.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | suffix array, 5 | but only the O(n log^2(n)) implementation, which is enough for most programming contest problems 6 | 7 | christoph dürr 2024 8 | """ 9 | 10 | def sort_class(s): 11 | """ sorts s and returns additional information 12 | 13 | :param s: string or list 14 | :returns p, c: p[j]=i if s[i] has rank j in sorted(s) and c[i] is rank of s[i] in sorted(set(s)) 15 | :complexity: O(n log n) or better if sort makes use of specific values in s 16 | """ 17 | S_index = [(x, i) for i, x in enumerate(s)] 18 | p = [i for x, i in sorted(S_index)] 19 | x2c = {x : i for i, x in enumerate(sorted(set(s)))} 20 | c = [x2c[x] for x in s] 21 | return p, c 22 | 23 | 24 | def sort_cyclic_shifts(s): 25 | """ given a string s, sort lexicographically all cyclic shifts of s. 26 | 27 | The i-th cyclic shift of s is s[i:] + s[i:] 28 | :param s: string or list 29 | :returns L: such that L[j]=i if the i-th cyclic shift of s has rank j 30 | :complexity: O(n * log(n)^2) 31 | """ 32 | p, c = sort_class(s) 33 | n = len(s) 34 | K = 1 35 | while K <= n: 36 | L = [(c[i], c[(i + K) % n]) for i in range(n)] 37 | p, c = sort_class(L) 38 | K <<= 1 39 | return p 40 | 41 | def suffix_array(s): 42 | """ given a string s, sort lexicographically suffixes of s 43 | :param s: string 44 | :returns: R with R[i] is j such that s[j:] has rank i 45 | :complexity: O(n log^2 n) 46 | """ 47 | special = chr(0) 48 | assert special < min(s) 49 | L = sort_cyclic_shifts(s + special) 50 | return L[1:] 51 | 52 | if __name__ == "__main__": 53 | # tested at https://www.spoj.com/problems/SARRAY/ 54 | import sys 55 | 56 | def readstr(): return sys.stdin.readline().rstrip() 57 | def readstrs(): return readstr().split() 58 | def readints(): return map(int, readstrs()) 59 | 60 | for val in suffix_array(readstr()): 61 | print(val) 62 | -------------------------------------------------------------------------------- /tryalgo/three_partition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | subsetsum 5 | 6 | jill-jênn vie et christoph dürr - 2015-2019 7 | """ 8 | 9 | 10 | # snip{ 11 | def three_partition(x): 12 | """partition a set of integers in 3 parts of same total value 13 | 14 | :param x: table of non negative values 15 | :returns: triplet of the integers encoding the sets, or None otherwise 16 | :complexity: :math:`O(2^{2n})` 17 | """ 18 | f = [0] * (1 << len(x)) 19 | for i, _ in enumerate(x): 20 | for S in range(1 << i): 21 | f[S | (1 << i)] = f[S] + x[i] 22 | for A in range(1 << len(x)): 23 | for B in range(1 << len(x)): 24 | if A & B == 0 and f[A] == f[B] and 3 * f[A] == f[-1]: 25 | return (A, B, ((1 << len(x)) - 1) ^ A ^ B) 26 | return None 27 | # snip} 28 | -------------------------------------------------------------------------------- /tryalgo/topological_order.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Topological order 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | 10 | # snip{ topological_order_dfs 11 | def topological_order_dfs(graph): 12 | """Topological sorting by depth first search 13 | 14 | :param graph: directed graph in listlist format, cannot be listdict 15 | :returns: list of vertices in order 16 | :complexity: `O(|V|+|E|)` 17 | """ 18 | n = len(graph) 19 | order = [] 20 | times_seen = [-1] * n 21 | for start in range(n): 22 | if times_seen[start] == -1: 23 | times_seen[start] = 0 24 | to_visit = [start] 25 | while to_visit: 26 | node = to_visit[-1] 27 | children = graph[node] 28 | if times_seen[node] == len(children): 29 | to_visit.pop() 30 | order.append(node) 31 | else: 32 | child = children[times_seen[node]] 33 | times_seen[node] += 1 34 | if times_seen[child] == -1: 35 | times_seen[child] = 0 36 | to_visit.append(child) 37 | return order[::-1] 38 | # snip} 39 | 40 | 41 | # snip{ 42 | def topological_order(graph): 43 | """Topological sorting by maintaining indegree 44 | 45 | :param graph: directed graph in listlist format, cannot be listdict 46 | :returns: list of vertices in order 47 | :complexity: `O(|V|+|E|)` 48 | """ 49 | V = range(len(graph)) 50 | indeg = [0 for _ in V] 51 | for node in V: # compute indegree 52 | for neighbor in graph[node]: 53 | indeg[neighbor] += 1 54 | Q = [node for node in V if indeg[node] == 0] 55 | order = [] 56 | while Q: 57 | node = Q.pop() # node without incoming arrows 58 | order.append(node) 59 | for neighbor in graph[node]: 60 | indeg[neighbor] -= 1 61 | if indeg[neighbor] == 0: 62 | Q.append(neighbor) 63 | return order 64 | # snip} 65 | -------------------------------------------------------------------------------- /tryalgo/tortoise_hare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Detect a cycle for a function on a finite domain. 5 | 6 | jill-jenn vie et christoph durr - 2023 7 | """ 8 | 9 | 10 | # snip{ 11 | def tortoise_hare(f, source=0): 12 | """ Detect cycle for function f, starting from source 13 | 14 | :param f: function from finite domain to itself 15 | :param source: element in this domain 16 | :warning: if the function does not reach a cycle from source 17 | then this function loops forever 18 | :returns: c, d such that after d iterations 19 | of f a cycle is reached, which has period c 20 | :complexity: `O(d+c)` 21 | """ 22 | t = f(source) # tortoise 23 | h = f(f(source)) # hare 24 | 25 | # move to some position in cycle 26 | while t != h: 27 | t = f(t) 28 | h = f(f(h)) 29 | # detect begining of cycle 30 | t = source 31 | d = 0 32 | while t != h: 33 | t = f(t) 34 | h = f(h) 35 | d += 1 36 | # detect period of cycle 37 | c = 1 38 | t = f(t) 39 | while t != h: 40 | t = f(t) 41 | c += 1 42 | return c, d 43 | # snip} 44 | 45 | 46 | -------------------------------------------------------------------------------- /tryalgo/trie.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | trie - correcteur orthographique 5 | 6 | jill-jênn vie et christoph dürr - 2014-2019 7 | """ 8 | 9 | # Don't write a Trie class otherwise you cannot represent leaves with None 10 | 11 | # snip{ 12 | from string import ascii_letters # in Python 2 one would import letters 13 | 14 | 15 | # pylint: disable=missing-docstring, too-few-public-methods 16 | class TrieNode: 17 | def __init__(self): # each node will have 18 | self.is_word = False # 52 children - 19 | self.s = {c: None for c in ascii_letters} # most will remain empty 20 | 21 | 22 | def add(T, w, i=0): # Add a word to the trie 23 | """ 24 | :param T: trie 25 | :param string w: word to be added to T 26 | :returns: new trie consisting of w added into T 27 | :complexity: O(len(w)) 28 | """ 29 | if T is None: 30 | T = TrieNode() 31 | if i == len(w): 32 | T.is_word = True 33 | else: 34 | T.s[w[i]] = add(T.s[w[i]], w, i + 1) 35 | return T 36 | 37 | 38 | def Trie(S): # Build the trie for the words in the dictionary S 39 | """ 40 | :param S: set of words 41 | :returns: trie containing all words from S 42 | :complexity: linear in total word sizes from S 43 | """ 44 | T = None 45 | for w in S: 46 | T = add(T, w) 47 | return T 48 | 49 | 50 | def spell_check(T, w): # Spell check a word against the trie 51 | """Spellchecker 52 | 53 | :param T: trie encoding the dictionary 54 | :param w: given word 55 | :returns: a closest word from the dictionary 56 | :complexity: linear if distance was constant 57 | """ 58 | assert T is not None 59 | dist = 0 60 | while True: 61 | u = search(T, dist, w) 62 | if u is not None: # Match at distance dist 63 | return u 64 | dist += 1 # No match - try increasing the distance 65 | 66 | 67 | # pylint: disable=too-many-return-statements, no-else-return 68 | def search(T, dist, w, i=0): 69 | """Searches for w[i:] in trie T with distance at most dist 70 | """ 71 | if i == len(w): 72 | if T is not None and T.is_word and dist == 0: 73 | return "" 74 | else: 75 | return None 76 | if T is None: 77 | return None 78 | f = search(T.s[w[i]], dist, w, i + 1) # matching 79 | if f is not None: 80 | return w[i] + f 81 | if dist == 0: 82 | return None 83 | for c in ascii_letters: 84 | f = search(T.s[c], dist - 1, w, i) # insertion 85 | if f is not None: 86 | return c + f 87 | f = search(T.s[c], dist - 1, w, i + 1) # substitution 88 | if f is not None: 89 | return c + f 90 | return search(T, dist - 1, w, i + 1) # deletion 91 | # snip} 92 | -------------------------------------------------------------------------------- /tryalgo/two_sat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | Solving 2-SAT boolean formulas 5 | 6 | jill-jenn vie et christoph durr - 2015-2019 7 | """ 8 | 9 | from tryalgo.strongly_connected_components import tarjan 10 | 11 | 12 | # snip{ 13 | def _vertex(lit): # integer encoding of a litteral 14 | if lit > 0: 15 | return 2 * (lit - 1) 16 | return 2 * (-lit - 1) + 1 17 | 18 | 19 | def two_sat(formula): 20 | """Solving a 2-SAT boolean formula 21 | 22 | :param formula: list of clauses, a clause is pair of literals 23 | over X1,...,Xn for some n. 24 | a literal is an integer, for example -1 = not X1, 3 = X3 25 | :returns: table with boolean assignment satisfying the formula or None 26 | :complexity: linear 27 | """ 28 | # num_variables is the number of variables 29 | num_variables = max(abs(clause[p]) 30 | for p in (0, 1) for clause in formula) 31 | graph = [[] for node in range(2 * num_variables)] 32 | for x_idx, y_idx in formula: # x_idx or y_idx 33 | graph[_vertex(-x_idx)].append(_vertex(y_idx)) # -x_idx => y_idx 34 | graph[_vertex(-y_idx)].append(_vertex(x_idx)) # -y_idx => x_idx 35 | sccp = tarjan(graph) 36 | comp_id = [None] * (2 * num_variables) # each node's component ID 37 | assignment = [None] * (2 * num_variables) 38 | for component in sccp: 39 | rep = min(component) # representative of the component 40 | for vtx in component: 41 | comp_id[vtx] = rep 42 | if assignment[vtx] is None: 43 | assignment[vtx] = True 44 | assignment[vtx ^ 1] = False # complementary literal 45 | for i in range(num_variables): 46 | if comp_id[2 * i] == comp_id[2 * i + 1]: 47 | return None # insatisfiable formula 48 | return assignment[::2] 49 | # snip} 50 | -------------------------------------------------------------------------------- /tryalgo/windows_k_distinct.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """\ 4 | All sliding windows containing k distinct elements 5 | 6 | jill-jenn vie et christoph durr - 2014-2018 7 | """ 8 | 9 | 10 | # snip{ 11 | def windows_k_distinct(x, k): 12 | """Find all largest windows containing exactly k distinct elements 13 | 14 | :param x: list or string 15 | :param k: positive integer 16 | :yields: largest intervals [i, j) with len(set(x[i:j])) == k 17 | :complexity: `O(|x|)` 18 | """ 19 | dist, i, j = 0, 0, 0 # dist = |{x[i], ..., x[j-1]}| 20 | occ = {xi: 0 for xi in x} # number of occurrences in x[i:j] 21 | while j < len(x): 22 | while dist == k: # move start of interval 23 | occ[x[i]] -= 1 # update counters 24 | if occ[x[i]] == 0: 25 | dist -= 1 26 | i += 1 27 | while j < len(x) and (dist < k or occ[x[j]]): 28 | if occ[x[j]] == 0: # update counters 29 | dist += 1 30 | occ[x[j]] += 1 31 | j += 1 # move end of interval 32 | if dist == k: 33 | yield (i, j) # one interval found 34 | # snip} 35 | --------------------------------------------------------------------------------