CodeKitHub
Regex greedy vs lazy matching: γιατί το pattern σου πιάνει πολλά περισσότερα

Regex greedy vs lazy matching: γιατί το pattern σου πιάνει πολλά περισσότερα

Δημοσιεύτηκε 24 Ιουλ 2026

Γράφεις <b>.*</b> για να πιάσεις το περιεχόμενο ενός bold tag, το δοκιμάζεις σε <b>Hello</b>, δουλεύει τέλεια. Μετά το τρέχεις σε πραγματικό HTML με δύο bold tags στην ίδια γραμμή — <b>Hello</b> and <b>World</b> — και αντί για δύο matches, παίρνεις ένα τεράστιο match που καλύπτει και τα δύο tags. Αυτό δεν είναι bug στη regex μηχανή σου· είναι η προεπιλεγμένη συμπεριφορά των *, + και {n,m}, και έχει μια διόρθωση ενός χαρακτήρα μόλις καταλάβεις τι συμβαίνει.

Γιατί το .* πιάνει περισσότερα απ’ όσα περιμένεις

Από προεπιλογή, οι quantifiers στη regex είναι greedy (άπληστοι) — το .* δεν σημαίνει “ταίριαξε κάποιους χαρακτήρες”, σημαίνει “ταίριαξε όσο το δυνατόν περισσότερους χαρακτήρες, και υποχώρησε μόνο αν το υπόλοιπο pattern το απαιτεί απόλυτα.”

Ας δούμε βήμα-βήμα το <b>.*</b> πάνω στο <b>Hello</b> and <b>World</b>:

  1. Το <b> ταιριάζει με το πρώτο <b>.
  2. Το .* ξεκινά καταναλώνοντας ολόκληρο το υπόλοιπο του string — τα πάντα μέχρι το τέλος, συμπεριλαμβανομένου και του δεύτερου <b>World</b>.
  3. Η μηχανή χρειάζεται τώρα να βρει </b> για να ολοκληρώσει το match, οπότε αρχίζει να υποχωρεί από το τέλος του .* έναν χαρακτήρα τη φορά.
  4. Το πρώτο σημείο (σαρώνοντας προς τα πίσω από το τέλος) όπου ταιριάζει το </b> είναι το τελευταίο </b> στο string — όχι το πρώτο στο οποίο “λογικά” θα έπρεπε να σταματήσει.

Έτσι, το match καταλήγει να είναι ολόκληρο το <b>Hello</b> and <b>World</b>, γιατί το greedy matching δοκιμάζει πάντα πρώτα το μακρύτερο δυνατό string και το συρρικνώνει μόνο όσο χρειάζεται ελάχιστα για να πετύχει το υπόλοιπο pattern.

Η λύση: κάνε τον quantifier lazy με το ?

Προσθέτοντας ένα ? αμέσως μετά από έναν quantifier, τον μετατρέπεις από greedy σε lazy (αλλιώς “non-greedy” ή “reluctant”): *?, +?, ??, {n,m}?.

Ένας lazy quantifier κάνει το αντίθετο: ξεκινά ταιριάζοντας όσο το δυνατόν λιγότερους χαρακτήρες, και επεκτείνεται μόνο αν το υπόλοιπο pattern δεν μπορεί ακόμα να πετύχει.

Το <b>.*?</b> πάνω στο ίδιο string:

  1. Το <b> ταιριάζει με το πρώτο <b>.
  2. Το .*? ξεκινά ταιριάζοντας μηδέν χαρακτήρες.
  3. Η μηχανή ελέγχει: ταιριάζει το </b> ακριβώς εδώ; Όχι (βρισκόμαστε στο “Hello…”, όχι ακόμα σε </b>) — οπότε επεκτείνει το .*? κατά ακριβώς έναν χαρακτήρα και ελέγχει ξανά.
  4. Αυτό επαναλαμβάνεται χαρακτήρα-χαρακτήρα μέχρι το .*? να έχει καταναλώσει ακριβώς το Hello, οπότε το </b> ταιριάζει αμέσως.

Αποτέλεσμα: τα <b>Hello</b> και <b>World</b> επιστρέφουν ως δύο ξεχωριστά matches, που είναι σχεδόν πάντα αυτό που πραγματικά ήθελες όταν αναλύεις δομές τύπου tag ή delimiter.

Πότε θέλεις πραγματικά greedy (δεν είναι απλώς “η λάθος προεπιλογή”)

Το greedy δεν είναι λάθος στη γλώσσα — είναι σωστό για μια διαφορετική, εξίσου συχνή περίπτωση: να ταιριάξεις το εξωτερικό όριο κάτι, όχι τη μικρότερη μονάδα μέσα του. Αν εξάγεις “όλα ανάμεσα στο πρώτο { και το τελευταίο }” σε ένα κομμάτι κειμένου τύπου JSON (ας πούμε, για να πιάσεις ολόκληρο ένα object ανεξάρτητα από τη φωλιά nesting), το greedy είναι ακριβώς σωστό, ενώ το lazy θα σταματούσε στο πρώτο εσωτερικό }, δίνοντάς σου ένα κομμένο, μη έγκυρο απόσπασμα.

Ο εμπειρικός κανόνας: lazy για επαναλαμβανόμενα μικρά οριοθετημένα κομμάτια (tags, quoted strings, στοιχεία λίστας)· greedy για “πιάσε ολόκληρο το εξωτερικό εύρος”.

Γρήγορη αναφορά

Pattern Συμπεριφορά Χρησιμοποίησέ το όταν
.*, .+, {n,m} Greedy — ταιριάζει το μεγαλύτερο δυνατό Θέλεις το εξωτερικό εύρος, ή υπάρχει μόνο ένα match στο string
.*?, .+?, {n,m}? Lazy — ταιριάζει το μικρότερο δυνατό Έχεις πολλά παρόμοια οριοθετημένα κομμάτια και θέλεις το καθένα ξεχωριστά

Αν δεν είσαι σίγουρος τι κάνει στην πραγματικότητα ένα pattern πάνω στα πραγματικά σου δεδομένα αντί για το test string σου, τρέχοντάς το πάνω στο πραγματικό κείμενο με πολλαπλά matches σε έναν regex tester με ζωντανή επισήμανση matches κάνει τη διαφορά αμέσως ορατή — θα δεις ένα τεράστιο επισημασμένο block για το greedy έναντι πολλών ξεχωριστών για το lazy, κάτι που συνήθως είναι πιο γρήγορο από το να το σκεφτείς χαρακτήρα-χαρακτήρα.

← Επιστροφή στο blog