IGI-3008 – ESIEE Paris – 2016-2017 Figure 1 — Un extrait des tables Unicode Apprentissage de la Programmation avec Python Corrigé du TP4 Jean-Claude GEORGES Retour sur les chaînes de caractères Fonctions et méthodes Les chaînes de caractères sont des objets indexés (dont les composants sont numérotés) non modifiables. Outre les littéraux, on peut créer une chaîne en utilisant sa valeur Unicode ou son nom (hélas en anglais). Exercice 1 — Chaînes bizarres Solution p. 7 Que contiennent les chaînes suivantes ? >>> '\u2713' >>> "Utilisez '\N{MULTIPLICATION SIGN}' ou '\u00d7' pour \ afficher un signe de multiplication" >>> "\N{WHITE SMILING FACE}" >>> '\u0627\u0628\u0648\u062c\u0639\u0641\u0631\u0020\u0645\u062d\u0645\ \u062f\u0020\u0628\u0646\u0020\u0645\u0648\u0633\u06cc\u0020\u062e\u0648\ \u0627\u0631\u0632\u0645\u06cc' Les listes des caractères Unicode et de leurs noms associés est disponible à l’adresse I http://www.unicode.org/charts/ (attention, toutes les machines ne disposent pas de tous les caractères) Figure 2 — Les noms Unicode FONCTIONS ET MÉTHODES 2 L’indexation s[i] permet d’extraire un caractère unique de la chaîne. La découpe (slicing) s[d:f:p] permet de créer une nouvelle chaîne construite à partir de la chaîne de départ. Figure 3 — Indexation et slicing Les numérotations se font comme indiqué Fig. 3. Exercice 2 — Indexation et slicing Solution p. 7 Avec >>> s = "0123456789", que donneront les commandes suivantes ? >>> s[3] >>> s[-1] >>> s[0] >>> s[-3] >>> s[40] >>> s[-40] >>> s[3:8] >>> s[8:3] >>> s[:8] >>> s[3:] >>> s[:] >>> s[-8:-3] >>> s[:-3] >>> s[-8:] >>> s[3:8:2] >>> s[:8:3] >>> s[2::2] >>> s[::2] >>> s[-3:-8:-2] >>> s[-2::-3] >>> s[:2:-3] >>> s[::-1] Figure 4 — Pourquoi changement de casse On parle de casse en imprimerie, car avant les ordinateurs, les caractères (cassetins) en plomb étaient rangés dans des caisses, avec en bas les minuscules et en haut les majuscules. Exercice 3 — Changement de casse Solution p. 8 Avec >>> s = "Un EXEMPLE de Chaîne de Caractères", que donneront les commandes suivantes ? >>> s.lower() >>> s.upper() >>> s.capitalize() >>> s.title() >>> s.swapcase() Corrigé du TP4 IGI-3008 (2016-2017) FONCTIONS ET MÉTHODES 3 D’autres opérateurs, fonctions ou méthodes sont utilisables sur les chaînes. Exercice 4 — Tests Solution p. 8 Avec s = "Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf", que donneront les commandes suivantes ? >>> "Jack" in s >>> "Jacky" in s def inferieur(s1, s2) : i = 0 while i < len(s1) and i < len(s2) : if ord(s1[i]) == ord(s2[i]) : # si c1 == c2 (en Unicode) i += 1 # on passe au suivant continue else : # si c1 != c2 ()en Unicode) return ord(s1[i]) < ord(s2[i]) # c1 < c2 else : # si s1 ou s2 est terminé return len(s1)<len(s2) # s1 doit être plus court que s2 Pour les règles de l’ordre lexicographique en français et leur programmation, voir I http://www-clips.imag.fr/geta/ >>> "" in s gilles.serasset/tri-du-francais.html >>> s.startswith("monsieur") >>> s.endswith("olf") >>> "Python" == "Python" >>> "Python" < "Python.3" >>> "Python.2" < "Python.3" >>> "Python" == "python" >>> "Python" <= "python" >>> "Zorro" < "cavalier" >>> "a" < "b" >>> "à" < "b" >>> "a123".isalnum() ;"a12 3".isalnum() >>> "Python".isalpha() ;"Pyth0n".isalpha() >>> "123".isdigit() ;"12.3".isdigit() ;"-12".isdigit() >>> "A2".isidentifier() ;"2A".isidentifier() ; >>> "arrière-grand-mère".islower() ;"Blanche-Neige et les 7 nains".islower() >>> "VOL 714 POUR SIDNEY".isupper() ;"VOL 714 POUR SlDNEY".isupper() >>> " ".isprintable() ;"tabulation \t".isprintable() >>> "\n\t ".isspace() Corrigé du TP4 Figure 5 — Comparaisons lexicographiques On remarque que les comparaisons sur les chaînes ne sont pas lexicographiques (ordre du dictionnaire). Elles suivent l’ordre Unicode. Le programme Python équivalent à la comparaison < est le suivant : IGI-3008 (2016-2017) FONCTIONS ET MÉTHODES 4 Exercice 5 — Suppression/ajout/remplacement Solution p. 9 Que donneront les commandes suivantes ? >>> "12".zfill(5) >>> " \t\n suppression des espaces à gauche >>> " suppression des espaces à droite \t\n ".rstrip() ".lstrip() >>> " suppression des espaces à droite et à gauche \t\n ".strip() >>> s = "Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf" >>> t = s.replace('ami', 'copain') >>> print(s) >>> print(t) Exercice 6 — Comptage et recherche Solution p. 9 Avec s = "Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf", que donneront les commandes suivantes ? len(s) ; s.count('e') ; s.count('ie') s.find('i') ; s.find('i',40) ; s.find('i',20,30) ; s.rfind('i') s.index('i') ; s.index('i',40) ; s.index('i',20,30) ;s.rindex('i') Écrivez un programme qui vérifie que toutes les lettres de l’alphabet sont contenues dans une chaîne s (qui vérifie que s est un pangramme, voir Fig. 6). On se limitera aux chaînes en minuscules et sans accent. Corrigé du TP4 IGI-3008 (2016-2017) Figure 6 — Pangrammes Un pangramme contient toutes les lettres de l’alphabet. Deux exemples classiques : Portez ce vieux whisky au juge blond qui fume The quick brown fox jumps over the lazy dog. Plus fort encore, ce pangramme de Gilles Esposito-Farèse, qui contient toutes les lettres accentuées et ligatures du français : Dès Noël où un zéphyr haï me vêt de glaçons würmiens, je dîne d’exquis rôtis de bœuf au kir à l’aÿ d’âge mûr et cætera ! FONCTIONS ET MÉTHODES 5 Exercice 7 — Partitions Solution p. 10 Que donneront les commandes suivantes ? Figure 7 — L’aide pour la méthode partition >>> "Une chaîne à découper".partition('à') >>> help(str.partition) Help on method_descriptor : >>> "Une chaîne à découper".partition('et') partition(...) S.partition(sep) -> (head, sep, tail) >>> "Une chaîne à découper".partition(' ') Search for the separator sep in S, and return the part before it, the separator itself, and the part after it. If the separator is not found, return S and two empty strings. >>> "Une chaîne à découper".split(' ') >>> "Une chaîne à découper".split(' ',2) >>> "Une chaîne à découper".split('e') >>> """Un texte Figure 8 — L’aide pour la méthode split sur >>> help(str.split) Help on method_descriptor : plusieurs lignes""".splitlines() >>> mots = " Une chaîne split(...) S.split(sep=None, maxsplit=-1) -> list of strings à découper en mots ".split() >>> mots Return a list of the words in S, using sep as the delimiter string. If maxsplit is given, at most maxsplit splits are done. If sep is not specified or is None, any whitespace string is a separator and empty strings are removed from the result. >>> ' '.join(mots) Figure 9 — L’aide pour la méthode join >>> help(str.join) Help on method_descriptor : join(...) S.join(iterable) -> str Return a string which is the concatenation of the strings in the iterable. The separator between elements is S. Corrigé du TP4 IGI-3008 (2016-2017) EXERCICES DE SYNTHÈSE 6 Exercices de synthèse Normalisation d’un mot français On a vu dans l’exercice 4 que les comparaisons sur les chaînes de caractères ne sont pas compatibles avec l’ordre alphabétique français. Exercice 8 — Normalisation d’un caractère Solution p. 10 Créez une fonction normalise_lettre qui retourne la (ou les) lettre(s) minuscule(s) correspondant au caractère de longueur 1 passé en paramètre, les caractères non lettres restant inchangés (’Z’ → ’z’, ’é’ → ’e’, ’œ’ → ’oe’, ’8’ → ’8’, ’ ?’ → ’ ?’ ). Figure 10 — Diacritiques et ligatures Il existe en français : - 16 lettres avec diacritiques : à â ä ç é è ê ë î ï ô ö ù û ü ÿ - 2 ligatures : æ, œ auxquelles on peut ajouter des lettres présentes dans certains dictionnaires pour des mots d’origine étrangère comme ñ (cañon), å (ångström) ou ō (Tōkyō) Exercice 9 — Normalisation d’un mot Solution p. 10 Créez une fonction normalise_texte qui retourne la chaîne minuscule du texte passé en paramètre, avec ses lettres accentuées ou cédillées converties en lettres normales et les ligatures séparées (’ZÈBRE’ → ’zebre’, ’bœuf’ → ’boeuf’). Testez cette fonction avec la chaîne suivante : Dès Noël où un zéphyr haï me vêt de glaçons würmiens, je dîne d’exquis rôtis de bœuf au kir à l’aÿ d’âge mûr et cætera ! Figure 11 — Quelques palindromes à tester Engage le jeu que je le gagne. Tu l’as trop écrasé, César, ce Port-Salut ! Saippuakivikauppias (marchand de pierre de savon en finnois) et le plus vieux dialogue (trilogue ?) palindromique du monde : – Madam, I’m Adam ! – Eve. – Ssssssss... Exercice 10 — Palindrome Solution p. 11 Écrivez les fonctions nécessaires à vérifier qu’un texte est un palindrome. Une erreur de transcription fait que le texte trouvé sur ce site indiqué Fig. 12 n’est pas un palindrome : trouvez la correction à faire dans le texte pour qu’il devienne un vrai palindrome. Figure 12 — Un palindrome géant Le site I http://homepage.urbanet.ch/cruci.com/lexique/ palindrome.htm nous donne le palindrome record de Georges Perec. Trace l’inégal palindrome ...... e mord ni la plage ni l’écart. Georges Perec, La clôture et autre poèmes, Hachette, 1980 Corrigé du TP4 IGI-3008 (2016-2017) SOLUTIONS 7 Solutions Exercice 2 — Indexation et slicing (p. 2) Exercice 1 — Chaînes bizarres (p. 1) ’X’ (le symbole de la coche) "Utilisez ’×’ ou ’×’ pour afficher un signe de multiplication" ’©’ (un smiley) (Al-Khuwarizmi en persan) Corrigé du TP4 >>> s = "0123456789" >>> # récupération d'un caractère >>> s[3] # caractère n°3 (la numérotation commence à 0) '3' >>> s[-1] # caractère -1 (dernier caractère) '9' >>> s[0] # caractère 0 (1er car.) '0' >>> s[-3] # caractère -3 (3e car. à partir de la fin) '7' >>> s[40] # erreur Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> s[-40] # erreur Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> # découpe (slice) comptage de 1 en 1 (par défaut) >>> s[3:8] # sous-chaîne de 3 inclus à 8 exclu '34567' >>> s[8:3] # de 8 inclus à 3 exclu (vide) '' >>> s[:8] # du début à 8 exclu '01234567' >>> s[3:] # de 3 à la fin '3456789' >>> s[:] # du début à la fin (copie) '0123456789' >>> s[-8:-3] # de -8 inclus à -3 exclu '23456' >>> s[:-3] # du début à -3 exclu '0123456' >>> s[-8:] # de -8 inclus à la fin '23456789' >>> # découpe (slice) comptage de p en p >>> s[3:8:2] # de 3 inclus à 8 exclu de 2 en 2 '357' >>> s[:8:3] # du début à 8 exclu de 3 en 3 '036' >>> s[2::2] # de 2 inclus à la fin de 2 en 2 '2468' >>> s[::2] # du début à la fin de 2 en 2 '02468' >>> s[-3:-8:-2] # à rebours de -3 à -8 exclu de 2 en 2 '753' >>> s[-2::-3] # à rebours de -2 au début de 3 en 3 '852' >>> s[:2:-3] # à rebours de la fin à 2 exclu de 3 en 3 '963' >>> s[::-1] # à rebours de la fin au début '9876543210' IGI-3008 (2016-2017) SOLUTIONS Exercice 3 — Changement de casse (p. 2) >>> s="Un EXEMPLE de Chaîne de Caractères" >>> s.lower() # mise en minuscules 'un exemple de chaîne de caractères' >>> s.upper() # mise en majuscules 'UN EXEMPLE DE CHAÎNE DE CARACTÈRES' 8 Exercice 4 — Tests (p. 3) >>> s = "Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf" >>> "Jack" in s ; "Jacky" in s ; "" in s True False True >>> s.startswith("monsieur") # commence par ... False car M majuscule False >>> s.endswith("olf") # se termine par... True >>> s.capitalize() # mise en majuscule de l'initiale 'Un exemple de chaîne de caractères' >>> s.title() # mise en majuscule de l'initiale de chaque mot 'Un Exemple De Chaîne De Caractères' >>> s.swapcase() # si quelqu'un me trouve son utilité ... 'uN exemple DE cHAÎNE DE cARACTÈRES' Notez que la chaîne s n’est pas modifiée. >>> "Python" == "Python" ;"Python" < "Python.3" ;"Python.2" < "Python.3" True True True >>> "Python" == "python" ;"Python" <= "python" ;"Zorro" < "cavalier" False True True >>> "a" < "b" ;"à" < "b" True False >>> "a123".isalnum() ;"a12 3".isalnum() True False >>> "Python".isalpha() ;"Pyth0n".isalpha() True False >>> "123".isdigit() ;"12.3".isdigit() ;"-12".isdigit() True False False >>> "A2".isidentifier() ;"2A".isidentifier() ; True False >>> "arrière-grand-mère".islower() ;"Blanche-Neige et les 7 nains".islower() True False >>> "VOL 714 POUR SIDNEY".isupper() ;"VOL 714 POUR SlDNEY".isupper() True False >>> " ".isprintable() ;"tabulation \t".isprintable() True False >>> "\n\t ".isspace() True Corrigé du TP4 IGI-3008 (2016-2017) SOLUTIONS Exercice 5 — Suppression/ajout/remplacement (p. 4) >>> "12".zfill(5) #} remplissage par des 0 '00012' >>> " \t\n suppression des espaces à gauche ".lstrip() 'suppression des espaces à gauche ' >>> " suppression des espaces à droite \t\n ".rstrip() ' suppression des espaces à droite' >>> " suppression des espaces à droite et à gauche \t\n ".strip() 'suppression des espaces à droite et à gauche' >>> s = "Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf" >>> t = s.replace('ami', 'copain') >>> print(s) Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf >>> print(t) Monsieur Jack, vous dactylographiez bien mieux que votre copain Wolf Notez que la chaîne initiale n’est pas modifiée, c’est une nouvelle chaîne qui est créée. 9 Exercice 6 — Comptage et recherche (p. 4) >>> >>> 65 >>> 6 >>> 4 s = "Monsieur Jack, vous dactylographiez bien mieux que votre ami Wolf" len(s) >>> 4 >>> 42 >>> -1 >>> 59 s.find('i') # place du premier 'i' rencontré (-1 si absent) s.count('e') # nombre de 'e' s.count('ie') # nombre de 'ie' s.find('i',40) # place du premier 'i' rencontré à partir de la position 40 s.find('i',20,30) # place du premier 'i' entre 20 inclus et 30 exclu s.rfind('i') # comme find, mais à rebours >>> s.index('i') # comme "find" mais erreur si absent 4 >>> s.index('i',40) 42 >>> s.index('i',20,30) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: substring not found >>> s.rindex('i') # comme index, mais à rebours 59 >>> def pangramme(s): ... alphabet = "abcdefghijklmnopqrstuvwxyz" ... for x in alphabet: ... if x not in s: ... return False ... else: ... return True ... >>> pangramme("portez ce vieux whisky au juge blond qui fume") True >>> pangramme("portez ce vieux whisky au juge blond qui boit") False Corrigé du TP4 IGI-3008 (2016-2017) SOLUTIONS 10 Exercice 7 — Partitions (p. 5) Exercice 8 — Normalisation d’un caractère (p. 6) >>> "Une chaîne à découper".partition('à') ('Une chaîne ', 'à', ' découper') >>> "Une chaîne à découper".partition('et') ('Une chaîne à découper', '', '') >>> "Une chaîne à découper".partition(' ') ('Une', ' ', 'chaîne à découper') >>> "Une chaîne à découper".split(' ') ['Une', 'chaîne', 'à', 'découper'] >>> "Une chaîne à découper".split(' ',2) # 2 coupures maxi. ['Une', 'chaîne', 'à découper'] >>> "Une chaîne à découper".split('e') ['Un', ' chaîn', ' à découp', 'r'] >>> """Un texte ... sur ... plusieurs ... lignes""".splitlines() ['Un texte', 'sur', 'plusieurs', 'lignes'] >>> mots = " Une chaîne à découper en mots >>> mots ['Une', 'chaîne', 'à', 'découper', 'en', 'mots'] >>> ' '.join(mots) 'Une chaîne à découper en mots' ".split() partition retourne systématiquement un triplet : (tête,séparateur,queue), avec séparateur et queue éventuellement vides (''). split retourne une liste éventuellement vide : [partie1, partie2,...] et perd les séparateurs. join est l’opération inverse de split . Elle recolle les morceaux d’une séquence de chaînes de caractères à l’aide du séparateur sur lequel elle porte. split et partition ont leurs équivalents à rebours : rsplit et rpartition. Corrigé du TP4 def normalise_lettre(c): """ normalisation d'une lettre minuscule""" if 'a' <= c <= 'z' or not c.isalpha(): # double inégalité légale en Python return c # c et une référence elif c in "àâ": return 'a' elif c in "æ": return "ae" elif c in "ç": return 'c' # 'c' est une constante caractère elif c in "èéêë": return 'e' elif c in "îï": return 'i' elif c in "ô": return 'o' elif c in "ùûü": return 'u' elif c in "ÿ": return 'y' elif c in "œ": return "oe" else: return "Erreur : on ne devrait jamais passer par là" Exercice 9 — Normalisation d’un mot (p. 6) def normalise_texte(s): res = "" for x in s.lower(): res+=normalise_lettre(x) return res IGI-3008 (2016-2017) SOLUTIONS 11 Exercice 10 — Palindrome (p. 6) def palindrome(s): """ retourne (-1, len(s)) si le texte est un palindrome (i,j) où s{i]!=s[j] si le texte n'est pas un palindrome """ s = normalise(s) i, j = 0, len(s)-1 while i<j: if not s[i].isalpha(): i += 1 continue if not s[j].isalpha(): j -= 1 continue if s[i]!=s[j]: return (i, j) i += 1 j -= 1 return (-1, len(s)) >>> s = """ 9691 ,EDNA' D NILUOM UA ...Andé, 1969""" >>> s = normalise(s) >>> palindrome(s) (881, 6860) En affichant le texte autour du caractère 880, on peut lire : ce noir Belzebuth, ô il offensé, tire !. Autour du caractère 6860, on peut lire Nef ! Folie ! Oh, tubez. On voit que le transcripteur aurait dû écrire : ce noir Belzebuth, oeil offensé, tire ! Avec cette modification : >>> palindrome(s) (-1, 7750) Corrigé du TP4 IGI-3008 (2016-2017)