Solution du problème PrintPages¶
Vous trouverez ici la solution pour l'exercice suivant:
Exercice de base¶
Il faut déjà découper la chaine de characters qui est donnée en parametre avec la fonction split.
Ensuite il faut convertir une chaine de characters en entier avec int.
Puis retourner le tout dans une liste:
def printpages(selection):
result = list()
for item in selection.split(","):
page = int(item)
result.append(page)
return result
On peut aussi rendre les choses un peu plus belles avec une comprehension de liste:
def printpages(selection):
return [int(item) for item in selection.split(",")]
Afin de ne plus être embeté par les espaces que mettrait l'utilisateur, on peut simplement les enlever dès le départ. Ainsi, nous ne serons plus génés par eux:
def printpages(selection):
return [int(item) for item in selection.replace(" ", "").split(",")]
Bonus 1¶
Commençons par generer le ploint virgule comme séparateur suplementaire. Pour cela, on peut decouper la chaine de charactere en entrée d'abord avec , et ensuite ;:
def printpages(selection):
result = list()
for item in selection.replace(" ", "").split(","):
for subitem in item.split(";"):
result.append(int(subitem))
return result
Nous restons avec 2 séparateurs dans cet exercice, mais si nous en avions plus, on se rend compte que ceci n'est pas adapté pour avoir de plus en plus de séparateurs. Une solution plus envisageable est d'utiliser une expression rationnelle:
import re
def printpages(selection):
result = list()
for item in re.split("[,;]", selection.replace(" ", "")):
page = int(item)
result.append(page)
return result
Et avec une compréhension de liste:
import re
def printpages(selection):
return [int(item) for item in re.split("[,;]", selection.replace(" ", ""))]
Bonus 2¶
Un peu plus compliqué, il va falloir gérer les plages de pages. Pour chacun des éléments de la selection, nous allons déjà voir s'il s'agit d'un entier (duck-typing). Dans le cas contraire, c'est une plage qu'il va falloir gérer.
import re
def printpages(selection):
result = list()
for item in re.split("[,;]", selection):
try:
result.append(int(item))
except ValueError:
start, end = item.split("-")
for i in range(int(start), int(end) + 1):
result.append(i)
return result
On peut simplifier un petit peu en utilisant la methode extend d'une liste:
import re
def printpages(selection):
result = list()
for item in re.split("[,;]", selection):
try:
result.append(int(item))
except ValueError:
start, end = item.split("-")
result.extend(range(int(start), int(end) + 1))
return result
Bonus 3¶
L'utilisation de yield pour chaque élément de la liste va suffire à faire de notre fonction un générateur:
import re
def printpages(selection):
for item in re.split("[,;]", selection):
try:
yield int(item)
except ValueError:
start, end = item.split("-")
for i in range(int(start), int(end) + 1):
yield i
On peut toutefois trouver interressant d'utiliser une nouvelle construction introduite avec python 3.3 (plus de details ici (PEP-380)): yield from <expr>
import re
def printpages(selection):
for item in re.split("[,;]", selection):
try:
yield int(item)
except ValueError:
start, end = item.split("-")
yield from range(int(start), int(end) + 1)
On peut même simplifier drastiquement le resultat en utilisant findall d'une expression rationnelles.
import re
pair_re = re.compile(
r"""
(\d+) # First group: single, or start, digit
(?: # Non matching group
\s*-\s*(>end|\d+) # end range digit or `end` keyword
)? # force second item to be parsed is possible
""",
re.VERBOSE
)
def printpages(selection):
pairs = (
(start, end) if end else (start, start)
for start, end in pair_re.findall(selection)
)
for start, end in pairs:
yield from range(int(start), int(end) + 1)
re.VERBOSE permet de decrire une regex pour le plus grand bien des revues de codes, et autres relecture pour debug.
Note2: On peut remarque l'utilisation de <EXPR1> if <COND> else <EXPR2> pour avoir toujours un range même losque l'on a un seul chiffre, et ainsi simplifier la seconde partie de l'algo.
Et pour finir, une petite alternative en changeant les deux dernieres lignes avec le yield en utilisant un generateur:
import re
pair_re = re.compile(r"""(\d+)(?:\s*-\s*(>end|\d+))?""", re.VERBOSE)
def printpages(selection):
pairs = (
(start, end) if end else (start, start)
for start, end in pair_re.findall(selection)
)
return (
num
for start, end in pairs
for num in range(int(start), int(end) + 1)
)
for sont imbriqués de manière non intuitive quant a la comprehension de liste, mais au final, sont dans l'ordre que l'on aurait utilisé en écrivant les boucles complétement.
Bonus 4¶
Il n'est pas compliqué non plus ici de résoudre ce bonus. Dèjà il faut que la fonction prenne un parametre suplementaire, avec une valeur par default afin que les tests précédents continuent à fonctionner.
Ensuite, si on trouve le mot clef >end, alors on doit prendre comme fin, le nombre total de pages:
import re
def printpages(selection, max_pages=0):
for item in re.split("[,;]", selection):
try:
yield int(item)
except ValueError:
start, end = item.split("-")
if end == ">end":
end = max_pages
yield from range(int(start), int(end) + 1)
Et en reprennant les resultats précédents:
import re
pair_re = re.compile(r"""(\d+)(?:\s*-\s*(>end|\d+))?""", re.VERBOSE)
def printpages_re_base(selection, max_pages=0):
pairs = (
(start, end) if end else (start, start)
for start, end in pair_re.findall(selection)
)
for start, end in pairs:
if end == ">end":
end = max_pages
yield from range(int(start), int(end) + 1)
Et voila qui conclut cet exercice. Rien de bien compliqué finalement.