Informatica di base

Gruppi, sostituizioni e alternative nelle regex

Autore

Manuel Ricci

Quando iniziammo il nostro percorso con le regular expression le descrissi come un qualcosa che serve a cercare e sostituire qualcosa nelle stringhe.

Con questa lezione iniziamo ad approfondire meglio il concetto di sostituzione grazie all’introduzione dei gruppi di cattura.

I gruppi di cattura

Immaginiamo di avere una serie di carattere come quella che segue:

1abcabcabcdefdefdefabcabcabc

Il nostro compito è quello di individuare le tre ripetizioni di def. Come fareste? Potremmo scrivere:

1d+e+f+

Ma questo per quanto sembri funzionare, ci apre ad altri possibili scenari come ddeff, deeff, ddeeeeffff e così via.

Per fare in modo che venga quindi individuata la tripletta def per tre volte possiamo scrivere:

1((def){3})

Tutte queste parentesi tonde sono necessarie per un motivo che spiego tra pochissimo, ma il gruppo di cattura è solitamente costituito da una coppia di parentesi tonde ( ).

Con l’uso del gruppo di cattura possiamo aggregare più espressioni e ottenere risultati più complessi e altrimenti impossibili con le entità viste finora.

Il motivo per cui ho scritto così tante parentesi tonde sta nel fatto che un gruppo di cattura ripetuto ({3}) catturerà solo l'ultima iterazione. Per cui bisogna mettere un gruppo di cattura attorno al gruppo ripetuto per acquisire tutte le iterazioni.

Il nome gruppo di cattura dovrebbe farvi sorgere spontanea una domanda, cosa cattura esattamente? Scopriamolo.

Cosa cattura il gruppo di cattura

L’uso dei gruppi ci apre il mondo delle backreference e al secondo scopo delle espressioni regolari, la sostituzione. I gruppi di cattura ci permettono infatti di riutilizzare ciò che viene catturato in un secondo momento. Un esempio chiarirà definitivamente quanto affermo.

Trovare una parola palindroma con le backreference

Un esempio molto basilare dell’uso delle backreference è quando stiamo cercando una o più parole palindrome, cioè quelle che si possono leggere sia da un verso che dall’altro e risultano uguali. Ipotizziamo quindi di avere le seguenti parole:

1osso
2esse
3elle
4emme
5enne
6alla
7ebbe

L’espressione regolare da usare è la seguente: \b(\w)(\w)\2\1\b

Tradotta:

  • \b è il bordo della parola
  • (\w) primo gruppo dove cerchiamo una lettera
  • (\w) secondo gruppo dove cerchiamo una lettera
  • \2 backreference al secondo gruppo
  • \1 backreference al primo gruppo

Perché prima il secondo e poi il primo? Perché le due lettere centrali di una palindroma a quattro lettere combaciano. È come spezzare una parola a metà e specchiare la metà rimasta.

Aumentiamo il tiro, che ne dite?

Invertire le parole con le espressioni regolari

Un esempio più complesso è quando abbiamo a che fare con un elenco simile a questo:

1ABAMONTI (Via)
2ABANO (Via)
3ABBA (Via)
4ABBADESSE (Via)
5ABBAZIA (Via)
6ABBIATEGRASSO (Piazza)
7ABBIATI (Via)

Ipotizziamo di voler invertire il nome con la via, per farlo ci serviranno ancora una volta i gruppi di cattura.

1([A-Z]+)\s\(([A-Za-z]+)\)

Tradotto:

  • ([A-Z]+) primo gruppo dove cerco da una a indeterminate lettere dalla A alla Z maiuscole.
  • \s spazio
  • \( escape della parentesi tonda per individuare il carattere che racchiude il tipo di via
  • ([A-Za-z]+) secondo gruppo, a differenza del primo stiamo attenti anche alla lettere minuscole
  • \) escape della parentesi tonda di chiusura per individuare il carattere che racchiude il tipo di via

Ho quindi due gruppi:

  1. Il nome della via
  2. Il tipo di via

    La nostra espressione di sostituzione sarà quindi:

1$2 \L$1\E

Se ti stai domandando dove metterla in regex101.com nel menu di sinistra nella sezione function, troverai substitution. L’area di lavoro si arricchirà di una nuova barra chiamata, per l’appunto, substitution. Lì dovrai inserire questa espressione di sostituzione.

Ma cosa fa esattamente?

  • $2 secondo gruppo (il tipo di via)
  • \L indica che da lì in poi i caratteri dovranno essere minuscoli
  • $1 primo gruppo (il nome della via)
  • \E interrompe \L

Il risultato sarà quindi:

1Via abamonti
2Via abano
3Via abba
4Via abbadesse
5Via abbazia
6Piazza abbiategrasso
7Via abbiati

Dare un nome ai gruppi di cattura

Partiamo subito con il dire un gigantesco grazie a Python per aver introdotto per primo la possibilità di dare un nome ai gruppi di cattura.

Per dare un nome possiamo usare la seguente sintassi:

1(?<nome>)

Quindi, la nostra espressione precedente può essere cambiata con

1(?P<nome>[A-Z]+)\s\((?P<tipo>[A-Za-z]+)\)

E la sostituzione di conseguenza

1${tipo} \L${nome}\E

Comodo vero?

La backreference ha un prezzo

Tutta questa comodità ha un costo. Il costo si calcola in termini di tempo e memoria utilizzata. Ci potrebbero essere situazioni dove non vogliamo memorizzare ciò che è stato trovato, come ad esempio nella nostra prima espressione regolare della lezione:

1((def){3})

Con $1 possiamo usare il gruppo, ma se non lo volessimo proprio, alleggerendo il lavoro dell’engine? Possiamo usare i non-capturing group o gruppo di non cattura, in questo modo:

1(?:(?:def){3})

Facendo seguire alla parentesi tonda di apertura il ?:.

Alternative

Già esplorate in passato, le alternative sono l’equivalente di un OR. Prendendo sempre in esame la regex precedente:

1(?:(?:def|abc){3})

Possiamo definire delle alternative andando a mettere una pipe | tra i valori che stiamo cercando. Così sia def che abc verranno individuate se ripetute per tre volte di seguito.

In pratica

Dato un elenco di file come questo:

1index.html
2script.js
3style.css
4main.js
5analytics.js
6chi-sono.html

Individuare tutti i file js e html (non solo le estensioni, ma anche tutto il nome del file).

La soluzione al problema è la seguente:

1^[^.]*\.(?:js|html)$
  • ^ inizio riga
  • [^.]*Tutti i caratteri ad esclusione di punto (per evitare il problema di ingordigia visto nella lezione precedente)
  • \. escape del punto
  • (?:js|html) gruppo di non cattura dove cerchiamo js o html
  • $ fine riga

Conclusioni

Con questa lezione abbiamo visto come si usano i gruppo di cattura, come fare delle sostituzioni con le regular expression e come si usa la pipe. Nella prossima lezione vedremo i caratteri unicode e ASCII.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.