W3docs

Quantificatori greedy e lazy

Come funzionano i quantificatori greedy e lazy nelle espressioni regolari JavaScript: greedy consuma il massimo poi torna indietro, lazy (*?, +?, ??, {n,m}?) prende il minimo.

Un quantificatore come *, + o ? dice a un'espressione regolare quante volte il pattern precedente può ripetersi. Ma quando più lunghezze di testo soddisferebbero tutte il pattern, il motore deve decidere quale scegliere. È questa decisione che i quantificatori greedy e lazy controllano.

Per impostazione predefinita ogni quantificatore è greedy: prende quanti più caratteri possibile. Aggiungendo un ? dopo il quantificatore esso diventa lazy: prende il meno possibile. Scegliere la modalità sbagliata è uno degli errori più comuni con le regex — il sintomo classico è un pattern che "corrisponde a troppo". Questa pagina spiega il meccanismo alla base di entrambe le modalità, così potrai scegliere quella giusta in modo consapevole.

Come funziona davvero il matching greedy: consuma, poi torna indietro

Un quantificatore greedy non sa magicamente dove fermarsi. Funziona in due fasi:

  1. Consuma il massimo. .* inizia inghiottendo tutto il resto della stringa.
  2. Torna indietro (backtrack). Se il pattern che segue .* non riesce più a corrispondere (perché tutto è già stato consumato), il motore restituisce i caratteri uno alla volta, riprovando dopo ciascuno, finché l'intero pattern non corrisponde.

Quindi greedy significa "prendere tutto, poi restituire malvolentieri il minimo necessario perché il resto del pattern abbia successo." Capire il passo di backtracking è la chiave per prevedere cosa fanno i pattern greedy.

* greedy in azione


javascript— editable

Qui .* corrisponde a qualsiasi carattere zero o più volte. Poiché non c'è nulla di obbligatorio dopo di esso, non è necessario alcun backtracking e mantiene tutto fino alla fine della riga, producendo "ABCD*E".

+ greedy in azione


javascript— editable

C+ corrisponde a "C" una o più volte e in modo greedy prende tutte e tre, quindi la corrispondenza è "ABCCC".

Come funziona il matching lazy: consuma il minimo, poi espandi

Un quantificatore lazy inverte la strategia:

  1. Consuma il minimo. .*? inizia non corrispondendo a nulla.
  2. Espandi. Solo se il resto del pattern non riesce a corrispondere il motore lascia che il quantificatore lazy prenda un altro carattere, poi riprova — ripetendo finché l'intero pattern non ha successo.

Quindi lazy significa "prendere il meno possibile, e crescere solo quando costretto." Qualsiasi quantificatore diventa lazy aggiungendo ?.

*? lazy in azione


javascript— editable

Questo è il caso che crea confusione. Il risultato è solo "AB". Perché? Perché .*? può corrispondere a nulla, e nel pattern non c'è nulla dopo di esso che forzi un consumo maggiore. Non appena AB corrisponde, il pattern è già completo, quindi il quantificatore lazy si ferma felicemente a zero caratteri. Un quantificatore lazy si espande solo quando qualcosa dopo di esso — un delimitatore, un letterale o un anchor — lo richiede.

+? lazy in azione


javascript— editable

C+? deve corrispondere ad almeno una "C" (questo è ciò che richiede +), e nulla dopo di esso ne richiede di più, quindi si ferma alla prima: "ABC".

L'esempio canonico: <.*> vs <.*?>

La differenza tra greedy e lazy è più facile da vedere quando c'è qualcosa dopo il quantificatore. Il caso più classico è la corrispondenza dei tag HTML. Esegui entrambi i pattern sulla stessa stringa:


javascript— editable
  • Greedy /<.*>/ corrisponde a "<p>Hello</p>" — l'intera stringa. .* consuma tutto, poi torna indietro quanto basta per lasciare un > al > finale nel pattern, atterrando sull'ultimo >.
  • Lazy /<.*?>/ corrisponde solo a "<p>" — un singolo tag. .*? si espande carattere per carattere e si ferma non appena raggiunge il primo >.

Quando il tuo obiettivo è "corrispondere a un tag", "corrispondere a una stringa tra virgolette" o "corrispondere fino al prossimo delimitatore", lazy è quasi sempre ciò che vuoi.

La famiglia completa dei quantificatori lazy

Ogni quantificatore greedy ha un gemello lazy creato aggiungendo ?:

GreedyLazySignificato della forma lazy
**?Zero o più, il meno possibile
++?Uno o più, il meno possibile
???Zero o uno, preferisce zero
{2,5}{2,5}?Tra 2 e 5, preferisce 2
{2,}{2,}?Almeno 2, preferisce 2

Nota che ?? non è un errore di battitura: il primo ? è il quantificatore (zero o uno) e il secondo lo rende lazy, quindi preferisce non corrispondere a nulla quando ha una scelta.


javascript— editable

Quando usare i quantificatori lazy?

Una regola pratica:

Usa un quantificatore lazy quando vuoi corrispondere fino alla prima occorrenza di un delimitatore, e greedy quando vuoi tutto fino all'ultimo.

Situazioni comuni in cui lazy è la scelta giusta:

  • Estrarre un singolo tag HTML/XML: /<.*?>/.
  • Catturare il contenuto di una coppia di virgolette o parentesi: /".*?"/, /\(.*?\)/.
  • Prendere il testo fino al prossimo separatore senza sconfinare nel record successivo.

Due avvertimenti importanti:

  • Un delimitatore è spesso più chiaro della laziness. Invece di /".*?"/ puoi scrivere /"[^"]*"/ con una classe di caratteri negata. Questo evita completamente il backtracking ed è in genere più veloce e prevedibile.
  • Lazy ha bisogno di qualcosa su cui fermarsi. Come ha mostrato l'esempio AB.*?, un quantificatore lazy alla fine di un pattern (senza nulla che lo forzi in avanti) si riduce al suo minimo e non corrisponde quasi a nulla. Abbinalo a un letterale, un anchor o un gruppo di cattura successivo.
  • I quantificatori sono greedy per impostazione predefinita — prendono il massimo, poi tornano indietro per fare in modo che il resto del pattern corrisponda.
  • Aggiungi ? per rendere un quantificatore lazy — prende il minimo, poi si espande solo quando costretto.
  • Un quantificatore lazy con nulla dopo di esso corrisponde al minimo legalmente possibile (spesso nulla), quindi dagli sempre un delimitatore o un anchor su cui fermarsi.
  • La famiglia lazy completa è *?, +?, ?? e {n,m}?.
  • Usa lazy quando vuoi "fino al primo" delimitatore; altrimenti una classe di caratteri negata è spesso la scelta più pulita e veloce.

Pratica

Pratica
Data la stringa '<p>Hi</p>', cosa corrisponde il pattern lazy /<.*?>/ ?
Data la stringa '<p>Hi</p>', cosa corrisponde il pattern lazy /<.*?>/ ?
Was this page helpful?