Compter les voyelles dans une chaîne de caractères
informatique
Date de publication
20 août 2023
Les challenges de la semaine
Chaque semaine, le le serveur discord de docstring propose un challenge : un petit problème à résoudre avec un code. Chacun peut y participer, et les modérateurs donnent des conseils à chacun. A la fin de la semaine, les réponses sont rendues publiques, et on peut discuter ensemble de chaque solution.
Énoncé
Créez une fonction nb_voyelles(phrase: str)->int qui retourne le résultat du nombre total de voyelles dans une phrase passée en paramètre.
Conditions
Les voyelles sont : aeiou, y n’est pas pris en compte
Les voyelles accentuées ne sont pas prises en compte
La phrase passée en paramètre doit être écrite en minuscule
La fonction filter est une fonction par défaut en python. Elle permet de “filtrer” les éléments d’une liste.
Pour filtrer une liste, il faut un prédicat, c’est-à-dire une fonction qui retourne un booléen (True ou False). Ce prédicat va permettre, pour chaque élément, de dire si on doit le garder ou non.
Exemple : garder seulement les éléments pairs
def est_pair(nombre):"""Prédicat qui dit si un nombre est pair."""# pair <=> divisible par 2 <=> le reste de la division par 2 est 0return nombre %2==0liste = [3, 1, 4, 1, 5, 9, 2, 6, 5]# on filtre la liste avec la condition `est_pair`# on ne garde donc que les éléments qui respectent le prédicat : les éléments pairs# la fonction filter ne renvoie pas une liste, donc on ajoute la fonction `list`liste_filtrée =list(filter(est_pair, liste))print(liste_filtrée)
[4, 2, 6]
Application au problème
On peut utiliser un filter pour ne garder que les voyelles dans une chaîne de caractères. Il suffit ensuite de mesurer la longueur de la liste de caractères obtenue.
Prédicat
Il faut donc écrire le prédicat qui teste si une lettre est une voyelle. On peut simplement utiliser le code : lettre in "aeiou". Le prédicat peut être écrit comme suit :
Ceci dit, ce code fonctionne aussi si lettre n’est pas une lettre, mais plusieurs lettre. On peut donc limiter la longueur de la lettre, par exemple avec une assertion qui oblige la longueur de lettre à être 1. Cela lèvera une erreur si le paramètre donné n’est pas une seule lettre.
La seconde étape est d’utiliser ce prédicat pour filtrer une chaîne de caractères. La fonction filter accepte directement des chaînes de caractères en entrée. Cependant, la fonction filter ne renvoie pas une liste, mais un objet filter. On ne peut pas mesurer directement la longueur d’un objet filter avec la fonction len, il faut donc d’abord le transformer en liste.
Le golf est un jeu dans lequel il faut atteindre un but en le moins de coups possible. Donc le golf de code (code golf) est un jeu dans lequel il faut réaliser un code avec le moins de caractères possible.
J’ai donc cherché à trouver une solution la plus courte possible au challenge. Voici ma proposition :
nb_voyelles_golf=lambda p:sum(l in"aeiou"for l in p)
Cette solution fonctionne en parcourant chaque lettre l de la phrase p, et en testant à chaque fois si elle est dans "aeiou". On obtient donc une liste de True et False. On fait ensuite la somme de cette liste, ce qui revient à compter les True, puisque les False sont transformés en 0.
Cette solution est humoristique, mais elle permet aussi de démontrer certains principes de la programmation objet.
objet Counter
Le premier objet à créer est Counter, un objet qui compte les occurences d’une valeur dans une liste (un itérable).
Dans mon implémentation, l’attribut item est un attribut non modifiable qui contient la valeur à compter. Il est en fait construit sur la base de l’attribut privé __item__. L’attribut non-modifiable est créé avec le décorateur @property, qui transforme une méthode en attribut non-modifiable.
La fonction matches_item est un prédicat qui dit si un objet est égal (correspond) avec la valeur a compter (item).
La méthode magique __call__ définit ce qui se passe lorsque l’on appelle un objet comme une fonction. Dans notre cas, l’objet retourne le nombre d’occurences de la valeur dans le paramètre.
On a donc un objet Counter, qui compte une valeur particulière. Cet objet est un Callable, c’est-à-dire qu’on peut l’appeler avec une liste en paramètre, et qu’il va retourner le nombre d’occurrences.
L’objet LetterCounter est un Counter spécialisé dans le comptage de lettres. Il est construit sur la base de l’objet Counter. Pour ne pas avoir à réécrire le code de Counter, on utilise l’héritage : la classe LetterCounter hérite de Counter.
La seule différence avec l’objet Counter est qu’il oblige l’objet compté à être une lettre. C’est la fonction __assert_is_letter__ qui lève une erreur dès que la valeur à compter n’est pas une lettre.
class LetterCounter(Counter):def__init__(self, letter: str):self.__assert_is_letter__(letter)super().__init__(letter)def __assert_is_letter__(self, letter: str):if (notisinstance(letter, str)) orlen(letter) !=1:raiseValueError(f"'{letter}' is not a letter.")def__repr__(self) ->str:returnf"LetterCounter('{self.item}')"
Exemple de LetterCounter
compteur_de_v = LetterCounter('v')texte ="je vois des avions voler"print(compteur_de_v(texte))
3
objet ParallelLetterCounter
On veut maintenant pouvoir compter plusieurs lettres à la fois. Pour cela, on créé un objet ParallelLetterCounter, qui va regrouper plusieurs objets LetterCounter et les faire fonctionner sur le même texte.
Il suffit de prendre une chaîne de caractères des lettres à compter (ici stockée dans un attribut privé et accessible via la propriété non modifiable letters_to_count) et de coder une boucle qui lance un LetterCounter pour chacune de ces lettres, puis qui fait le total des comptes.
class ParallelLetterCounter:def__init__(self, letters_to_count: str):self.__letters_to_count__ =str(letters_to_count)@propertydef letters_to_count(self) ->str:returnself.__letters_to_count__def__call__(self, text: str) ->int: text =str(text) count =0for letter inself.letters_to_count: counter = LetterCounter(letter) count += counter(text)return count
Exemple de ParallelLetterCounter
compteur_de_abcd = ParallelLetterCounter('abcd')texte ="il est dangereux de boire cette eau."print(compteur_de_abcd(texte))
6
objet Sentence
L’objet Sentence représente une phrase, autrement dit une chaîne de caractères. Il contient des fonctions utilitaires, notamment la possibilité de récupérer le nombre de voyelles d’un texte.
Ici, la propriété non modifiable text contient le texte que l’on manipule. La propriété case_sensitive est un booléen (True ou False) (elle ne peut pas être assignée à une autre valeur) qui contrôle si la recherche sera sensible à la casse (à la capitalisation, c’est-à-dire à la différence entre majuscules et minuscules).
La méthode vowels_count retourne le nombre de voyelles d’un texte, mais sans prendre en compte l’option case_sensitive (elle est toujours sensible à la capitalisation). La propriété number_of_vowels contient le nombre de voyelles, en considérant cette fois la valeur de l’option case_sensitive dans son calcul.
class Sentence:def__init__(self, text: str, case_sensitive: bool=False):self.__text__ =str(text)self.__case_sensitive__ = case_sensitive@propertydef text(self):ifself.case_sensitive:returnself.__text__else:returnself.__text__.lower()@propertydef case_sensitive(self) ->bool:returnself.__case_sensitive__@case_sensitive.setterdef case_sensitive(self, value: bool) ->None:self.__case_sensitive__ =bool(value)def__repr__(self) ->str:returnf"Phrase('{self.text}')"def vowels_count(self) ->int:"""Deprecated Returns the number of vowels in this sentence. You should use the `number_of_vowels` property instead. This does not take case into account, even if `case_sensitive` is set to False """ vowel_counter = ParallelLetterCounter("aeiou")return vowel_counter(self.__text__)@propertydef number_of_vowels(self) ->int:"""The number of vowels contained in this sentence. The letter "y" is not considered to be a vowel. If case sensitivity is enabled, it will only count lower case letters. """ vowel_counter = ParallelLetterCounter("aeiou")return vowel_counter(self.text)
Cette solution est un code obfusqué (code impénétrable en français), c’est-à-dire que son but est d’être le plus incompréhensible possible pour des humains.
phrase ="cette solution est vraiment illisible"# 14 voyellesprint(nb_voyelles_obfuscated(phrase))
14
Solution avec un Counter
Le module collections contient plusieurs objets très utiles pour manipuler des collections d’objets. Notamment, l’objet collections.Counter permet de compter les occurrences de chaque valeur dans une liste. Par défaut, un Counter compte toutes les valeurs, mais on peut lui donner un paramètre : une liste des valeurs à compter. Nous pouvons donc utiliser un Counter("aeiou") pour compter les voyelles dans une chaîne de caractères.
Il suffit ensuite de mettre à jour le compteur (ajouter au décompte des éléments) avec notre phrase. Le fait de préciser les lettres à compter à la création du compteur fait qu’il ne comptera que ces lettres. Le nombre de voyelles sera donc le total des décomptes.
import collectionsdef nb_voyelles_counter(phrase: str) ->int: compteur = collections.Counter(phrase)returnsum(compteur[v] for v in"aeiou")
Exemple de comptage des voyelles
phrase ="les compteurs sont vraiment utiles"# 11 voyellesprint(nb_voyelles_counter(phrase))