6.10 Piller Python: la méthode extract - naver/lispe GitHub Wiki

Python

English Version

Pour le néophyte que j'étais au début des années 2000, Python fut une vraie découverte. Voilà un langage où la compilation n'entravait en rien le fil de la pensée. Le code infiniment malléable permettait une exploration rapide d'un problème. En quelques instants, on pouvait ouvrir un fichier, charger les données et les modifier à volonté grâce à une syntaxe concise.

Très concise

Trop peut-être...

Le premier code que j'examinais contenait la ligne suivante:

sub = s[-10:]

Heu!!!

A l'époque Internet était loin d'être une source illimitée d'information et malgré mes efforts et une lecture attentive de la documentation, cette notation résistait à ma compréhension. Evidemment, pour les générations nourries depuis leur enfance à Python, l'idée que cette simple instruction puisse poser problème peut susciter une certaine moquerie.

Pour moi, l'idée qu'un nombre négatif puisse apparaître dans un index était inconcevable. Toute mon expérience de programmeur C ou Pascal me hurlait que c'était mal. Un index négatif ne pouvait pas exister.

Fort heureusement, le collègue qui m'avait fait découvrir Python m'expliqua rapidement l'idée sous-jacente. Un index négatif se calculait à partir de la fin du conteneur.

Or j'avais déjà rencontré cette notation dans un précédent projet en APL, langage qui, j'en suis sûr, était connu des membres de l'équipe ABC à laquelle Van Rossum contribuait.

s ← m[¯10;]

Hélas pour moi, je n'avais pas fait le lien à l'époque...

Comme quoi, les bonnes idées sont rarement perdues en informatique...

extract

En Lisp, une notation aussi concise est hélas inaccessible. La seule solution est d'implanter un mécanisme équivalent sous la forme d'une fonction.

Cette fonction s'appelle extract et elle fonctionne exactement comme la notation Python ci-dessus...

(extract s -10 0)

Notons qu'à la différence de Python, les indexes négatifs intègrent le 0.

Nous pourrions aussi écrire:

(extract s -10 (size s))

Mais, cette forme est plus coûteuse à l'exécution...

Sous-Chaines de caractères

Mais pourquoi s'arrêter là...

En fait, lorsque l'on fait de la manipulation de chaines de caractère, l'extraction se fait souvent en repérant une sous-chaine à partir de laquelle on effectue l'extraction.

Imaginons que nous ayons la chaine suivante: "123[4567]890 et que nous voulions extraire la partie entre les crochets: [4567].

Dans la plupart des langages de programmation, le premier pas consiste à trouver la position du '[' puis celle du ']' et à extraire notre chaine.

# Note: Il existe certainement une façon plus efficace en Python que je ne connais pas...

sub = ""
pdebut = s.find("["])
if pdebut != -1:
   pfin = s.find("]")
   if pfin > pdebut:
      sub = s[pdebut:pfin+1]

Si l'on veut exclure les crochets, il faut évidemment manipuler les indexes:

sub = ""
pdebut = s.find("["])
if pdebut != -1:
   pfin = s.find("]")
   if pfin > pdebut:
      sub = s[pdebut-1:pfin]

Notons qu'à chaque étape, il nous faut tester les indexes pour vérifier que les éléments recherchés soient bien présents.

Evidemment, on peut aussi utiliser rfind si jamais on cherche la dernière occurence dans la chaine...

Recherche encapsulée

Après tout rien nous empêche d'étendre la notation de Python pour y inclure directement les chaines à chercher...

Non seulement, extract sait utiliser des chaines de caractères comme index, mais il permet aussi de préciser si ces caractères doivent ou non être inclus dans la sous-chaine finale.

On peut même jouer sur la distinction find/rfind.

On peut donc écrire directement:

(setq s "123[4567]890")
(extract s "[" "]") ; donnera "4567"

Index numérique après la chaine

Il existe deux cas, lorsque l'on donne un index numérique après la recherche de la chaine:

  • Index > 0, dans ce cas cet index est considéré comme un nombre de caractères à extraire après la sous-chaine
  • Index <= 0, dans ce cas il s'agit d'une position calculée à partir de la fin de la chaine
(setq s "1234567890")
(extract s "5" 2) ; renvoie "67"
(extract s "5" -2) ; renvoie "678"
(extract s "5" 0) ; renvoie "67890"

Garder ou non les caractères: +

Par défaut, l'exemple suivant renverra la chaine: 4567, les crochets sont perdus.

(setq s "123[4567]890")
(extract s "[" "]") ; donnera "4567"

Si l'on veut les conserver, il suffit de faire précéder la chaine de recherche par: +.

(extract s +"[" +"]") ; donnera "[4567]"

Evidemment l'opérateur + peut être aussi utilisé avec une évaluation, à condition que celle-ci renvoie une chaine de caractères.

(extract s + (+ "[" "4") 0) ; renverra [4567]890

Chercher depuis la fin: -

De la même façon qu'un index numérique négatif permet de calculer la position d'un caractère depuis la fin de la chaine, on peut utiliser l'opérateur - pour que la recherche commence depuis la fin de la chaine.

(setq s "ab12ab34")
(extract s "ab" 0) ; renverra "12ab34"

; avec l'opérateur -
(extract s -"ab" 0) ; renverra "34"

Combiner les deux opérateurs: -+

Cet opérateur -+ permet de rechercher notre chaine depuis la fin et de la conserver dans la chaine finale.

(setq s "ab12ab34")

; avec l'opérateur -
(extract s -"ab" 0) ; renverra "34"

; avec l'opérateur -+
(extract s -+"ab" 0) ; renverra "ab34"

Conclusion

Peut-être qu'une page Wiki complète est aussi un peu trop pour décrire une instruction aussi simple et évidente que extract... Cependant, la possibilité de considérer les sous-chaînes comme des index est incroyablement utile.

Tellement utile que lorsque j'ai implémenté : tamgu un autre de mes langages de programmation, inspiré de Python (mais aussi de Haskell et Prolog), ça a été la toute première notation que j'ai introduite.

//Voici un exemple de code tamgu avec des chaînes comme index entre crochets :

chaîne s = "qsjkqdkqs[123]qsdjkdsj" ;
println(s["[" :"]"]) ;

J'utilise LispE dans de nombreux projets pour filtrer les données textuelles des fichiers JSON ou XML et cette instruction est certainement celle que j'utilise le plus...