Skip to content

Commit 6e1bb43

Browse files
authored
Code session 2 (#72)
* Code session 2 * fix data * fix doc link
1 parent 40802cc commit 6e1bb43

9 files changed

Lines changed: 1460 additions & 96 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*.dbf
1010
*.xlsx
1111
*.pickle
12+
*.shp
13+
*.shx
1214
.coverage
1315
data.*
1416
paris*.*

_doc/api/datasets.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Jeux de données
55
Cartographie
66
============
77

8+
.. autofunction:: teachpyx.datasets.get_naturalearth_cities
9+
10+
.. autofunction:: teachpyx.datasets.get_naturalearth_lowres
11+
812
.. autofunction:: teachpyx.datasets.load_enedis_dataset
913

1014
Classification

_doc/articles/2024/2024-11-31-route2024.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ Séance 1
1111
Séance 2
1212
++++++++
1313

14-
Tests unitaires et classes
14+
Tests unitaires et classes toujours avec les dames
15+
16+
:ref:`Prises aux dames <nbl-practice-py-base-dame_prise>`
1517

1618
Séance 3
1719
++++++++
1820

21+
Héritage
22+
1923
:ref:`classes pour représenter un graphe <nbl-practice-py-base-classe_tree>`
2024

2125
Fin des classes puis :ref:`les itérateurs <nbl-practice-py-base-classe_iterateur>` et

_doc/c_data/enedis_cartes.ipynb

Lines changed: 1184 additions & 89 deletions
Large diffs are not rendered by default.

_doc/practice/py-base/dame_prise.ipynb

Lines changed: 187 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@
2626
"Les valeurs numériques sont toujours plus efficace que des chaînes de caractères. Elles prennent moins de place en mémoire et les opérations sont plus efficaces."
2727
]
2828
},
29+
{
30+
"cell_type": "markdown",
31+
"metadata": {},
32+
"source": [
33+
"## Partie I : sans les classes"
34+
]
35+
},
2936
{
3037
"cell_type": "code",
31-
"execution_count": 5,
38+
"execution_count": 1,
3239
"metadata": {},
3340
"outputs": [
3441
{
@@ -75,7 +82,7 @@
7582
},
7683
{
7784
"cell_type": "code",
78-
"execution_count": 6,
85+
"execution_count": 2,
7986
"metadata": {},
8087
"outputs": [
8188
{
@@ -84,7 +91,7 @@
8491
"[(0, 0), (4, 4), (4, 6)]"
8592
]
8693
},
87-
"execution_count": 6,
94+
"execution_count": 2,
8895
"metadata": {},
8996
"output_type": "execute_result"
9097
}
@@ -114,7 +121,7 @@
114121
},
115122
{
116123
"cell_type": "code",
117-
"execution_count": 13,
124+
"execution_count": 3,
118125
"metadata": {},
119126
"outputs": [
120127
{
@@ -183,7 +190,7 @@
183190
},
184191
{
185192
"cell_type": "code",
186-
"execution_count": 16,
193+
"execution_count": 4,
187194
"metadata": {},
188195
"outputs": [
189196
{
@@ -213,7 +220,181 @@
213220
{
214221
"cell_type": "markdown",
215222
"metadata": {},
216-
"source": []
223+
"source": [
224+
"## Partie 2 : Avec les classes\n",
225+
"\n",
226+
"Dans cette partie, on construit deux classes ``Coup`` et ``Damier`` avec pour objectif de déterminer si le fait de commencer est un avantage dans une partie où les deux joueurs jouent de façon aléatoire. Pour y répondre, il faut simuler un grand nombre de parties.\n",
227+
"\n",
228+
"La classe ``Coup`` contient au moins deux positions, celle de départ et celle d'arrivée. Elle peut en contenir plus si le pion ou la dame peut en prendre plusieurs."
229+
]
230+
},
231+
{
232+
"cell_type": "code",
233+
"execution_count": 5,
234+
"metadata": {},
235+
"outputs": [
236+
{
237+
"name": "stdout",
238+
"output_type": "stream",
239+
"text": [
240+
"[(0, 0), (3, 3)]\n"
241+
]
242+
}
243+
],
244+
"source": [
245+
"class Coup:\n",
246+
"\n",
247+
" def __init__(self, positions: list[tuple[int, int]]):\n",
248+
"\n",
249+
" self.positions = positions\n",
250+
"\n",
251+
" def __len__(self) -> int:\n",
252+
" \"Retourne le nombre de positions.\"\n",
253+
" return len(self.positions)\n",
254+
"\n",
255+
" def __str__(self) -> str:\n",
256+
" \"Appelée implicitement par Python si print(c) où c est un Coup\"\n",
257+
" return str(self.positions)\n",
258+
"\n",
259+
" def __getitem__(self, i):\n",
260+
" \"Donne un sens à c[0] où c est de type Coup.\"\n",
261+
" return self.positions[i]\n",
262+
"\n",
263+
"\n",
264+
"# Vérification rapide.\n",
265+
"c = Coup([(0, 0), (3, 3)])\n",
266+
"print(c)"
267+
]
268+
},
269+
{
270+
"cell_type": "markdown",
271+
"metadata": {},
272+
"source": [
273+
"Maintenant le ``Damier``:"
274+
]
275+
},
276+
{
277+
"cell_type": "code",
278+
"execution_count": 6,
279+
"metadata": {},
280+
"outputs": [
281+
{
282+
"name": "stdout",
283+
"output_type": "stream",
284+
"text": [
285+
"[[3 0 3 0 3 0 3 0 3 0]\n",
286+
" [0 3 0 3 0 3 0 3 0 3]\n",
287+
" [3 0 3 0 3 0 3 0 3 0]\n",
288+
" [0 3 0 3 0 3 0 3 0 3]\n",
289+
" [0 0 0 0 0 0 0 0 0 0]\n",
290+
" [0 0 0 0 0 0 0 0 0 0]\n",
291+
" [4 0 4 0 4 0 4 0 4 0]\n",
292+
" [0 4 0 4 0 4 0 4 0 4]\n",
293+
" [4 0 4 0 4 0 4 0 4 0]\n",
294+
" [0 4 0 4 0 4 0 4 0 4]]\n"
295+
]
296+
}
297+
],
298+
"source": [
299+
"class Damier:\n",
300+
"\n",
301+
" def __init__(self, N: int = 10):\n",
302+
"\n",
303+
" self.damier = np.zeros((N, N), dtype=int)\n",
304+
"\n",
305+
" def __str__(self) -> str:\n",
306+
" \"Appelée implicitement par Python si print(c) où c est un Coup\"\n",
307+
" return str(self.damier)\n",
308+
"\n",
309+
" def __len__(self) -> int:\n",
310+
" \"Retourne la dimension du damier.\"\n",
311+
" return len(self.damier)\n",
312+
"\n",
313+
" def init(self):\n",
314+
" \"Initialise le damier pour un début de partie.\"\n",
315+
" N = len(self)\n",
316+
" for i in range(N):\n",
317+
" if i in ((N - 1) // 2, N // 2):\n",
318+
" continue\n",
319+
" c = 3 if i < N // 2 else 4\n",
320+
" for j in range(N):\n",
321+
" if (i + j) % 2 == 0:\n",
322+
" self.damier[i, j] = c\n",
323+
"\n",
324+
" def joue(self, coup: Coup):\n",
325+
" \"Joue un coup. On suppose que celui-ci est valide.\"\n",
326+
" for i in range(1, len(coup)):\n",
327+
" self.annule(coup[i - 1], coup[i])\n",
328+
" self.damier[coup[1]] = self.damier[coup[0]]\n",
329+
" self.damier[coup[0]] = 0\n",
330+
"\n",
331+
" def annule(self, p1: tuple[int, int], p2: tuple[int, int]):\n",
332+
" \"Annule toutes les cases du damier entre deux positions.\"\n",
333+
" di = (p2[0] - p1[0]) // abs(p2[0] - p1[0])\n",
334+
" dj = (p2[1] - p1[1]) // abs(p2[1] - p1[1])\n",
335+
" for k in range(1, abs(p2[0] - p1[0])):\n",
336+
" self.damier[p1[0] + di * k, p1[1] + dj * k] = 0\n",
337+
"\n",
338+
"\n",
339+
"d = Damier()\n",
340+
"d.init()\n",
341+
"print(d) # équivalent à print(d.__str__())"
342+
]
343+
},
344+
{
345+
"cell_type": "markdown",
346+
"metadata": {},
347+
"source": [
348+
"On écrit un test unitaire pour vérifier que la méthode ``init`` est valide."
349+
]
350+
},
351+
{
352+
"cell_type": "code",
353+
"execution_count": 7,
354+
"metadata": {},
355+
"outputs": [],
356+
"source": [
357+
"def test_init():\n",
358+
" d = Damier(4)\n",
359+
" d.init()\n",
360+
" assert d.damier.tolist() == [\n",
361+
" [3, 0, 3, 0],\n",
362+
" [0, 0, 0, 0],\n",
363+
" [0, 0, 0, 0],\n",
364+
" [0, 4, 0, 4],\n",
365+
" ], f\"{d.damier.tolist()}\"\n",
366+
"\n",
367+
"\n",
368+
"test_init()"
369+
]
370+
},
371+
{
372+
"cell_type": "markdown",
373+
"metadata": {},
374+
"source": [
375+
"On fait de même pour la méthode ``joue``."
376+
]
377+
},
378+
{
379+
"cell_type": "code",
380+
"execution_count": 8,
381+
"metadata": {},
382+
"outputs": [],
383+
"source": [
384+
"def test_coup():\n",
385+
" d = Damier(4)\n",
386+
" d.init()\n",
387+
" d.joue(Coup([(0, 0), (1, 1)]))\n",
388+
" assert d.damier.tolist() == [\n",
389+
" [0, 0, 3, 0],\n",
390+
" [0, 3, 0, 0],\n",
391+
" [0, 0, 0, 0],\n",
392+
" [0, 4, 0, 4],\n",
393+
" ], f\"{d.damier.tolist()}\"\n",
394+
"\n",
395+
"\n",
396+
"test_coup()"
397+
]
217398
}
218399
],
219400
"metadata": {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import unittest
2+
from teachpyx.ext_test_case import ExtTestCase
3+
from teachpyx.datasets import get_naturalearth_cities, get_naturalearth_lowres
4+
5+
6+
class TestGpdHelper(ExtTestCase):
7+
def test_get_naturalearth_cities(self):
8+
filenames = get_naturalearth_cities()
9+
for filename in filenames:
10+
self.assertExists(filename)
11+
12+
def test_get_naturalearth_lowres(self):
13+
filenames = get_naturalearth_lowres()
14+
for filename in filenames:
15+
self.assertExists(filename)
16+
17+
18+
if __name__ == "__main__":
19+
unittest.main()

teachpyx/datasets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
# from .titanic import load_titanic_dataset
1414
# from .tweets import load_tweet_dataset
1515
from .wines import load_wines_dataset, load_wine_dataset
16+
17+
from .gpd_helper import get_naturalearth_cities, get_naturalearth_lowres

teachpyx/datasets/gpd_helper.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from ..tools.data_helper import download
2+
3+
4+
def get_naturalearth_cities(dest: str = ".", timeout: int = 10, verbose: bool = False):
5+
"""
6+
Retrieves file ``naturalearth_cities.shp``, ``naturalearth_cities.shx``,
7+
``naturalearth_cities.dbf`` in
8+
`teachdata/geopandas/data/naturalearth_cities/
9+
<https://github.com/sdpython/teachdata/blob/main/geopandas/data/naturalearth_cities/>`_.
10+
"""
11+
urls = [
12+
"https://github.com/sdpython/teachdata/raw/main/geopandas/data/naturalearth_cities/naturalearth_cities.shp",
13+
"https://github.com/sdpython/teachdata/raw/main/geopandas/data/naturalearth_cities/naturalearth_cities.shx",
14+
"https://github.com/sdpython/teachdata/raw/main/geopandas/data/naturalearth_cities/naturalearth_cities.dbf",
15+
]
16+
return [download(url, dest=dest, timeout=timeout, verbose=verbose) for url in urls]
17+
18+
19+
def get_naturalearth_lowres(dest: str = ".", timeout: int = 10, verbose: bool = False):
20+
"""
21+
Retrieves files ``naturalearth_lowres.shp``, ``naturalearth_lowres.shx``,
22+
``naturalearth_lowres.dbf`` in
23+
`teachdata/geopandas/data/naturalearth_lowres/
24+
<https://github.com/sdpython/teachdata/blob/main/geopandas/data/naturalearth_lowres/>`_.
25+
"""
26+
urls = [
27+
"https://github.com/sdpython/teachdata/raw/main/geopandas/data/naturalearth_lowres/naturalearth_lowres.shp",
28+
"https://github.com/sdpython/teachdata/raw/main/geopandas/data/naturalearth_lowres/naturalearth_lowres.shx",
29+
"https://github.com/sdpython/teachdata/raw/main/geopandas/data/naturalearth_lowres/naturalearth_lowres.dbf",
30+
]
31+
return [download(url, dest=dest, timeout=timeout, verbose=verbose) for url in urls]

teachpyx/tools/data_helper.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,32 @@ def decompress_zip(filename, dest: str, verbose: bool = False) -> List[str]:
4141
return files
4242

4343

44+
def download(
45+
url: str, dest: str = ".", timeout: int = 10, verbose: bool = False
46+
) -> str:
47+
"""
48+
Download one file.
49+
50+
:param url: url
51+
:param dest: destination folder
52+
:param timeout: timeout
53+
:param verbose: display progress
54+
:return: filename
55+
"""
56+
filename = url.split("/")[-1]
57+
dest_zip = os.path.join(dest, filename)
58+
if not os.path.exists(dest_zip):
59+
if verbose:
60+
print(f"downloads into {dest_zip!r} from {url!r}")
61+
with urlopen(url, timeout=timeout) as u:
62+
content = u.read()
63+
with open(dest_zip, "wb") as f:
64+
f.write(content)
65+
elif verbose:
66+
print(f"already downloaded {dest_zip!r}")
67+
return dest_zip
68+
69+
4470
def download_and_unzip(
4571
url: str, dest: str = ".", timeout: int = 10, verbose: bool = False
4672
) -> List[str]:

0 commit comments

Comments
 (0)