diff --git a/_data/paris_54000.zip b/_data/paris_54000.zip
new file mode 100644
index 00000000..122a919a
Binary files /dev/null and b/_data/paris_54000.zip differ
diff --git a/_doc/api/index.rst b/_doc/api/index.rst
index 527bcf35..657e821c 100644
--- a/_doc/api/index.rst
+++ b/_doc/api/index.rst
@@ -5,3 +5,8 @@ Code inclus dans cette librairie
classique
tools
+
+.. toctree::
+ :caption: Fonctions implémentées pour les exercices
+
+ practice/rues_paris
diff --git a/_doc/api/practice/rues_paris.rst b/_doc/api/practice/rues_paris.rst
new file mode 100644
index 00000000..8357b394
--- /dev/null
+++ b/_doc/api/practice/rues_paris.rst
@@ -0,0 +1,5 @@
+============================
+teachpyx.practice.rues_paris
+============================
+
+.. automodule:: teachpyx.practice.rues_paris
diff --git a/_doc/articles/2022-12-07-cartopy.rst b/_doc/articles/2022-12-07-cartopy.rst
index 7641aa3f..0a056e07 100644
--- a/_doc/articles/2022-12-07-cartopy.rst
+++ b/_doc/articles/2022-12-07-cartopy.rst
@@ -8,8 +8,7 @@
Installer `cartopy
`_
est une vraie gageure. J'ai utilisé la version disponible sur
-`Archived: Unofficial Windows Binaries for Python Extension Packages
-`_
+*Archived: Unofficial Windows Binaries for Python Extension Packages*
mais le site n'est plus maintenu et je veux bien comprendre
que c'est un travail ingrat qui requiert une attention
permenante (voir
diff --git a/_doc/practice/algo-compose/euler.png b/_doc/practice/algo-compose/euler.png
new file mode 100644
index 00000000..394ebefc
Binary files /dev/null and b/_doc/practice/algo-compose/euler.png differ
diff --git a/_doc/practice/algo-compose/paris_parcours.ipynb b/_doc/practice/algo-compose/paris_parcours.ipynb
new file mode 100644
index 00000000..bad47cd1
--- /dev/null
+++ b/_doc/practice/algo-compose/paris_parcours.ipynb
@@ -0,0 +1,924 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Parcourir les rues de Paris\n",
+ "\n",
+ "Algorithme de plus courts chemins dans un graphe. Calcul d'un chemin comparé au calcul de tous les chemins les plus courts."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Populating the interactive namespace from numpy and matplotlib\n"
+ ]
+ }
+ ],
+ "source": [
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Cette idée vient d'une soirée Google Code initiée par Google et à laquelle des élèves de l'ENSAE ont participé. On dispose de la description des rues de Paris (qu'on considèrera comme des lignes droites). On veut déterminer le trajet de huit voitures de telle sorte qu'elles parcourent la ville le plus rapidement possible. On supposera deux cas :\n",
+ "\n",
+ "- Les voitures peuvent être placées n'importe où dans la ville.\n",
+ "- Les voitures démarrent et reviennent au même point de départ, le même pour toutes.\n",
+ "\n",
+ "Ce notebook décrit comment récupérer les données et propose une solution. Ce problème est plus connu sous le nom du [problème du postier chinois](https://fr.wikipedia.org/wiki/Probl%C3%A8me_du_postier_chinois) ou [Route inspection problem](https://en.wikipedia.org/wiki/Route_inspection_problem) pour lequel il existe un algorithme optimal à coût polynomial. Le problème n'est donc pas [NP complet](https://en.wikipedia.org/wiki/NP-completeness)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Les données\n",
+ "\n",
+ "On récupère les données sur Internet."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " downloading of http://www.xavierdupre.fr/enseignement/complements/paris_54000.zip to paris_54000.zip\n",
+ " unzipped paris_54000.txt to .\\paris_54000.txt\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "['.\\\\paris_54000.txt']"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "url = \"https://github.com/sdpython/teachpyx/_data/paris_54000.zip\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On extrait du fichier l'ensemble des carrefours (vertices) et des rues ou segment de rues (edges)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "#E= 17958 #V= 11348 > 11347\n"
+ ]
+ }
+ ],
+ "source": [
+ "name = data[0]\n",
+ "with open(name, \"r\") as f:\n",
+ " lines = f.readlines()\n",
+ "\n",
+ "vertices = []\n",
+ "edges = []\n",
+ "for i, line in enumerate(lines):\n",
+ " spl = line.strip(\"\\n\\r\").split(\" \")\n",
+ " if len(spl) == 2:\n",
+ " vertices.append((float(spl[0]), float(spl[1])))\n",
+ " elif len(spl) == 5 and i > 0:\n",
+ " v1, v2 = int(spl[0]), int(spl[1])\n",
+ " ways = int(spl[2]) # dans les deux sens ou pas\n",
+ " p1 = vertices[v1]\n",
+ " p2 = vertices[v2]\n",
+ " edges.append((v1, v2, ways, p1, p2))\n",
+ " elif i > 0:\n",
+ " raise ValueError(f\"Unable to interpret line {i}: {line!r}\")\n",
+ "\n",
+ "m = max(max(_[0] for _ in edges), max(_[1] for _ in edges))\n",
+ "print(f\"#E={len(edges)} #V={len(vertices)}>{m}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On trace sur un graphique un échantillon des carrefours. On suppose la ville de Paris suffisamment petite et loin des pôles pour considérer les coordonnées comme cartésiennes (et non comme longitude/latitude)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[]"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "import random\n",
+ "\n",
+ "sample = [vertices[random.randint(0, len(vertices) - 1)] for i in range(0, 1000)]\n",
+ "plt.plot([_[0] for _ in sample], [_[1] for _ in sample], \".\")\n",
+ "plt.title(\"Carrefours de Paris\");"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Puis on dessine également un échantillon des rues."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "sample = [edges[random.randint(0, len(edges) - 1)] for i in range(0, 1000)]\n",
+ "for edge in sample:\n",
+ " plt.plot([_[0] for _ in edge[-2:]], [_[1] for _ in edge[-2:]], \"b-\")\n",
+ "plt.title(\"Rue de Paris\");"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Petite remarque : il n'y a pas de rues reliant un même carrefour à lui-même."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(list(e for e in edges if e[0] == e[1]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Une première solution au premier problème\n",
+ "\n",
+ "Ce problème est très similaire à celui du [portier chinois](http://fr.wikipedia.org/wiki/Probl%C3%A8me_du_postier_chinois). La solution qui suit n'est pas nécessaire la meilleure mais elle donne une idée de ce que peut-être une recherche un peu expérimentale sur le sujet.\n",
+ "\n",
+ "Chaque noeud représente un carrefour et chaque rue est un arc reliant des deux carrefours. L'objectif est de parcourir tous les arcs du graphe avec 8 voitures. \n",
+ "\n",
+ "Premiere remarque, l'énoncé ne dit pas qu'il faut parcourir toutes les rues une seule fois. On conçoit aisément que ce serait l'idéal mais on ne sait pas si ce serait possible. Néanmoins, si une telle solution (un chemin passant une et une seule fois par toutes les rues) existe, elle est forcément optimale.\n",
+ "\n",
+ "Deuxième remarque, les sens interdits rendent le problème plus complexe. On va dans un premier temps ne pas en tenir compte. On verra comment ajouter la contrainte par la suite et il y a aussi le problème des impasses. On peut néanmoins les contourner en les supprimant du graphe : il faut nécessairement faire demi-tour et il n'y a pas de meilleure solution.\n",
+ "\n",
+ "Ces deux remarques étant faite, ce problème rappelle un peu le problème des sept ponts de [Konigsberg](http://fr.wikipedia.org/wiki/Probl%C3%A8me_des_sept_ponts_de_K%C3%B6nigsberg) : comment traverser passer par les sept de la ville une et une seule fois. Le mathématicien [Euler](http://fr.wikipedia.org/wiki/Leonhard_Euler) a répondu à cette question : c'est simple, il suffit que chaque noeud du graphe soit rejoint par un nombre pair de d'arc (= ponts) sauf au plus 2 (les noeuds de départ et d'arrivée). De cette façon, à chaque qu'on rejoint un noeud, il y a toujours une façon de repartir."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import networkx as nx\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "g = nx.Graph()\n",
+ "for i, j in [(1, 2), (1, 3), (1, 4), (2, 3), (3, 4), (4, 5), (5, 2), (2, 4)]:\n",
+ " g.add_edge(i, j)\n",
+ "f, ax = plt.subplots(figsize=(6, 3))\n",
+ "nx.draw(g, ax=ax)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On ne peut pas trouver une chemin qui parcourt tous les arcs du graphe précédent une et une seule fois. Qu'en est-il du graphe de la ville de Paris ? On compte les noeuds qui ont un nombre pairs et impairs d'arcs les rejoignant (on appelle cela le [degré](http://fr.wikipedia.org/wiki/Degr%C3%A9_(th%C3%A9orie_des_graphes)))."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[[(2, 1337), (3, 7103), (4, 2657), (5, 209), (6, 35), (7, 6), (8, 1)]]"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "nb_edge = {}\n",
+ "for edge in edges:\n",
+ " v1, v2 = edge[:2]\n",
+ " nb_edge[v1] = nb_edge.get(v1, 0) + 1\n",
+ " nb_edge[v2] = nb_edge.get(v2, 0) + 1\n",
+ "parite = {}\n",
+ "for k, v in nb_edge.items():\n",
+ " parite[v] = parite.get(v, 0) + 1\n",
+ "\n",
+ "[sorted(parite.items())]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On remarque que la plupart des carrefours sont le départ de 3 rues. Qu'à cela ne tienne, pourquoi ne pas ajouter des arcs entre des noeuds de degré impair jusqu'à ce qu'il n'y en ait plus que 2. De cette façon, il sera facile de construire un seul chemin parcourant toutes les rues. Comment ajouter ces arcs ? Cela va se faire en deux étapes :\n",
+ "\n",
+ "- On utilise l'algorithme de [Bellman-Ford](http://fr.wikipedia.org/wiki/Algorithme_de_Bellman-Ford) pour construire une matrice des plus courts chemins entre tous les noeuds.\n",
+ "- On s'inspire de l'algorithme de poids minimal [Kruskal](http://fr.wikipedia.org/wiki/Algorithme_de_Kruskal). On trie les arcs par ordre croissant de distance. On ajoute ceux qui réduisent le nombre de noeuds de degré impairs en les prenant dans cet ordre.\n",
+ "\n",
+ "**Quelques justifications :** le meilleur parcours ne pourra pas descendre en deça de la somme des distances des rues puisqu'il faut toutes les parcourir. De plus, s'il existe un chemin qui parcourt toutes les rues, en dédoublant toutes celles parcourues plus d'une fois, il est facile de rendre ce chemin *eulérien* dans un graphe légèrement modifié par rapport au graphe initial."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Etape 1 : la matrice Bellman\n",
+ "\n",
+ "Je ne détaillerai pas trop, la page Wikipedia est assez claire. Dans un premier temps on calcule la longueur de chaque arc (de façon cartésienne). Une autre distance ([Haversine](http://en.wikipedia.org/wiki/Haversine_formula)) ne changerait pas le raisonnement."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def distance(p1, p2):\n",
+ " return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5\n",
+ "\n",
+ "\n",
+ "edges = [edge + (distance(edge[-2], edge[-1]),) for edge in edges]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Ensuite, on implémente l'algorithme de [Bellman-Ford](http://fr.wikipedia.org/wiki/Algorithme_de_Bellman-Ford)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "2015-04-12 01:16:40.590690 iteration 0 modif 72870 # 35916 / 128777104 = 0.03%\n",
+ "2015-04-12 01:16:41.340550 iteration 1 modif 120368 # 104842 / 128777104 = 0.08%\n",
+ "2015-04-12 01:16:42.887810 iteration 2 modif 180646 # 213826 / 128777104 = 0.17%\n",
+ "2015-04-12 01:16:45.510596 iteration 3 modif 255702 # 368960 / 128777104 = 0.29%\n",
+ "2015-04-12 01:16:49.759326 iteration 4 modif 347092 # 576106 / 128777104 = 0.45%\n",
+ "2015-04-12 01:16:55.781000 iteration 5 modif 455899 # 839276 / 128777104 = 0.65%\n",
+ "2015-04-12 01:17:04.276258 iteration 6 modif 584263 # 1162870 / 128777104 = 0.90%\n"
+ ]
+ }
+ ],
+ "source": [
+ "import datetime\n",
+ "\n",
+ "init = {(e[0], e[1]): e[-1] for e in edges}\n",
+ "init.update({(e[1], e[0]): e[-1] for e in edges})\n",
+ "\n",
+ "edges_from = {}\n",
+ "for e in edges:\n",
+ " if e[0] not in edges_from:\n",
+ " edges_from[e[0]] = []\n",
+ " if e[1] not in edges_from:\n",
+ " edges_from[e[1]] = []\n",
+ " edges_from[e[0]].append(e)\n",
+ " edges_from[e[1]].append((e[1], e[0], e[2], e[4], e[3], e[5]))\n",
+ "\n",
+ "modif = 1\n",
+ "total_possible_edges = len(edges_from) ** 2\n",
+ "it = 0\n",
+ "while modif > 0:\n",
+ " modif = 0\n",
+ " initc = (\n",
+ " init.copy()\n",
+ " ) # to avoid RuntimeError: dictionary changed size during iteration\n",
+ " s = 0\n",
+ " for i, d in initc.items():\n",
+ " fromi2 = edges_from[i[1]]\n",
+ " s += d\n",
+ " for e in fromi2:\n",
+ " if (\n",
+ " i[0] == e[1]\n",
+ " ): # on fait attention à ne pas ajouter de boucle sur le même noeud\n",
+ " continue\n",
+ " new_e = i[0], e[1]\n",
+ " new_d = d + e[-1]\n",
+ " if new_e not in init or init[new_e] > new_d:\n",
+ " init[new_e] = new_d\n",
+ " modif += 1\n",
+ " print(\n",
+ " f\"{datetime.datetime.now()} iteration {it} modif {modif} # {len(initc)}/{total_possible_edges}=\"\n",
+ " f\"{len(initc)*100 / total_possible_edges:0.00f}%\"\n",
+ " )\n",
+ " it += 1\n",
+ " if it > 6:\n",
+ " break"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On s'aperçoit vite que cela va être très très long. On décide alors de ne considérer que les paires de noeuds pour lesquelles la distance à vol d'oiseau est inférieure au plus grand segment de rue ou inférieure à cette distance multipliée par un coefficient."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.017418989861067814"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "max_segment = max(e[-1] for e in edges)\n",
+ "max_segment"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On calcule les arcs admissibles (en espérant que les noeuds de degré impairs seront bien dedans). Cette étape prend quelques minutes :"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "original 35916 / 128777104 = 0.000278900510140374\n",
+ "addition 2875586 / 128777104 = 0.022329947721141486\n"
+ ]
+ }
+ ],
+ "source": [
+ "possibles = {(e[0], e[1]): e[-1] for e in edges}\n",
+ "possibles.update({(e[1], e[0]): e[-1] for e in edges})\n",
+ "initial = possibles.copy()\n",
+ "for i1, v1 in enumerate(vertices):\n",
+ " for i2 in range(i1 + 1, len(vertices)):\n",
+ " v2 = vertices[i2]\n",
+ " d = distance(v1, v2)\n",
+ " if d < max_segment / 2: # on ajuste le seuil\n",
+ " possibles[i1, i2] = d\n",
+ " possibles[i2, i1] = d\n",
+ "\n",
+ "print(\n",
+ " f\"original {len(initial)}/{total_possible_edges} = \"\n",
+ " f\"{len(initial)/total_possible_edges}\"\n",
+ ")\n",
+ "print(\n",
+ " f\"addition {len(possibles)-len(initial)}/{total_possible_edges} = \"\n",
+ " f\"{(len(possibles)-len(initial))/total_possible_edges}\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On vérifie que les noeuds de degré impairs font tous partie de l'ensemble des noeuds recevant de nouveaux arcs. La matrice de Bellman envisagera au pire 2.2% de toutes les distances possibles."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "si vous voyez cette ligne, c'est que tout est bon\n"
+ ]
+ }
+ ],
+ "source": [
+ "allv = {p[0]: True for p in possibles if p not in initial} # possibles est symétrique\n",
+ "for v, p in nb_edge.items():\n",
+ " if p % 2 == 1 and v not in allv:\n",
+ " raise Exception(\"problème pour le noeud: {0}\".format(v))\n",
+ "print(\"si vous voyez cette ligne, c'est que tout est bon\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On continue avec l'algorithme de [Bellman-Ford](http://fr.wikipedia.org/wiki/Algorithme_de_Bellman-Ford) modifié :"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "2015-04-12 01:18:45.389333 iteration 0 modif 72870 # 35916 / 128777104 = 0.03%\n",
+ "2015-04-12 01:18:46.179293 iteration 1 modif 119604 # 104842 / 128777104 = 0.08%\n",
+ "2015-04-12 01:18:47.853483 iteration 2 modif 178033 # 213086 / 128777104 = 0.17%\n",
+ "2015-04-12 01:18:50.790772 iteration 3 modif 248648 # 365751 / 128777104 = 0.28%\n",
+ "2015-04-12 01:18:55.302585 iteration 4 modif 330443 # 566457 / 128777104 = 0.44%\n",
+ "2015-04-12 01:19:02.002318 iteration 5 modif 419549 # 815211 / 128777104 = 0.63%\n",
+ "2015-04-12 01:19:11.623367 iteration 6 modif 508807 # 1109019 / 128777104 = 0.86%\n",
+ "2015-04-12 01:19:23.579840 iteration 7 modif 585973 # 1438040 / 128777104 = 1.12%\n",
+ "2015-04-12 01:19:38.370350 iteration 8 modif 639491 # 1785232 / 128777104 = 1.39%\n",
+ "2015-04-12 01:19:56.035255 iteration 9 modif 656961 # 2127675 / 128777104 = 1.65%\n",
+ "2015-04-12 01:20:16.436453 iteration 10 modif 638987 # 2441604 / 128777104 = 1.90%\n",
+ "2015-04-12 01:20:39.075644 iteration 11 modif 591284 # 2711201 / 128777104 = 2.11%\n",
+ "2015-04-12 01:21:04.245760 iteration 12 modif 519515 # 2928527 / 128777104 = 2.27%\n",
+ "2015-04-12 01:21:33.496050 iteration 13 modif 434787 # 3091667 / 128777104 = 2.40%\n",
+ "2015-04-12 01:22:02.419568 iteration 14 modif 346204 # 3205671 / 128777104 = 2.49%\n",
+ "2015-04-12 01:22:29.389913 iteration 15 modif 263229 # 3279078 / 128777104 = 2.55%\n",
+ "2015-04-12 01:22:55.394012 iteration 16 modif 191482 # 3323381 / 128777104 = 2.58%\n",
+ "2015-04-12 01:23:22.033884 iteration 17 modif 133738 # 3348569 / 128777104 = 2.60%\n",
+ "2015-04-12 01:23:49.684842 iteration 18 modif 90442 # 3362160 / 128777104 = 2.61%\n",
+ "2015-04-12 01:24:17.267404 iteration 19 modif 59686 # 3369505 / 128777104 = 2.62%\n",
+ "2015-04-12 01:24:46.839139 iteration 20 modif 38936 # 3373640 / 128777104 = 2.62%\n"
+ ]
+ }
+ ],
+ "source": [
+ "import datetime\n",
+ "init = { (e[0],e[1]) : e[-1] for e in edges }\n",
+ "init.update ( { (e[1],e[0]) : e[-1] for e in edges } )\n",
+ "\n",
+ "edges_from = { }\n",
+ "for e in edges :\n",
+ " if e[0] not in edges_from : \n",
+ " edges_from[e[0]] = []\n",
+ " if e[1] not in edges_from : \n",
+ " edges_from[e[1]] = []\n",
+ " edges_from[e[0]].append(e)\n",
+ " edges_from[e[1]].append( (e[1], e[0], e[2], e[4], e[3], e[5] ) )\n",
+ " \n",
+ "modif = 1\n",
+ "total_possible_edges = len(edges_from)**2\n",
+ "it = 0\n",
+ "while modif > 0 :\n",
+ " modif = 0\n",
+ " initc = init.copy() # to avoid RuntimeError: dictionary changed size during iteration\n",
+ " s = 0\n",
+ " for i,d in initc.items() :\n",
+ " if i not in possibles : \n",
+ " continue # we skip undesired edges ------------------- addition\n",
+ " fromi2 = edges_from[i[1]]\n",
+ " s += d\n",
+ " for e in fromi2 :\n",
+ " if i[0] == e[1] : # on fait attention à ne pas ajouter de boucle sur le même noeud\n",
+ " continue\n",
+ " new_e = i[0], e[1]\n",
+ " new_d = d + e[-1]\n",
+ " if new_e not in init or init[new_e] > new_d :\n",
+ " init[new_e] = new_d \n",
+ " modif += 1\n",
+ " print(f\"{datetime.datetime.now()} iteration {it} modif {modif} # {len(initc)}/{total_possible_edges}=\" \n",
+ " f{len(initc)*100 / total_possible_edges:0.00f}%\")\n",
+ " it += 1\n",
+ " if it > 20 : \n",
+ " break"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "L'algorithme consiste à regarder les chemins $a \\rightarrow b \\rightarrow c$ et à comparer s'il est plus rapide que $a \\rightarrow c$. 2.6% > 2.2% parce que le filtre est appliqué seulement sur $a \\rightarrow b$. Finalement, on considère les arcs ajoutés puis on retire les arcs originaux."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "original = {(e[0], e[1]): e[-1] for e in edges}\n",
+ "original.update({(e[1], e[0]): e[-1] for e in edges})\n",
+ "additions = {k: v for k, v in init.items() if k not in original}\n",
+ "additions.update({(k[1], k[0]): v for k, v in additions.items()})"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Kruskall\n",
+ "\n",
+ "On trie les arcs par distance croissante, on enlève les arcs qui ne relient pas des noeuds de degré impair puis on les ajoute un par jusqu'à ce qu'il n'y ait plus d'arc de degré impair."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "nb degré impairs 22 nombre d'arcs ajoutés 3648\n",
+ "longueur ajoutée 3.5423464430662346\n",
+ "longueur initiale 17.418504406203844\n"
+ ]
+ }
+ ],
+ "source": [
+ "degre = {}\n",
+ "for k, v in original.items(): # original est symétrique\n",
+ " degre[k[0]] = degre.get(k[0], 0) + 1\n",
+ "\n",
+ "tri = [\n",
+ " (v, k)\n",
+ " for k, v in additions.items()\n",
+ " if degre[k[0]] % 2 == 1 and degre[k[1]] % 2 == 1\n",
+ "]\n",
+ "tri.extend(\n",
+ " [\n",
+ " (v, k)\n",
+ " for k, v in original.items()\n",
+ " if degre[k[0]] % 2 == 1 and degre[k[1]] % 2 == 1\n",
+ " ]\n",
+ ")\n",
+ "tri.sort()\n",
+ "\n",
+ "impairs = sum(v % 2 for k, v in degre.items())\n",
+ "\n",
+ "added_edges = []\n",
+ "\n",
+ "for v, a in tri:\n",
+ " if degre[a[0]] % 2 == 1 and degre[a[1]] % 2 == 1:\n",
+ " # il faut refaire le test car degre peut changer à chaque itération\n",
+ " degre[a[0]] += 1\n",
+ " degre[a[1]] += 1\n",
+ " added_edges.append(a + (v,))\n",
+ " impairs -= 2\n",
+ " if impairs <= 0:\n",
+ " break\n",
+ "\n",
+ "# on vérifie\n",
+ "print(f\"nb degré impairs {impairs} nombre d'arcs ajoutés {len(added_edges)}\")\n",
+ "print(f\"longueur ajoutée {sum( v for a,b,v in added_edges )}\")\n",
+ "print(f\"longueur initiale {sum( e[-1] for e in edges )}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Le nombre de noeuds impairs obtenus à la fin doit être inférieur à 2 pour être sûr de trouver un chemin (mais on choisira 0 pour avoir un circuit eulérien). Mon premier essai n'a pas donné satisfaction (92 noeuds impairs restant) car j'avais choisi un seuil (max_segment / 4) trop petit lors de la sélection des arcs à ajouter. J'ai augmenté le seuil par la suite mais il reste encore 22 noeuds de degré impairs. On a le choix entre augmenter ce seuil mais l'algorithme est déjà long ou chercher dans une autre direction comme laisser l'algorithme de Bellman explorer les noeuds de degré impairs. Ca ne veut pas forcément dire qu'il manque des arcs mais que peut-être ils sont mal choisis. Si l'arc $i \\rightarrow j$ est choisi, l'arc $j \\rightarrow k$ ne le sera pas car $j$ aura un degré pair. Mais dans ce cas, si l'arc $j \\rightarrow k$ était le dernier arc disponible pour combler $k$, on est coincé. On peut augmenter le seuil encore mais cela risquee de prendre du temps et puis cela ne fonctionnerait pas toujours sur tous les jeux de données.\n",
+ "\n",
+ "On pourait alors écrire une sorte d'algorithme itératif qui exécute l'algorithme de Bellman, puis lance celui qui ajoute les arcs. Puis on revient au premier en ajoutant plus d'arcs autour des noeuds problèmatique lors de la seconde étape. L'ensemble est un peu long pour tenir dans un notebook mais le code est celui de la fonction [eulerien_extension](http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx/ensae_teaching_cs/special/rues_paris.html#special.rues_paris.eulerien_extension). Je conseille également la lecture de cet article : [Efficient Algorithms for Eulerian Extension](http://www.akt.tu-berlin.de/fileadmin/fg34/publications-akt/euler_short.pdf) (voir également [On Making Directed Graphs Eulerian](http://arxiv.org/abs/1101.4283)). L'exécution qui suit prend une vingtaine de minutes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "data\n",
+ "start, nb edges 17958\n",
+ "possible_edges\n",
+ "original 17958 / 64382878.0 = 0.00027892508936925745\n",
+ "addition 1312214 / 64382878.0 = 0.020381412586122666\n",
+ "next\n",
+ "iteration 0 modif 72876 # 17958 / 64382878 = 0.03%\n",
+ "iteration 1 modif 119544 # 52421 / 64382878 = 0.08%\n",
+ "iteration 2 modif 177609 # 106511 / 64382878 = 0.17%\n",
+ "iteration 3 modif 247689 # 182680 / 64382878 = 0.28%\n",
+ "iteration 4 modif 327843 # 282626 / 64382878 = 0.44%\n",
+ "iteration 5 modif 413418 # 405980 / 64382878 = 0.63%\n",
+ "iteration 6 modif 496069 # 550546 / 64382878 = 0.86%\n",
+ "iteration 7 modif 561366 # 710517 / 64382878 = 1.10%\n",
+ "iteration 8 modif 598700 # 875788 / 64382878 = 1.36%\n",
+ "iteration 9 modif 600000 # 1034325 / 64382878 = 1.61%\n",
+ "iteration 10 modif 567801 # 1175548 / 64382878 = 1.83%\n",
+ "iteration 11 modif 510076 # 1292961 / 64382878 = 2.01%\n",
+ "iteration 12 modif 433796 # 1384196 / 64382878 = 2.15%\n",
+ "iteration 13 modif 349510 # 1449682 / 64382878 = 2.25%\n",
+ "iteration 14 modif 267371 # 1493038 / 64382878 = 2.32%\n",
+ "iteration 15 modif 194659 # 1519796 / 64382878 = 2.36%\n",
+ "iteration 16 modif 135778 # 1535222 / 64382878 = 2.38%\n",
+ "iteration 17 modif 90864 # 1543743 / 64382878 = 2.40%\n",
+ "iteration 18 modif 58784 # 1548367 / 64382878 = 2.40%\n",
+ "iteration 19 modif 37306 # 1550830 / 64382878 = 2.41%\n",
+ "iteration 20 modif 23232 # 1552160 / 64382878 = 2.41%\n",
+ "nb odd degrees 7318 nb added edges 0\n",
+ "nb odd degrees 28 nb added edges 3645\n",
+ "added length 312.732395725235\n",
+ "initial length 1511.8818424919855\n",
+ "degrees [444, 833, 1112, 1672, 2080, 2218, 2428, 2595, 2767, 2772]\n",
+ "------- nb odd vertices 28 iteration 0\n",
+ "iteration 0 modif 18055 # 1552928 / 64382878 = 2.41%\n",
+ "iteration 1 modif 11117 # 1555011 / 64382878 = 2.42%\n",
+ "iteration 2 modif 8346 # 1556008 / 64382878 = 2.42%\n",
+ "iteration 3 modif 6811 # 1557026 / 64382878 = 2.42%\n",
+ "iteration 4 modif 6056 # 1558080 / 64382878 = 2.42%\n",
+ "iteration 5 modif 5889 # 1559203 / 64382878 = 2.42%\n",
+ "iteration 6 modif 6182 # 1560422 / 64382878 = 2.42%\n",
+ "iteration 7 modif 6606 # 1561720 / 64382878 = 2.43%\n",
+ "iteration 8 modif 7245 # 1563108 / 64382878 = 2.43%\n",
+ "iteration 9 modif 8000 # 1564601 / 64382878 = 2.43%\n",
+ "iteration 10 modif 8813 # 1566180 / 64382878 = 2.43%\n",
+ "iteration 11 modif 9947 # 1567891 / 64382878 = 2.44%\n",
+ "iteration 12 modif 11220 # 1569765 / 64382878 = 2.44%\n",
+ "iteration 13 modif 12595 # 1571750 / 64382878 = 2.44%\n",
+ "iteration 14 modif 14231 # 1573865 / 64382878 = 2.44%\n",
+ "iteration 15 modif 15907 # 1576113 / 64382878 = 2.45%\n",
+ "iteration 16 modif 17720 # 1578466 / 64382878 = 2.45%\n",
+ "iteration 17 modif 19396 # 1580917 / 64382878 = 2.46%\n",
+ "iteration 18 modif 21385 # 1583422 / 64382878 = 2.46%\n",
+ "iteration 19 modif 23468 # 1586072 / 64382878 = 2.46%\n",
+ "iteration 20 modif 25721 # 1588844 / 64382878 = 2.47%\n",
+ "nb odd degrees 7318 nb added edges 0\n",
+ "nb odd degrees 0 nb added edges 3659\n",
+ "added length 341.68448700406753\n",
+ "initial length 1511.8818424919855\n",
+ "degrees []\n",
+ "end, nb added 3659\n"
+ ]
+ }
+ ],
+ "source": [
+ "from ensae_teaching_cs.special.rues_paris import (\n",
+ " eulerien_extension,\n",
+ " distance_paris,\n",
+ " get_data,\n",
+ ")\n",
+ "\n",
+ "print(\"data\")\n",
+ "edges = get_data()\n",
+ "print(f\"start, nb edges {len(edges)}\")\n",
+ "added = eulerien_extension(edges, distance=distance_paris)\n",
+ "print(f\"end, nb added {len(added)}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On enregistre le résultat où on souhaite recommencer à partir de ce moment-là plus tard."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "with open(\"added.txt\", \"w\") as f:\n",
+ " f.write(str(added))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Chemin Eulérien\n",
+ "\n",
+ "A cet instant, on n'a pas vraiment besoin de connaître la longueur du chemin eulérien passant par tous les arcs. Il s'agit de la somme des arcs initiaux et ajoutés (soit environ 334 + 1511). On suppose qu'il n'y qu'une composante connexe. Construire le chemin eulérien fait apparaître quelques difficultés comme la suivante : on parcourt le graphe dans un sens mais on peut laisser de côté une partie du chemin et créer une seconde composante connexe."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from IPython.display import Image\n",
+ "\n",
+ "Image(\"euler.png\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Quelques algorithmes sont disponibles sur cette page [Eulerian_path](http://en.wikipedia.org/wiki/Eulerian_path). L'algorithme de Hierholzer consiste à commencer un chemin eulérien qui peut revenir au premier avant d'avoir tout parcouru. Dans ce cas, on parcourt les noeuds du graphe pour trouver un noeud qui repart ailleurs et qui revient au même noeud. On insert cette boucle dans le chemin initial. Tout d'abord, on construit une structure qui pour chaque noeud associe les noeuds suivant. La fonction [euler_path](http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx/ensae_teaching_cs/special/rues_paris.html#special.rues_paris.eurler_path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[(2121, ['street', 3, 2121, 0.049532522902426074]),\n",
+ " (10363, ['street', 2121, 10363, 0.1474817976215633]),\n",
+ " (3517, ['street', 3517, 10363, 0.10602477572757586]),\n",
+ " (2829, ['street', 2829, 3517, 0.03409802890007801]),\n",
+ " (3515, ['street', 3515, 2829, 0.060836636019222866])]"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "path = euler_path(edges, added_edges)\n",
+ "path[:5]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Le label ``street`` signifie que l'arête provient de l'ensemble des rues, le label ``jump`` signifie que c'est une arête ajoutée. Pour couper le parcours en 8 (8 voitures), il suffit de couper le chemin en 8 parts presque égales en trouvant 8 arêtes ``jump`` presque également réparties le long du chemin eulérien. On peut bien évidemment couper à une arête ``street`` mais celle-là devra faire partie d'un des deux ensembles pas l'arête ``jump`` qui peut être jetée. On peut envisager une approche dichotomique. Couper en deux, recouper en deux chaque intervalle et minimiser sur la meilleur arête ``jump`` du début.\n",
+ "\n",
+ "Il reste maintenant à prendre en compte les sens interdits. Cette modification peut intervenir à deux endroits :\n",
+ "\n",
+ "* Soit le nombre de sens interdit est faible et on peut s'en dépatouiller en parcourant le plus possible des rues dans le bon sens, en choisissant le plus les arcs dans le bon sens lors de la création du chemin eulérien.\n",
+ "* Soit on crée un graphe eulérien orienté (pour chaque noeud, les nombres d'arêtes sortantes et entrantes sont égaux, voir [Euler Circuit in a Directed Graph](http://www.geeksforgeeks.org/euler-circuit-directed-graph/)).\n",
+ "\n",
+ "Dans le cas où on souhaite que les voitures partent toutes du même point et reviennent à ce même point, la solution précédente fournira une borne supérieure (il suffit d'ajouter des arêtes ``jump`` pour amener et ramener les voitures)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Algorithme optimal\n",
+ "\n",
+ "La variante de l'algorithme de [Kruskal](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm) propose une solution approchée pour apparier les noeuds de degré impair. On cherche à minimiser la somme des distances entre les noeuds apparier. Il existe un algorithme optimal pour résoudre ce problème : l'[algorithme d'Edmonds pour les couplages](https://en.wikipedia.org/wiki/Blossom_algorithm)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Variantes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Sens interdit et gaphe orienté\n",
+ "\n",
+ "Si la ville contient des sens interdits, c'est comme si le graphe était orienté. Chaque arc ne peut être parcouru que dans un sens. Il faut alors distinguer les degrés entrants et sortants de chaque noeud. Il faut s'assurer pour chaque noeud que le nombre d'arc entrant et sortant sont identiques. Si ce n'est pas le cas, on se sert de l'algorithme d'appariement pour recréer des arcs."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Windy postman problem\n",
+ "\n",
+ "C'est une variante pour laquelle le coût d'un arc n'est pas le même selon qu'on le parcourt dans un sens ou dans l'autre (à contre sens). Ce problème est [NP complet](https://en.wikipedia.org/wiki/NP-completeness)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_doc/practice/algocompose.rst b/_doc/practice/algocompose.rst
index 3b2d9ed8..2fe78a01 100644
--- a/_doc/practice/algocompose.rst
+++ b/_doc/practice/algocompose.rst
@@ -12,3 +12,4 @@ se construisent sous la forme d'un assemblage d'algorithme plus simple.
algo-base/exercice_morse
../auto_examples/plot_tsp
../auto_examples/plot_einstein_riddle
+ algo-compose/paris_parcours
diff --git a/_doc/practice/algorithm_culture.rst b/_doc/practice/algorithm_culture.rst
index acc19cd5..0ef887d2 100644
--- a/_doc/practice/algorithm_culture.rst
+++ b/_doc/practice/algorithm_culture.rst
@@ -252,7 +252,7 @@ Articles sur des algorithmes
++++++++++++++++++++++++++++
* `Blossom5 `_ **matching**
-* `Local max-cut in smoothed polynomial time `_ **max-cut**
+* `Local max-cut in smoothed polynomial time `_ **max-cut**
* `Expander Flows, Geometric Embeddings and Graph Partitioning `_ **graph partitionning**
* `The Read-Optimized Burrows-Wheeler Transform `_
* `String Periods in the Order-Preserving Model `_
diff --git a/_doc/practice/index.rst b/_doc/practice/index.rst
index ade03aad..c9e17cbe 100644
--- a/_doc/practice/index.rst
+++ b/_doc/practice/index.rst
@@ -4,11 +4,11 @@ Culture et Pratique
La programmation, c'est comme la musique.
Il faut toujours pratiquer régulièrement.
-La première série explore les bases du langage.
+La première série d'exercices explore les bases du langage.
.. toctree::
:maxdepth: 1
- :caption: Langage Python
+ :caption: Exercices sur le langage Python
pystruct
pydata
diff --git a/_unittests/ut_practice/test_rue_paris.py b/_unittests/ut_practice/test_rue_paris.py
new file mode 100644
index 00000000..11610691
--- /dev/null
+++ b/_unittests/ut_practice/test_rue_paris.py
@@ -0,0 +1,1093 @@
+import unittest
+from teachpyx.ext_test_case import ExtTestCase
+from teachpyx.practice.rues_paris import (
+ bellman,
+ kruskal,
+ possible_edges,
+ distance_haversine,
+ graph_degree,
+ eulerien_extension,
+ distance_paris,
+ euler_path,
+ connected_components,
+)
+
+
+class TestRueParis(ExtTestCase):
+ vertices = [
+ (48.873361200000005, 2.3236609),
+ (48.8730454, 2.3235788),
+ (48.876246800000004, 2.3318573000000002),
+ (48.875252700000004, 2.3322956),
+ (48.8735712, 2.3109766),
+ (48.872916700000005, 2.3104976),
+ (48.8433599, 2.3415645),
+ (48.8438889, 2.3392425),
+ (48.898623, 2.3656344000000002),
+ (48.898654400000005, 2.3666086),
+ (48.878685000000004, 2.3514280000000003),
+ (48.879387400000006, 2.3516223000000003),
+ (48.872115900000004, 2.3392093000000003),
+ (48.8722422, 2.3382998),
+ (48.843958400000005, 2.4134804),
+ (48.8418774, 2.4136059000000003),
+ (48.8632179, 2.2878296000000002),
+ (48.863393, 2.2874957),
+ (48.8781848, 2.3522114000000003),
+ (48.8777526, 2.3511422),
+ (48.849360700000005, 2.3317293),
+ (48.849249500000006, 2.3309420000000003),
+ (48.8556727, 2.3366922000000003),
+ (48.8562785, 2.3366043000000003),
+ (48.8535047, 2.3615469),
+ (48.8535813, 2.3614117),
+ (48.8639445, 2.3755101),
+ (48.8648776, 2.3748282),
+ (48.8551694, 2.261925),
+ (48.855523100000006, 2.2621554),
+ (48.891942400000005, 2.3223583000000003),
+ (48.8917226, 2.3230341),
+ (48.828755300000005, 2.3166122000000002),
+ (48.829231400000005, 2.3173138),
+ (48.8492437, 2.2750144000000003),
+ (48.8494856, 2.2742921000000003),
+ (48.894308900000006, 2.3594273),
+ (48.894249200000004, 2.3594347),
+ (48.8640516, 2.3624232000000003),
+ (48.864375900000006, 2.3618135000000002),
+ (48.8494077, 2.4151688),
+ (48.851955200000006, 2.4151274000000003),
+ (48.8601674, 2.3820656000000002),
+ (48.8610946, 2.3811391),
+ (48.8533142, 2.3459001),
+ (48.8537722, 2.344293),
+ (48.8423542, 2.2941794),
+ (48.8404633, 2.2957232000000003),
+ (48.836452200000004, 2.2592016),
+ (48.8371057, 2.2601667),
+ (48.876521200000006, 2.3127781),
+ (48.8776163, 2.3125533000000003),
+ (48.849343700000006, 2.3682517),
+ (48.8515298, 2.3692419),
+ (48.8936245, 2.3300976),
+ (48.893475800000004, 2.329429),
+ (48.842168300000004, 2.2855643000000003),
+ (48.842397500000004, 2.2850610000000002),
+ (48.8785497, 2.3505446),
+ (48.8794977, 2.3508043),
+ (48.851695600000006, 2.3487173),
+ (48.8514083, 2.3493394000000003),
+ (48.8848719, 2.2979169),
+ (48.8851079, 2.2978681),
+ (48.86544850000001, 2.3687925),
+ (48.8652585, 2.3680487),
+ (48.8384706, 2.3507640000000003),
+ (48.838631500000005, 2.3515752),
+ (48.8475101, 2.4064010000000002),
+ (48.8476775, 2.4064302),
+ (48.8403009, 2.3460075000000002),
+ (48.8398089, 2.3472132),
+ (48.896738500000005, 2.3384891000000003),
+ (48.8967758, 2.3384491),
+ (48.8360859, 2.3003709000000003),
+ (48.8367408, 2.299553),
+ (48.829905800000006, 2.3341749000000003),
+ (48.830201300000006, 2.3348945000000003),
+ (48.81933, 2.3619721),
+ (48.819208700000004, 2.3620485),
+ (48.893085500000005, 2.3157935000000003),
+ (48.8929655, 2.3159988),
+ (48.8817426, 2.3738127),
+ (48.881476600000006, 2.3743903),
+ (48.871239900000006, 2.3605841),
+ (48.871501800000004, 2.3603519),
+ (48.8494262, 2.3956660000000003),
+ (48.8498279, 2.395527),
+ (48.882198900000006, 2.3044874),
+ (48.8824861, 2.3055082000000002),
+ (48.892683600000005, 2.3400773000000004),
+ (48.891423200000006, 2.3396854),
+ (48.828216600000005, 2.3170147),
+ (48.828375, 2.3171796000000002),
+ (48.8771033, 2.4069115),
+ (48.8772473, 2.4067931000000002),
+ (48.8404803, 2.3794622000000003),
+ (48.8405416, 2.3791428000000003),
+ (48.8708554, 2.2850300000000003),
+ (48.8698787, 2.2852855),
+ (48.878974400000004, 2.3557592),
+ (48.8782082, 2.3555521),
+ (48.8599399, 2.2907052),
+ (48.860304500000005, 2.2911493000000003),
+ (48.8679081, 2.3866252),
+ (48.8682901, 2.3863284),
+ (48.827594100000006, 2.32099),
+ (48.8261705, 2.3195845),
+ (48.8445667, 2.2870145),
+ (48.8449425, 2.2860621),
+ (48.8389649, 2.3585651000000003),
+ (48.838711800000006, 2.3577637),
+ (48.876016, 2.3401507),
+ (48.8766649, 2.3394591),
+ (48.835332400000006, 2.3733283000000003),
+ (48.8354979, 2.3731241),
+ (48.8453074, 2.3982187),
+ (48.84526210000001, 2.3987593),
+ (48.864641000000006, 2.3035632),
+ (48.8639207, 2.2989008),
+ (48.874107900000006, 2.3397429),
+ (48.8747843, 2.3397888),
+ (48.862969, 2.3418717),
+ (48.8623776, 2.3415815),
+ (48.823074500000004, 2.3253825000000004),
+ (48.8221222, 2.3250976000000003),
+ (48.8856551, 2.2916608000000003),
+ (48.8860898, 2.2924402),
+ (48.826483100000004, 2.3418208000000003),
+ (48.8270119, 2.3420461),
+ (48.8877511, 2.3063884000000003),
+ (48.8889805, 2.304241),
+ (48.8761869, 2.3194496),
+ (48.875665500000004, 2.3196156),
+ (48.8777215, 2.3315215),
+ (48.8776105, 2.3309402),
+ (48.837042000000004, 2.2565999000000003),
+ (48.837166, 2.2565265),
+ (48.8581033, 2.3820198),
+ (48.8593835, 2.3828233),
+ (48.832689200000004, 2.2883536),
+ (48.8334051, 2.289441),
+ (48.891664000000006, 2.3349628),
+ (48.8918514, 2.3335172),
+ (48.8423742, 2.3535862),
+ (48.842243, 2.3536228),
+ (48.8539302, 2.3648526000000003),
+ (48.853958500000005, 2.3647523),
+ (48.8454689, 2.2824441),
+ (48.845120200000004, 2.2830738),
+ (48.8410742, 2.2546347),
+ (48.8410893, 2.2550578000000003),
+ (48.868929300000005, 2.3146814),
+ (48.8693837, 2.3132517000000004),
+ (48.876280900000005, 2.3583099),
+ (48.876194700000006, 2.3582784),
+ (48.876635500000006, 2.3486800000000003),
+ (48.8767458, 2.3472677),
+ (48.851683200000004, 2.3420153000000004),
+ (48.8511144, 2.3415342000000003),
+ (48.8790045, 2.3147799),
+ (48.8794399, 2.3142755),
+ (48.8616691, 2.3448132),
+ (48.8617945, 2.3442988000000002),
+ (48.835680800000006, 2.3112781),
+ (48.836246, 2.3102979),
+ (48.855284000000005, 2.2895261000000002),
+ (48.8561612, 2.2931029),
+ (48.824318500000004, 2.3636902),
+ (48.8249517, 2.3624289000000003),
+ (48.8628093, 2.2918135),
+ (48.862751100000004, 2.2920106000000002),
+ (48.8681819, 2.398056),
+ (48.868529800000005, 2.3992063000000003),
+ (48.8356738, 2.3249401),
+ (48.8353905, 2.3257185000000002),
+ (48.8624171, 2.3103990000000003),
+ (48.8624275, 2.3110419),
+ (48.871344, 2.3177128000000002),
+ (48.871694000000005, 2.318445),
+ (48.8774226, 2.349085),
+ (48.8771841, 2.3489544),
+ (48.87938320000001, 2.3370294),
+ (48.879117900000004, 2.3369039000000003),
+ (48.8821905, 2.3011631),
+ (48.883269500000004, 2.3019395),
+ (48.8673537, 2.3515468),
+ (48.867840900000004, 2.3517371000000002),
+ (48.848900300000004, 2.3404672),
+ (48.847503, 2.3408323),
+ (48.866788, 2.3100655000000003),
+ (48.8668318, 2.3102252),
+ (48.8591447, 2.3749638),
+ (48.8587694, 2.3737956000000002),
+ (48.8730794, 2.2970525),
+ (48.8731894, 2.297116),
+ (48.8534328, 2.2973824),
+ (48.853697700000005, 2.2969404),
+ (48.829909900000004, 2.3677045000000003),
+ (48.8293831, 2.3686139),
+ ]
+ edges = [
+ (
+ 0,
+ 1,
+ 1,
+ (48.873361200000005, 2.3236609),
+ (48.8730454, 2.3235788),
+ 0.0003262974869682125,
+ ),
+ (
+ 2,
+ 3,
+ 1,
+ (48.876246800000004, 2.3318573000000002),
+ (48.875252700000004, 2.3322956),
+ 0.0010864353179087373,
+ ),
+ (
+ 4,
+ 5,
+ 2,
+ (48.8735712, 2.3109766),
+ (48.872916700000005, 2.3104976),
+ 0.0008110556392718516,
+ ),
+ (
+ 6,
+ 7,
+ 1,
+ (48.8433599, 2.3415645),
+ (48.8438889, 2.3392425),
+ 0.0023814963783302164,
+ ),
+ (
+ 8,
+ 9,
+ 2,
+ (48.898623, 2.3656344000000002),
+ (48.898654400000005, 2.3666086),
+ 0.0009747059043630242,
+ ),
+ (
+ 10,
+ 11,
+ 1,
+ (48.878685000000004, 2.3514280000000003),
+ (48.879387400000006, 2.3516223000000003),
+ 0.0007287786014985378,
+ ),
+ (
+ 12,
+ 13,
+ 1,
+ (48.872115900000004, 2.3392093000000003),
+ (48.8722422, 2.3382998),
+ 0.000918227607948986,
+ ),
+ (
+ 14,
+ 15,
+ 1,
+ (48.843958400000005, 2.4134804),
+ (48.8418774, 2.4136059000000003),
+ 0.0020847808637880117,
+ ),
+ (
+ 16,
+ 17,
+ 1,
+ (48.8632179, 2.2878296000000002),
+ (48.863393, 2.2874957),
+ 0.00037702681602255083,
+ ),
+ (
+ 18,
+ 19,
+ 2,
+ (48.8781848, 2.3522114000000003),
+ (48.8777526, 2.3511422),
+ 0.0011532499642313606,
+ ),
+ (
+ 20,
+ 21,
+ 1,
+ (48.849360700000005, 2.3317293),
+ (48.849249500000006, 2.3309420000000003),
+ 0.000795114287382352,
+ ),
+ (
+ 22,
+ 23,
+ 1,
+ (48.8556727, 2.3366922000000003),
+ (48.8562785, 2.3366043000000003),
+ 0.0006121438148041292,
+ ),
+ (
+ 24,
+ 25,
+ 2,
+ (48.8535047, 2.3615469),
+ (48.8535813, 2.3614117),
+ 0.00015539176297330173,
+ ),
+ (
+ 26,
+ 27,
+ 2,
+ (48.8639445, 2.3755101),
+ (48.8648776, 2.3748282),
+ 0.0011557089685536276,
+ ),
+ (
+ 28,
+ 29,
+ 2,
+ (48.8551694, 2.261925),
+ (48.855523100000006, 2.2621554),
+ 0.0004221230270947142,
+ ),
+ (
+ 30,
+ 31,
+ 1,
+ (48.891942400000005, 2.3223583000000003),
+ (48.8917226, 2.3230341),
+ 0.0007106459596741995,
+ ),
+ (
+ 32,
+ 33,
+ 1,
+ (48.828755300000005, 2.3166122000000002),
+ (48.829231400000005, 2.3173138),
+ 0.0008478878286659365,
+ ),
+ (
+ 34,
+ 35,
+ 1,
+ (48.8492437, 2.2750144000000003),
+ (48.8494856, 2.2742921000000003),
+ 0.0007617302015802999,
+ ),
+ (
+ 36,
+ 37,
+ 2,
+ (48.894308900000006, 2.3594273),
+ (48.894249200000004, 2.3594347),
+ 6.015687824477596e-05,
+ ),
+ (
+ 38,
+ 39,
+ 1,
+ (48.8640516, 2.3624232000000003),
+ (48.864375900000006, 2.3618135000000002),
+ 0.0006905827828738192,
+ ),
+ (
+ 40,
+ 41,
+ 1,
+ (48.8494077, 2.4151688),
+ (48.851955200000006, 2.4151274000000003),
+ 0.0025478363781901324,
+ ),
+ (
+ 42,
+ 43,
+ 1,
+ (48.8601674, 2.3820656000000002),
+ (48.8610946, 2.3811391),
+ 0.0013107639337423486,
+ ),
+ (
+ 44,
+ 45,
+ 1,
+ (48.8533142, 2.3459001),
+ (48.8537722, 2.344293),
+ 0.0016710877924281285,
+ ),
+ (
+ 46,
+ 47,
+ 1,
+ (48.8423542, 2.2941794),
+ (48.8404633, 2.2957232000000003),
+ 0.0024410696938019956,
+ ),
+ (
+ 48,
+ 49,
+ 1,
+ (48.836452200000004, 2.2592016),
+ (48.8371057, 2.2601667),
+ 0.0011655386136882784,
+ ),
+ (
+ 50,
+ 51,
+ 1,
+ (48.876521200000006, 2.3127781),
+ (48.8776163, 2.3125533000000003),
+ 0.0011179351725326468,
+ ),
+ (
+ 52,
+ 53,
+ 1,
+ (48.849343700000006, 2.3682517),
+ (48.8515298, 2.3692419),
+ 0.002399901925075645,
+ ),
+ (
+ 54,
+ 55,
+ 2,
+ (48.8936245, 2.3300976),
+ (48.893475800000004, 2.329429),
+ 0.0006849362379076621,
+ ),
+ (
+ 56,
+ 57,
+ 2,
+ (48.842168300000004, 2.2855643000000003),
+ (48.842397500000004, 2.2850610000000002),
+ 0.0005530312197335353,
+ ),
+ (
+ 58,
+ 59,
+ 1,
+ (48.8785497, 2.3505446),
+ (48.8794977, 2.3508043),
+ 0.0009829283239392353,
+ ),
+ (
+ 60,
+ 61,
+ 1,
+ (48.851695600000006, 2.3487173),
+ (48.8514083, 2.3493394000000003),
+ 0.0006852369663133512,
+ ),
+ (
+ 62,
+ 63,
+ 1,
+ (48.8848719, 2.2979169),
+ (48.8851079, 2.2978681),
+ 0.00024099261399570973,
+ ),
+ (
+ 64,
+ 65,
+ 1,
+ (48.86544850000001, 2.3687925),
+ (48.8652585, 2.3680487),
+ 0.0007676838151226447,
+ ),
+ (
+ 66,
+ 67,
+ 1,
+ (48.8384706, 2.3507640000000003),
+ (48.838631500000005, 2.3515752),
+ 0.0008270031741179077,
+ ),
+ (
+ 68,
+ 69,
+ 1,
+ (48.8475101, 2.4064010000000002),
+ (48.8476775, 2.4064302),
+ 0.00016992763165754094,
+ ),
+ (
+ 70,
+ 71,
+ 2,
+ (48.8403009, 2.3460075000000002),
+ (48.8398089, 2.3472132),
+ 0.0013022198316724116,
+ ),
+ (
+ 72,
+ 73,
+ 2,
+ (48.896738500000005, 2.3384891000000003),
+ (48.8967758, 2.3384491),
+ 5.469268689390239e-05,
+ ),
+ (
+ 74,
+ 75,
+ 1,
+ (48.8360859, 2.3003709000000003),
+ (48.8367408, 2.299553),
+ 0.0010477854837711283,
+ ),
+ (
+ 76,
+ 77,
+ 1,
+ (48.829905800000006, 2.3341749000000003),
+ (48.830201300000006, 2.3348945000000003),
+ 0.0007779102840302752,
+ ),
+ (
+ 78,
+ 79,
+ 1,
+ (48.81933, 2.3619721),
+ (48.819208700000004, 2.3620485),
+ 0.00014335497898282511,
+ ),
+ (
+ 80,
+ 81,
+ 2,
+ (48.893085500000005, 2.3157935000000003),
+ (48.8929655, 2.3159988),
+ 0.00023779842304041672,
+ ),
+ (
+ 82,
+ 83,
+ 1,
+ (48.8817426, 2.3738127),
+ (48.881476600000006, 2.3743903),
+ 0.0006359070372292929,
+ ),
+ (
+ 84,
+ 85,
+ 2,
+ (48.871239900000006, 2.3605841),
+ (48.871501800000004, 2.3603519),
+ 0.0003500120712191077,
+ ),
+ (
+ 86,
+ 87,
+ 1,
+ (48.8494262, 2.3956660000000003),
+ (48.8498279, 2.395527),
+ 0.0004250692767046083,
+ ),
+ (
+ 88,
+ 89,
+ 1,
+ (48.882198900000006, 2.3044874),
+ (48.8824861, 2.3055082000000002),
+ 0.001060432213768252,
+ ),
+ (
+ 90,
+ 91,
+ 1,
+ (48.892683600000005, 2.3400773000000004),
+ (48.891423200000006, 2.3396854),
+ 0.0013199218802638546,
+ ),
+ (
+ 92,
+ 93,
+ 1,
+ (48.828216600000005, 2.3170147),
+ (48.828375, 2.3171796000000002),
+ 0.0002286538213084464,
+ ),
+ (
+ 94,
+ 95,
+ 1,
+ (48.8771033, 2.4069115),
+ (48.8772473, 2.4067931000000002),
+ 0.00018642574929344586,
+ ),
+ (
+ 96,
+ 97,
+ 1,
+ (48.8404803, 2.3794622000000003),
+ (48.8405416, 2.3791428000000003),
+ 0.00032522922685364927,
+ ),
+ (
+ 98,
+ 99,
+ 2,
+ (48.8708554, 2.2850300000000003),
+ (48.8698787, 2.2852855),
+ 0.0010095658175694848,
+ ),
+ (
+ 100,
+ 101,
+ 1,
+ (48.878974400000004, 2.3557592),
+ (48.8782082, 2.3555521),
+ 0.0007936956910564454,
+ ),
+ (
+ 102,
+ 103,
+ 1,
+ (48.8599399, 2.2907052),
+ (48.860304500000005, 2.2911493000000003),
+ 0.0005745937434427083,
+ ),
+ (
+ 104,
+ 105,
+ 1,
+ (48.8679081, 2.3866252),
+ (48.8682901, 2.3863284),
+ 0.00048375018346404227,
+ ),
+ (
+ 106,
+ 107,
+ 1,
+ (48.827594100000006, 2.32099),
+ (48.8261705, 2.3195845),
+ 0.0020005167357479573,
+ ),
+ (
+ 108,
+ 109,
+ 1,
+ (48.8445667, 2.2870145),
+ (48.8449425, 2.2860621),
+ 0.0010238610257259065,
+ ),
+ (
+ 110,
+ 111,
+ 2,
+ (48.8389649, 2.3585651000000003),
+ (48.838711800000006, 2.3577637),
+ 0.0008404174974367317,
+ ),
+ (
+ 112,
+ 113,
+ 1,
+ (48.876016, 2.3401507),
+ (48.8766649, 2.3394591),
+ 0.0009483574062568779,
+ ),
+ (
+ 114,
+ 115,
+ 2,
+ (48.835332400000006, 2.3733283000000003),
+ (48.8354979, 2.3731241),
+ 0.0002628457532435862,
+ ),
+ (
+ 116,
+ 117,
+ 2,
+ (48.8453074, 2.3982187),
+ (48.84526210000001, 2.3987593),
+ 0.0005424946543511399,
+ ),
+ (
+ 118,
+ 119,
+ 1,
+ (48.864641000000006, 2.3035632),
+ (48.8639207, 2.2989008),
+ 0.004717711929527524,
+ ),
+ (
+ 120,
+ 121,
+ 1,
+ (48.874107900000006, 2.3397429),
+ (48.8747843, 2.3397888),
+ 0.0006779555811369695,
+ ),
+ (
+ 122,
+ 123,
+ 2,
+ (48.862969, 2.3418717),
+ (48.8623776, 2.3415815),
+ 0.0006587639941564931,
+ ),
+ (
+ 124,
+ 125,
+ 1,
+ (48.823074500000004, 2.3253825000000004),
+ (48.8221222, 2.3250976000000003),
+ 0.0009940036720269252,
+ ),
+ (
+ 126,
+ 127,
+ 1,
+ (48.8856551, 2.2916608000000003),
+ (48.8860898, 2.2924402),
+ 0.0008924284004890405,
+ ),
+ (
+ 128,
+ 129,
+ 1,
+ (48.826483100000004, 2.3418208000000003),
+ (48.8270119, 2.3420461),
+ 0.0005747952070065542,
+ ),
+ (
+ 130,
+ 131,
+ 1,
+ (48.8877511, 2.3063884000000003),
+ (48.8889805, 2.304241),
+ 0.002474419350069803,
+ ),
+ (
+ 132,
+ 133,
+ 2,
+ (48.8761869, 2.3194496),
+ (48.875665500000004, 2.3196156),
+ 0.0005471873171013459,
+ ),
+ (
+ 134,
+ 135,
+ 1,
+ (48.8777215, 2.3315215),
+ (48.8776105, 2.3309402),
+ 0.0005918029148282243,
+ ),
+ (
+ 136,
+ 137,
+ 1,
+ (48.837042000000004, 2.2565999000000003),
+ (48.837166, 2.2565265),
+ 0.00014409566266867946,
+ ),
+ (
+ 138,
+ 139,
+ 1,
+ (48.8581033, 2.3820198),
+ (48.8593835, 2.3828233),
+ 0.0015114642867070992,
+ ),
+ (
+ 140,
+ 141,
+ 1,
+ (48.832689200000004, 2.2883536),
+ (48.8334051, 2.289441),
+ 0.0013019030570644572,
+ ),
+ (
+ 142,
+ 143,
+ 1,
+ (48.891664000000006, 2.3349628),
+ (48.8918514, 2.3335172),
+ 0.0014576961686158724,
+ ),
+ (
+ 144,
+ 145,
+ 1,
+ (48.8423742, 2.3535862),
+ (48.842243, 2.3536228),
+ 0.00013620939761850324,
+ ),
+ (
+ 146,
+ 147,
+ 2,
+ (48.8539302, 2.3648526000000003),
+ (48.853958500000005, 2.3647523),
+ 0.00010421602564026223,
+ ),
+ (
+ 148,
+ 149,
+ 1,
+ (48.8454689, 2.2824441),
+ (48.845120200000004, 2.2830738),
+ 0.0007198012086660956,
+ ),
+ (
+ 150,
+ 151,
+ 1,
+ (48.8410742, 2.2546347),
+ (48.8410893, 2.2550578000000003),
+ 0.00042336936592088766,
+ ),
+ (
+ 152,
+ 153,
+ 1,
+ (48.868929300000005, 2.3146814),
+ (48.8693837, 2.3132517000000004),
+ 0.0015001738065953281,
+ ),
+ (
+ 154,
+ 155,
+ 1,
+ (48.876280900000005, 2.3583099),
+ (48.876194700000006, 2.3582784),
+ 9.177521451741075e-05,
+ ),
+ (
+ 156,
+ 157,
+ 1,
+ (48.876635500000006, 2.3486800000000003),
+ (48.8767458, 2.3472677),
+ 0.001416600642382841,
+ ),
+ (
+ 158,
+ 159,
+ 1,
+ (48.851683200000004, 2.3420153000000004),
+ (48.8511144, 2.3415342000000003),
+ 0.0007449769459546838,
+ ),
+ (
+ 160,
+ 161,
+ 2,
+ (48.8790045, 2.3147799),
+ (48.8794399, 2.3142755),
+ 0.0006663276371280349,
+ ),
+ (
+ 162,
+ 163,
+ 1,
+ (48.8616691, 2.3448132),
+ (48.8617945, 2.3442988000000002),
+ 0.0005294643708504993,
+ ),
+ (
+ 164,
+ 165,
+ 1,
+ (48.835680800000006, 2.3112781),
+ (48.836246, 2.3102979),
+ 0.0011314782719947791,
+ ),
+ (
+ 166,
+ 167,
+ 1,
+ (48.855284000000005, 2.2895261000000002),
+ (48.8561612, 2.2931029),
+ 0.0036827948734616056,
+ ),
+ (
+ 168,
+ 169,
+ 1,
+ (48.824318500000004, 2.3636902),
+ (48.8249517, 2.3624289000000003),
+ 0.001411318507635477,
+ ),
+ (
+ 170,
+ 171,
+ 1,
+ (48.8628093, 2.2918135),
+ (48.862751100000004, 2.2920106000000002),
+ 0.00020551313826585387,
+ ),
+ (
+ 172,
+ 173,
+ 1,
+ (48.8681819, 2.398056),
+ (48.868529800000005, 2.3992063000000003),
+ 0.0012017589192520163,
+ ),
+ (
+ 174,
+ 175,
+ 1,
+ (48.8356738, 2.3249401),
+ (48.8353905, 2.3257185000000002),
+ 0.000828351042734914,
+ ),
+ (
+ 176,
+ 177,
+ 1,
+ (48.8624171, 2.3103990000000003),
+ (48.8624275, 2.3110419),
+ 0.0006429841133339727,
+ ),
+ (
+ 178,
+ 179,
+ 1,
+ (48.871344, 2.3177128000000002),
+ (48.871694000000005, 2.318445),
+ 0.0008115521178599309,
+ ),
+ (
+ 180,
+ 181,
+ 1,
+ (48.8774226, 2.349085),
+ (48.8771841, 2.3489544),
+ 0.0002719165497001383,
+ ),
+ (
+ 182,
+ 183,
+ 2,
+ (48.87938320000001, 2.3370294),
+ (48.879117900000004, 2.3369039000000003),
+ 0.0002934865243945523,
+ ),
+ (
+ 184,
+ 185,
+ 2,
+ (48.8821905, 2.3011631),
+ (48.883269500000004, 2.3019395),
+ 0.0013292998006503502,
+ ),
+ (
+ 186,
+ 187,
+ 1,
+ (48.8673537, 2.3515468),
+ (48.867840900000004, 2.3517371000000002),
+ 0.0005230467761128879,
+ ),
+ (
+ 188,
+ 189,
+ 1,
+ (48.848900300000004, 2.3404672),
+ (48.847503, 2.3408323),
+ 0.0014442109610448733,
+ ),
+ (
+ 190,
+ 191,
+ 1,
+ (48.866788, 2.3100655000000003),
+ (48.8668318, 2.3102252),
+ 0.00016559749394223043,
+ ),
+ (
+ 192,
+ 193,
+ 1,
+ (48.8591447, 2.3749638),
+ (48.8587694, 2.3737956000000002),
+ 0.0012270050244400786,
+ ),
+ (
+ 194,
+ 195,
+ 1,
+ (48.8730794, 2.2970525),
+ (48.8731894, 2.297116),
+ 0.00012701279463055954,
+ ),
+ (
+ 196,
+ 197,
+ 2,
+ (48.8534328, 2.2973824),
+ (48.853697700000005, 2.2969404),
+ 0.0005153018629915703,
+ ),
+ (
+ 198,
+ 199,
+ 1,
+ (48.829909900000004, 2.3677045000000003),
+ (48.8293831, 2.3686139),
+ 0.0010509646045432734,
+ ),
+ ]
+
+ def test_algo(self):
+ edges = self.edges
+ max_segment = max(e[-1] for e in edges)
+ possibles = possible_edges(edges, max_segment / 8)
+ init = bellman(edges, allow=lambda e: e in possibles)
+ init = bellman(edges, allow=lambda e: e in possibles, init=init)
+ added = kruskal(edges, init)
+ d = graph_degree(edges + added)
+ allow = sorted([k for k, v in d.items() if v % 2 == 1])
+ allow = set(allow)
+ init = bellman(
+ edges,
+ allow=lambda e: e in possibles or e[0] in allow or e[1] in allow,
+ init=init,
+ )
+ added = kruskal(edges, init)
+ d = graph_degree(edges + added)
+ allow = sorted([k for k, v in d.items() if v % 2 == 1])
+ self.assertEmpty(list(allow))
+
+ def test_algo2(self):
+ edges = self.edges
+ edges = edges[:1000]
+ added = eulerien_extension(edges, alpha=1 / 8)
+ assert len(added) > 0
+
+ def test_algo_euler4(self):
+ edges = self.edges
+
+ vertices = {}
+ for e in edges:
+ for i in range(0, 2):
+ _ = e[i]
+ p = e[i + 3]
+ vertices[_] = p
+
+ connex = connected_components(edges)
+ v = [v for k, v in connex.items()]
+ mi, ma = min(v), max(v)
+
+ while mi != ma:
+ edges.append(
+ (
+ mi,
+ ma,
+ 2,
+ vertices[mi],
+ vertices[ma],
+ distance_haversine(*(vertices[mi] + vertices[ma])),
+ )
+ )
+
+ connex = connected_components(edges)
+ v = [v for k, v in connex.items()]
+ mi, ma = min(v), max(v)
+
+ added = eulerien_extension(edges, distance=distance_paris)
+
+ graph_degree(edges + added)
+
+ path = euler_path(edges, added)
+ alls = edges + added
+ self.assertEqual(len(alls), len(path))
+
+ def test_algo3(self):
+ edges = self.edges
+ added = eulerien_extension(edges, distance=distance_paris)
+ self.assertNotEmpty(added)
+
+
+if __name__ == "__main__":
+ unittest.main(verbosity=2)
diff --git a/_unittests/ut_tools/test_data_helper.py b/_unittests/ut_tools/test_data_helper.py
new file mode 100644
index 00000000..631a15ca
--- /dev/null
+++ b/_unittests/ut_tools/test_data_helper.py
@@ -0,0 +1,14 @@
+import unittest
+from teachpyx.ext_test_case import ExtTestCase
+from teachpyx.tools.data_helper import download_and_unzip
+
+
+class TestDataHelper(ExtTestCase):
+ def test_download_and_unzip(self):
+ url = "https://github.com/sdpython/teachpyx/raw/paris/_data/paris_54000.zip"
+ data = download_and_unzip(url, ".", verbose=True)
+ self.assertEqual(data[0].strip("/\\."), "paris_54000.txt")
+
+
+if __name__ == "__main__":
+ unittest.main(verbosity=2)
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 9d6f88f4..c06ffacd 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -18,7 +18,7 @@ pytest
pytest-cov
ruff
scikit-learn>=1.2
-sphinx
+sphinx<7.2 # furo raises an error
sphinx-gallery
sphinx-issues
sphinxcontrib-blockdiag
diff --git a/teachpyx/practice/__init__.py b/teachpyx/practice/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/teachpyx/practice/__init__.py
@@ -0,0 +1 @@
+
diff --git a/teachpyx/practice/rues_paris.py b/teachpyx/practice/rues_paris.py
new file mode 100644
index 00000000..720754d1
--- /dev/null
+++ b/teachpyx/practice/rues_paris.py
@@ -0,0 +1,531 @@
+# -*- coding: utf-8 -*-
+from typing import Callable, Dict, List, Optional, Tuple
+import random
+import math
+from ..tools.data_helper import download_and_unzip
+
+
+def distance_paris(lat1: float, lng1: float, lat2: float, lng2: float) -> float:
+ """
+ Distance euclidienne approchant la distance de Haversine
+ (uniquement pour Paris).
+ """
+ return ((lat1 - lat2) ** 2 + (lng1 - lng2) ** 2) ** 0.5 * 90
+
+
+def distance_haversine(lat1: float, lng1: float, lat2: float, lng2: float) -> float:
+ """
+ Calcule la distance de Haversine
+ `Haversine formula `_
+ """
+ radius = 6371
+ dlat = math.radians(lat2 - lat1)
+ dlon = math.radians(lng2 - lng1)
+ a = math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos(
+ math.radians(lat1)
+ ) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) * math.sin(dlon / 2)
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
+ d = radius * c
+ return d
+
+
+def get_data(
+ url: str = "https://github.com/sdpython/teachpyx/raw/paris/_data/paris_54000.zip",
+ dest: str = ".",
+ timeout: int = 10,
+ verbose: bool = False,
+ keep: int = -1,
+) -> List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]]:
+ """
+ Retourne les données des rues de Paris. On suppose que les arcs sont uniques
+ et qu'il si :math:`j \\rightarrow k` est présent,
+ :math:`j \\rightarrow k` ne l'est pas.
+ Ceci est vérifié par un test.
+
+ :param url: location of the data
+ :param dest: répertoire dans lequel télécharger les données
+ :param timeout: timeout (seconds) when estabishing the connection
+ :param verbose: affiche le progrès
+ :param keep: garde tout si la valeur est -1, sinon garde les 1000 premières rues
+ :return: liste d'arcs
+
+ Un arc est défini par un 6-uple contenant les informations suivantes :
+
+ - v1: indice du premier noeud
+ - v2: indice du second noeud
+ - ways: sens unique ou deux sens
+ - p1: coordonnées du noeud 1
+ - p2: coordonnées du noeud 2
+ - d: distance
+ """
+ data = download_and_unzip(url=url, timeout=timeout, verbose=verbose)
+ name = data[0]
+ with open(name, "r") as f:
+ lines = f.readlines()
+
+ vertices = []
+ edges = []
+ for i, line in enumerate(lines):
+ spl = line.strip("\n\r").split(" ")
+ if len(spl) == 2:
+ vertices.append((float(spl[0]), float(spl[1])))
+ elif len(spl) == 5 and i > 0:
+ v1, v2 = int(spl[0]), int(spl[1])
+ ways = int(spl[2]) # dans les deux sens ou pas
+ p1 = vertices[v1]
+ p2 = vertices[v2]
+ edges.append(
+ (v1, v2, ways, p1, p2, distance_haversine(p1[0], p1[1], p2[0], p2[1]))
+ )
+ elif i > 0:
+ raise RuntimeError(f"Unable to interpret line {i}: {line!r}")
+
+ pairs = {}
+ for e in edges:
+ p = e[:2]
+ if p in pairs:
+ raise ValueError(f"Unexpected pairs, already present: {e}")
+ pairs[p] = True
+
+ if keep is not None:
+ short_edges = edges[:keep]
+ new_vertices = {}
+ edges = []
+ for edge in short_edges:
+ p1, p2 = edge[-3:-1]
+ if p1 not in new_vertices:
+ new_vertices[p1] = len(new_vertices)
+ if p2 not in new_vertices:
+ new_vertices[p2] = len(new_vertices)
+ i1, i2 = new_vertices[p1], new_vertices[p2]
+ edges.append((i1, i2, edge[2], p1, p2, edge[-1]))
+ items = [(v, i) for i, v in new_vertices.items()]
+ items.sort()
+ vertices = [_[1] for _ in items]
+
+ return edges
+
+
+def graph_degree(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]]
+) -> Dict[Tuple[int, int], int]:
+ """
+ Calcul le degré de chaque noeud.
+
+ :param edges: list des arcs
+ :return: degrés
+ """
+ nb_edges = {}
+ for edge in edges:
+ v1, v2 = edge[:2]
+ nb_edges[v1] = nb_edges.get(v1, 0) + 1
+ nb_edges[v2] = nb_edges.get(v2, 0) + 1
+ return nb_edges
+
+
+def possible_edges(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]],
+ threshold: float,
+ distance: Callable = distance_haversine,
+):
+ """
+ Construit la liste de tous les arcs possibles en
+ filtrant sur la distance à vol d'oiseau.
+
+ :param edges: list des arcs
+ :param threshold: seuil au-delà duquel deux noeuds ne seront pas connectés
+ :param distance: la distance de Haversine est beaucoup trop
+ longue sur de grands graphes, on peut la changer
+ :return: arcs possibles (symétrique --> incluant edges)
+ """
+ vertices: Dict[int : Tuple[float, float]] = {e[0]: e[3] for e in edges}
+ vertices.update({e[1]: e[4] for e in edges})
+
+ possibles = {(e[0], e[1]): e[-1] for e in edges}
+ possibles.update({(e[1], e[0]): e[-1] for e in edges})
+ # initial = possibles.copy()
+ for i1, v1 in vertices.items():
+ for i2, v2 in vertices.items():
+ if i1 >= i2:
+ continue
+ d = distance(*(v1 + v2))
+ if d < threshold:
+ possibles[i1, i2] = d
+ possibles[i2, i1] = d
+
+ return possibles
+
+
+def bellman(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]],
+ max_iter: int = 20,
+ allow: Optional[Callable] = None,
+ init: Optional[Dict[Tuple[int, int], float]] = None,
+ verbose: bool = False,
+) -> Dict[Tuple[int, int], float]:
+ """
+ Implémente l'algorithme de `Bellman-Ford `_.
+
+ :param edges: liste de tuples (noeud 1, noeud 2, ?, ?, ?, poids)
+ :param max_iter: nombre d'itérations maximal
+ :param allow: fonction déterminant si l'algorithme
+ doit envisager cette liaison ou pas
+ :param init: initialisation (pour pouvoir continuer après une première exécution)
+ :param verbose: afficher le progrès
+ :return: listes des arcs et des distances calculées
+ """
+
+ if init is None:
+ init: Dict[Tuple[int, int], float] = {(e[0], e[1]): e[-1] for e in edges}
+ init.update({(e[1], e[0]): e[-1] for e in edges})
+
+ def always_true(e):
+ return True
+
+ if allow is None:
+ allow = always_true
+
+ edges_from = {}
+ for e in edges:
+ if e[0] not in edges_from:
+ edges_from[e[0]] = []
+ if e[1] not in edges_from:
+ edges_from[e[1]] = []
+ edges_from[e[0]].append(e)
+ if len(e) == 2:
+ edges_from[e[1]].append((e[1], e[0], 1.0))
+ elif len(e) == 3:
+ edges_from[e[1]].append((e[1], e[0], e[2]))
+ elif len(e) == 6:
+ edges_from[e[1]].append((e[1], e[0], e[2], e[4], e[3], e[5]))
+ else:
+ raise ValueError(
+ f"an edge should be a tuple of 2, 3, or 6 elements, "
+ f"last item is the weight, not:\n{e}"
+ )
+
+ modif = 1
+ total_possible_edges = (len(edges_from) ** 2 - len(edges_from)) // 2
+ it = 0
+ while modif > 0:
+ modif = 0
+ # to avoid RuntimeError: dictionary changed size during iteration
+ initc = init.copy()
+ s = 0
+ for i, d in initc.items():
+ if allow(i):
+ fromi2 = edges_from[i[1]]
+ s += d
+ for e in fromi2:
+ # on fait attention à ne pas ajouter de boucle sur le même
+ # noeud
+ if i[0] == e[1]:
+ continue
+ new_e = i[0], e[1]
+ new_d = d + e[-1]
+ if new_e not in init or init[new_e] > new_d:
+ init[new_e] = new_d
+ modif += 1
+ if verbose:
+ print(
+ f"iteration {it} #modif {modif} # "
+ f"{len(initc) // 2}/{total_possible_edges} = "
+ f"{len(initc) * 50 / total_possible_edges:1.2f}%"
+ )
+ it += 1
+ if it > max_iter:
+ break
+
+ return init
+
+
+def kruskal(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]],
+ extension: Dict[Tuple[int, int], float],
+) -> List[Tuple[int, int]]:
+ """
+ Applique l'algorithme de Kruskal (ou ressemblant) pour choisir les arcs à ajouter.
+
+ :param edges: listes des arcs
+ :param extension: résultat de l'algorithme de Bellman
+ :return: added_edges
+ """
+
+ original: Dict[Tuple[int, int], float] = {(e[0], e[1]): e[-1] for e in edges}
+ original.update({(e[1], e[0]): e[-1] for e in edges})
+ additions: Dict[Tuple[int, int], float] = {
+ k: v for k, v in extension.items() if k not in original
+ }
+ additions.update({(k[1], k[0]): v for k, v in additions.items()})
+
+ degre: Dict[Tuple[int, int], int] = {}
+ for k, v in original.items(): # original est symétrique
+ degre[k[0]] = degre.get(k[0], 0) + 1
+
+ tri = [
+ (v, k)
+ for k, v in additions.items()
+ if degre[k[0]] % 2 == 1 and degre[k[1]] % 2 == 1
+ ]
+ tri.extend(
+ [
+ (v, k)
+ for k, v in original.items()
+ if degre[k[0]] % 2 == 1 and degre[k[1]] % 2 == 1
+ ]
+ )
+ tri.sort()
+
+ impairs = sum(v % 2 for k, v in degre.items())
+ added_edges = []
+ if impairs > 2:
+ for v, a in tri:
+ if degre[a[0]] % 2 == 1 and degre[a[1]] % 2 == 1:
+ # il faut refaire le test car degre peut changer à chaque
+ # itération
+ degre[a[0]] += 1
+ degre[a[1]] += 1
+ added_edges.append(a + (v,))
+ impairs -= 2
+ if impairs <= 0:
+ break
+ return added_edges
+
+
+def eulerien_extension(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]],
+ max_iter: int = 20,
+ alpha: float = 0.5,
+ distance: Callable = distance_haversine,
+ verbose: bool = False,
+) -> List[Tuple[int, int]]:
+ """
+ Construit une extension eulérienne d'un graphe.
+
+ :param edges: liste des arcs
+ :param max_iter: nombre d'itérations pour la fonction @see fn bellman
+ :param alpha: coefficient multiplicatif de ``max_segment``
+ :param distance: la distance de Haversine est beaucoup trop
+ longue sur de grands graphes, on peut la changer
+ :param verbose: afficher l'avancement
+ :return: added edges
+ """
+ max_segment = max(e[-1] for e in edges)
+
+ possibles = possible_edges(edges, max_segment * alpha, distance=distance)
+
+ init = bellman(edges, allow=lambda e: e in possibles)
+ added = kruskal(edges, init)
+ d = graph_degree(edges + added)
+ allow = [k for k, v in d.items() if v % 2 == 1]
+ totali = 0
+ while len(allow) > 0:
+ if verbose:
+ print(f"------- nb odd vertices {len(allow)} iteration {totali}")
+ allowset = set(allow)
+ init = bellman(
+ edges,
+ max_iter=max_iter,
+ allow=lambda e: e in possibles or e[0] in allowset or e[1] in allowset,
+ init=init,
+ verbose=verbose,
+ )
+ added = kruskal(edges, init)
+ d = graph_degree(edges + added)
+ allow = [k for k, v in d.items() if v % 2 == 1]
+ totali += 1
+ if totali > 20:
+ # tant pis, ça ne marche pas
+ break
+
+ return added
+
+
+def connected_components(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]]
+) -> Dict[int, int]:
+ """
+ Computes the connected components.
+
+ :param edges: edges
+ :return: dictionary { vertex : id of connected components }
+ """
+ res = {}
+ for k in edges:
+ for _ in k[:2]:
+ if _ not in res:
+ res[_] = _
+ modif = 1
+ while modif > 0:
+ modif = 0
+ for k in edges:
+ a, b = k[:2]
+ r, s = res[a], res[b]
+ if r != s:
+ m = min(res[a], res[b])
+ res[a] = res[b] = m
+ modif += 1
+
+ return res
+
+
+def euler_path(
+ edges: List[Tuple[int, int, int, Tuple[float, float], Tuple[float, float], float]],
+ added_edges,
+):
+ """
+ Computes an eulerian path. We assume every vertex has an even degree.
+
+ :param edges: initial edges
+ :param added_edges: added edges
+ :return: path, list of `(vertex, edge)`
+ """
+ alledges = {}
+ edges_from = {}
+ somme = 0
+ for e in edges:
+ k = e[:2]
+ v = e[-1]
+ alledges[k] = ["street"] + list(k + (v,))
+ a, b = k
+ alledges[b, a] = alledges[a, b]
+ if a not in edges_from:
+ edges_from[a] = []
+ if b not in edges_from:
+ edges_from[b] = []
+ edges_from[a].append(alledges[a, b])
+ edges_from[b].append(alledges[a, b])
+ somme += v
+
+ for e in added_edges: # il ne faut pas enlever les doublons
+ k = e[:2]
+ v = e[-1]
+ a, b = k
+ alledges[k] = ["jump"] + list(k + (v,))
+ alledges[b, a] = alledges[a, b]
+ if a not in edges_from:
+ edges_from[a] = []
+ if b not in edges_from:
+ edges_from[b] = []
+ edges_from[a].append(alledges[a, b])
+ edges_from[b].append(alledges[a, b])
+ somme += v
+
+ degre = {}
+ for a, v in edges_from.items():
+ t = len(v)
+ degre[t] = degre.get(t, 0) + 1
+
+ two = [a for a, v in edges_from.items() if len(v) == 2]
+ odd = [a for a, v in edges_from.items() if len(v) % 2 == 1]
+ if len(odd) > 0:
+ raise ValueError("some vertices have an odd degree")
+ begin = two[0]
+
+ # checking
+ for v, le in edges_from.items():
+ for e in le:
+ to = e[1] if v != e[1] else e[2]
+ if to not in edges_from:
+ raise RuntimeError(
+ "unable to find vertex {0} for edge {0},{1}".format(to, v)
+ )
+ if to == v:
+ raise RuntimeError(f"circular edge {to}")
+
+ # loop
+ path = _explore_path(edges_from, begin)
+ for p in path:
+ if len(p) == 0:
+ raise RuntimeError("this exception should not happen")
+ while len(edges_from) > 0:
+ start = None
+ for i, p in enumerate(path):
+ if p[0] in edges_from:
+ start = i, p
+ break
+ sub = _explore_path(edges_from, start[1][0])
+ i = start[0]
+ path[i : i + 1] = path[i : i + 1] + sub
+ return path
+
+
+def _delete_edge(edges_from, n: int, to: int):
+ """
+ Removes an edge from the graph.
+
+ :param edges_from: structure which contains the edges (will be modified)
+ :param n: first vertex
+ :param to: second vertex
+ :return: the edge
+ """
+ le = edges_from[to]
+ f = None
+ for i, e in enumerate(le):
+ if (e[1] == to and e[2] == n) or (e[2] == to and e[1] == n):
+ f = i
+ break
+
+ assert f is not None
+ del le[f]
+ if len(le) == 0:
+ del edges_from[to]
+
+ le = edges_from[n]
+ f = None
+ for i, e in enumerate(le):
+ if (e[1] == to and e[2] == n) or (e[2] == to and e[1] == n):
+ f = i
+ break
+
+ assert f is not None
+ keep = le[f]
+ del le[f]
+ if len(le) == 0:
+ del edges_from[n]
+
+ return keep
+
+
+def _explore_path(edges_from, begin):
+ """
+ Explores an eulerian path, remove used edges from edges_from.
+
+ :param edges_from: structure which contains the edges (will be modified)
+ :param begin: first vertex to use
+ :return: path
+ """
+ path = [(begin, None)]
+ stay = True
+ while stay and len(edges_from) > 0:
+ n = path[-1][0]
+ if n not in edges_from:
+ # fin
+ break
+ le = edges_from[n]
+
+ if len(le) == 1:
+ h = 0
+ e = le[h]
+ to = e[1] if n != e[1] else e[2]
+ else:
+ to = None
+ nb = 100
+ while to is None or to == begin:
+ h = random.randint(0, len(le) - 1) if len(le) > 1 else 0
+ e = le[h]
+ to = e[1] if n != e[1] else e[2]
+ nb -= 1
+ if nb < 0:
+ raise RuntimeError(f"algorithm issue {len(path)}")
+
+ if len(edges_from[to]) == 1:
+ if begin != to:
+ raise RuntimeError("wrong algorithm")
+ else:
+ stay = False
+
+ keep = _delete_edge(edges_from, n, to)
+ path.append((to, keep))
+
+ return path[1:]
diff --git a/teachpyx/tools/data_helper.py b/teachpyx/tools/data_helper.py
new file mode 100644
index 00000000..957ff69a
--- /dev/null
+++ b/teachpyx/tools/data_helper.py
@@ -0,0 +1,68 @@
+import os
+import zipfile
+from typing import List
+from urllib.request import urlopen
+
+
+def decompress_zip(filename, dest: str, verbose: bool = False) -> List[str]:
+ """
+ Unzips a zip file.
+
+ :param filename: file to process
+ :param dest: destination
+ :param verbose: verbosity
+ :return: return the list of decompressed files
+ """
+ try:
+ fp = zipfile.ZipFile(filename, "r")
+ except zipfile.BadZipFile as e: # pragma: no cover
+ raise RuntimeError(f"Unable to unzip {filename!r}") from e
+ files = []
+ for info in fp.infolist():
+ if not os.path.exists(info.filename):
+ data = fp.read(info.filename)
+ tos = os.path.join(dest, info.filename)
+ if not os.path.exists(tos):
+ finalfolder = os.path.split(tos)[0]
+ if not os.path.exists(finalfolder):
+ if verbose:
+ print(f"creating folder {finalfolder!r}")
+ os.makedirs(finalfolder)
+ if not info.filename.endswith("/"):
+ with open(tos, "wb") as u:
+ u.write(data)
+ files.append(tos)
+ if verbose:
+ print(f"unzipped {info.filename!r} to {tos!r}")
+ elif not tos.endswith("/"):
+ files.append(tos)
+ elif not info.filename.endswith("/"):
+ files.append(info.filename)
+ return files
+
+
+def download_and_unzip(
+ url: str, dest: str = ".", timeout: int = 10, verbose: bool = False
+) -> List[str]:
+ """
+ Downloads a file and unzip it.
+
+ :param url: url
+ :param dest: destination folder
+ :param timeout: timeout
+ :param verbose: display progress
+ :return: list of unzipped files
+ """
+ filename = url.split("/")[-1]
+ dest_zip = os.path.join(dest, filename)
+ if not os.path.exists(dest_zip):
+ if verbose:
+ print(f"downloads into {dest_zip!r} from {url!r}")
+ with urlopen(url, timeout=timeout) as u:
+ content = u.read()
+ with open(dest_zip, "wb") as f:
+ f.write(content)
+ elif verbose:
+ print(f"already downloaded {dest_zip!r}")
+
+ return decompress_zip(dest_zip, dest, verbose=verbose)