{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# 2A.eco - Les expressions r\u00e9guli\u00e8res : \u00e0 quoi \u00e7a sert ? (correction)\n", "\n", "Chercher un mot dans un texte est une t\u00e2che facile, c'est l'objectif de la m\u00e9thode find attach\u00e9e aux cha\u00eenes de caract\u00e8res, elle suffit encore lorsqu'on cherche un mot au pluriel ou au singulier mais il faut l'appeler au moins deux fois pour chercher ces deux formes. Pour des expressions plus compliqu\u00e9es, il est conseill\u00e9 d'utiliser les expressions r\u00e9guli\u00e8res. C'est une fonctionnalit\u00e9 qu'on retrouve dans beaucoup de langages. C'est une forme de grammaire qui permet de rechercher des expressions."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["\n", "Lorsqu'on remplit un formulaire, on voit souvent le format ``\"MM/JJ/AAAA\"`` qui pr\u00e9cise sous quelle forme on s'attend \u00e0 ce qu\u2019une date soit \u00e9crite. Les expressions r\u00e9guli\u00e8res permettent de d\u00e9finir \u00e9galement ce format et de chercher dans un texte toutes les cha\u00eenes de caract\u00e8res qui sont conformes \u00e0 ce format.\n", "\n", "La liste qui suit contient des dates de naissance. On cherche \u00e0 obtenir toutes les dates de cet exemple sachant que les jours ou les mois contiennent un ou deux chiffres, les ann\u00e9es deux ou quatre."]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": ["s = \"\"\"date 0 : 14/9/2000\n", "date 1 : 20/04/1971 date 2 : 14/09/1913 date 3 : 2/3/1978\n", "date 4 : 1/7/1986 date 5 : 7/3/47 date 6 : 15/10/1914\n", "date 7 : 08/03/1941 date 8 : 8/1/1980 date 9 : 30/6/1976\"\"\""]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le premier chiffre du jour est soit 0, 1, 2, ou 3 ; ceci se traduit par ``[0-3]``. Le second chiffre est compris entre 0 et 9, soit ``[0-9]``. Le format des jours est traduit par ``[0-3][0-9]``. Mais le premier jour est facultatif, ce qu'on pr\u00e9cise avec le symbole ? : ``[0-3]?[0-9]``. Les mois suivent le m\u00eame principe : ``[0-1]?[0-9]``. Pour les ann\u00e9es, ce sont les deux premiers chiffres qui sont facultatifs, le symbole ? s'appliquent sur les deux premiers chiffres, ce qu'on pr\u00e9cise avec des parenth\u00e8ses : ``([0-2][0-9])?[0-9][0-9]``. Le format final d'une date devient :"]}, {"cell_type": "raw", "metadata": {}, "source": ["[0-3]?[0-9]/[0-1]?[0-9]/([0-2][0-9])?[0-9][0-9]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le module re g\u00e8re les expressions r\u00e9guli\u00e8res, celui-ci traite diff\u00e9remment les parties de l'expression r\u00e9guli\u00e8re qui sont entre parenth\u00e8ses de celles qui ne le sont pas : c'est un moyen de dire au module re que nous nous int\u00e9ressons \u00e0 telle partie de l'expression qui est signal\u00e9e entre parenth\u00e8ses. Comme la partie qui nous int\u00e9resse - une date - concerne l'int\u00e9gralit\u00e9 de l'expression r\u00e9guli\u00e8re, il faut ins\u00e9rer celle-ci entre parenth\u00e8ses.\n", "\n", "La premi\u00e8re \u00e9tape consiste \u00e0 construire l'expression r\u00e9guli\u00e8re, la seconde \u00e0 rechercher toutes les fois qu'un morceau de la cha\u00eene s d\u00e9finie plus haut correspond \u00e0 l\u2019expression r\u00e9guli\u00e8re."]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[('14/9/2000', '20'), ('20/04/1971', '19'), ('14/09/1913', '19'), ('2/3/1978', '19'), ('1/7/1986', '19'), ('7/3/47', ''), ('15/10/1914', '19'), ('08/03/1941', '19'), ('8/1/1980', '19'), ('30/6/1976', '19')]\n"]}], "source": ["import re\n", "# premi\u00e8re \u00e9tape : construction\n", "expression = re.compile(\"([0-3]?[0-9]/[0-1]?[0-9]/([0-2][0-9])?[0-9][0-9])\")\n", "# seconde \u00e9tape : recherche\n", "res = expression.findall(s)\n", "print(res)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le r\u00e9sultat une liste de couples dont chaque \u00e9l\u00e9ment correspond aux parties comprises entre parenth\u00e8ses qu'on appelle des groupes. Lorsque les expressions r\u00e9guli\u00e8res sont utilis\u00e9es, on doit d'abord se demander comment d\u00e9finir ce qu\u2019on cherche puis quelles fonctions utiliser pour obtenir les r\u00e9sultats de cette recherche. Les deux paragraphes qui suivent y r\u00e9pondent."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Syntaxe\n", "\n", "La syntaxe des expressions r\u00e9guli\u00e8res est d\u00e9crite sur le site officiel de python. La page [Regular Expression Syntax](https://docs.python.org/3/library/re.html?highlight=re#regular-expression-syntax) d\u00e9crit comment se servir des expressions r\u00e9guli\u00e8res, les deux pages sont en anglais. Comme toute grammaire, celle des expressions r\u00e9guli\u00e8res est susceptible d\u2019\u00e9voluer au fur et \u00e0 mesure des versions du langage python."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Les ensembles de caract\u00e8res\n", "Lors d\u2019une recherche, on s\u2019int\u00e9resse aux caract\u00e8res et souvent aux classes de caract\u00e8res : on cherche un chiffre, une lettre, un caract\u00e8re dans un ensemble pr\u00e9cis ou un caract\u00e8re qui n\u2019appartient pas \u00e0 un ensemble pr\u00e9cis. Certains ensembles sont pr\u00e9d\u00e9finis, d\u2019autres doivent \u00eatre d\u00e9finis \u00e0 l\u2019aide de crochets.\n", "\n", "Pour d\u00e9finir un ensemble de caract\u00e8res, il faut \u00e9crire cet ensemble entre crochets : [0123456789] d\u00e9signe un chiffre. Comme c\u2019est une s\u00e9quence de caract\u00e8res cons\u00e9cutifs, on peut r\u00e9sumer cette \u00e9criture en [0-9]. Pour inclure les symboles -, +, il suffit d\u2019\u00e9crire : [-0-9+]. Il faut penser \u00e0 mettre le symbole - au d\u00e9but pour \u00e9viter qu\u2019il ne d\u00e9signe une s\u00e9quence.\n", "\n", "Le caract\u00e8re ^ ins\u00e9r\u00e9 au d\u00e9but du groupe signifie que le caract\u00e8re cherch\u00e9 ne doit pas \u00eatre un de ceux qui suivent. Le tableau suivant d\u00e9crit les ensembles pr\u00e9d\u00e9finis et leur \u00e9quivalent en terme d\u2019ensemble de caract\u00e8res :"]}, {"cell_type": "markdown", "metadata": {}, "source": ["* ``.`` d\u00e9signe tout caract\u00e8re non sp\u00e9cial quel qu'il soit.\n", "* ``\\d`` d\u00e9signe tout chiffre, est \u00e9quivalent \u00e0 ``[0-9]``.\n", "* ``\\D`` d\u00e9signe tout caract\u00e8re diff\u00e9rent d'un chiffre, est \u00e9quivalent \u00e0 ``[^0-9]``.\n", "* ``\\s`` d\u00e9signe tout espace ou caract\u00e8re approch\u00e9, est \u00e9quivalent \u00e0 ``[\\; \\t\\n\\r\\f\\v]``. Ces caract\u00e8res sont sp\u00e9ciaux, les plus utilis\u00e9s sont ``\\t`` qui est une tabulation, ``\\n`` qui est une fin de ligne et qui ``\\r`` qui est un retour \u00e0 la ligne.\n", "* ``\\S`` d\u00e9signe tout caract\u00e8re diff\u00e9rent d'un espace, est \u00e9quivalent \u00e0 ``[^ \\t\\n\\r\\f\\v]``.\n", "* ``\\w`` d\u00e9signe tout lettre ou chiffre, est \u00e9quivalent \u00e0 ``[a-zA-Z0-9_]``.\n", "* ``\\W`` d\u00e9signe tout caract\u00e8re diff\u00e9rent d'une lettre ou d'un chiffre, est \u00e9quivalent \u00e0 ``[^a-zA-Z0-9_]``.\n", "* ``^`` d\u00e9signe le d\u00e9but d'un mot sauf s'il est plac\u00e9 entre crochets.\n", "* ``$`` d\u00e9signe la fin d'un mot sauf s'il est plac\u00e9 entre crochets."]}, {"cell_type": "markdown", "metadata": {}, "source": ["A l'instar des cha\u00eenes de caract\u00e8res, comme le caract\u00e8re ``\\`` est un caract\u00e8re sp\u00e9cial, il faut le doubler : ``[\\\\]``.\n", "\n", "Le caract\u00e8re ``\\`` est d\u00e9j\u00e0 un caract\u00e8re sp\u00e9cial pour les cha\u00eenes de caract\u00e8res en python, il faut donc le quadrupler pour l'ins\u00e9rer dans un expression r\u00e9guli\u00e8re. L'expression suivante filtre toutes les images dont l\u2019extension est png et qui sont enregistr\u00e9es dans un r\u00e9pertoire image."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["\n", "\n"]}], "source": ["import re\n", "s = \"something\\\\support\\\\vba\\\\image/vbatd1_4.png\"\n", "print(re.compile(\"[\\\\\\\\/]image[\\\\\\\\/].*[.]png\").search(s)) # r\u00e9sultat positif\n", "print(re.compile(\"[\\\\\\\\/]image[\\\\\\\\/].*[.]png\").search(s)) # m\u00eame r\u00e9sultat"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Les multiplicateurs\n", "Les multiplicateurs permettent de d\u00e9finir des expressions r\u00e9guli\u00e8res comme : un mot entre six et huit lettres qu\u2019on \u00e9crira ``[\\w]{6,8}``. Le tableau suivant donne la liste des multiplicateurs principaux :"]}, {"cell_type": "markdown", "metadata": {}, "source": ["* ``*`` pr\u00e9sence de l'ensemble de caract\u00e8res qui pr\u00e9c\u00e8de entre 0 fois et l'infini\n", "* ``+`` pr\u00e9sence de l'ensemble de caract\u00e8res qui pr\u00e9c\u00e8de entre 1 fois et l'infini\n", "* ``?`` pr\u00e9sence de l'ensemble de caract\u00e8res qui pr\u00e9c\u00e8de entre 0 et 1 fois\n", "* ``{m,n}`` pr\u00e9sence de l'ensemble de caract\u00e8res qui pr\u00e9c\u00e8de entre *m* et *n* fois, si *m=n*, cette expression peut \u00eatre r\u00e9sum\u00e9e par ``{n}``.\n", "* ``(?!(...))`` absence du groupe d\u00e9sign\u00e9 par les points de suspensions."]}, {"cell_type": "markdown", "metadata": {}, "source": ["L\u2019algorithme des expressions r\u00e9guli\u00e8res essaye toujours de faire correspondre le plus grand morceau \u00e0 l\u2019expression r\u00e9guli\u00e8re. "]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"data": {"text/plain": ["'

mot

'"]}, "execution_count": 6, "metadata": {}, "output_type": "execute_result"}], "source": ["\"

mot

\""]}, {"cell_type": "markdown", "metadata": {}, "source": ["``<.*>`` correspond avec ``

``, ``

`` ou encore ``

mot

``."]}, {"cell_type": "markdown", "metadata": {}, "source": ["Par cons\u00e9quent, l\u2019expression r\u00e9guli\u00e8re correspond \u00e0 trois morceaux. Par d\u00e9faut, il prendra le plus grand. Pour choisir les plus petits, il faudra \u00e9crire les multiplicateurs comme ceci : ``*?``, ``+?``"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["('

mot

',)\n", "('

',)\n", "('

',)\n"]}], "source": ["import re\n", "s = \"

mot

\"\n", "print(re.compile(\"(<.*>)\").match(s).groups()) # ('

mot

',)\n", "print(re.compile(\"(<.*?>)\").match(s).groups()) # ('

',)\n", "print(re.compile(\"(<.+?>)\").match(s).groups()) # ('

',)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Exercice 1\n", "\n", "Recherchez les dates pr\u00e9sentes dans la phrase suivante"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": ["texte = \"\"\"Je suis n\u00e9 le 28/12/1903 et je suis mort le 08/02/1957. Ma seconde femme est morte le 10/11/1963. \n", "J'ai \u00e9crit un livre intitul\u00e9 'Comprendre les fractions : les exemples en page 12/46/83' \"\"\""]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["['28/12/1903', '08/02/1957', '10/11/1963']\n"]}], "source": ["import re\n", "expression = re.compile(\"[0-9]{2}/[0-9]{2}/[0-9]{4}\")\n", "cherche = expression.findall(texte)\n", "print(cherche)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Puis dans celle-ci : "]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": ["texte = \"\"\"Je suis n\u00e9 le 28/12/1903 et je suis mort le 08/02/1957. Je me suis mari\u00e9 le 8/5/45. \n", "J'ai \u00e9crit un livre intitul\u00e9 'Comprendre les fractions : les exemples en page 12/46/83' \"\"\""]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["['28/12/1903', '08/02/1957', '8/5/45']\n"]}], "source": ["expression = re.compile(\"[0-3]?[0-9]/[0-1]?[0-9]/[0-1]?[0-9]?[0-9]{2}\")\n", "cherche = expression.findall(texte)\n", "print(cherche)"]}], "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.0"}}, "nbformat": 4, "nbformat_minor": 2}