├── .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 | [](https://pypi.python.org/pypi/tryalgo/)
2 | [](https://pypi.python.org/pypi/tryalgo/)
3 | 
4 | [](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 |
--------------------------------------------------------------------------------