Regelmessige uttrykk med gå Del 2

Oversikt

Dette er del to av en todelt serie av opplæringsprogrammer om vanlige uttrykk i Go. I del ett lærte vi hva vanlige uttrykk er, hvordan du uttrykker dem i Go, og grunnleggende om å bruke Go regexp-biblioteket for å matche tekst mot vanlige uttrykksmønstre. 

I del to vil vi fokusere på å bruke regexp-biblioteket i sin helhet, inkludert å samle vanlige uttrykk, finne en eller flere kamper i teksten, erstatte vanlige uttrykk, gruppere submatcher og håndtere nye linjer.

Bruke Regexp-biblioteket

Regexp-biblioteket gir fullverdig støtte for vanlige uttrykk, samt muligheten til å kompilere mønstrene dine for effektivere utførelse når du bruker det samme mønsteret for å matche mot flere tekster. Du kan også finne indekser av kamper, erstatte kamper og bruke grupper. La oss dykke inn.

Kompilere Regex

Det er to metoder for å samle regexes: Kompilere() og MustCompile (). Kompilere() vil returnere en feil hvis det angitte mønsteret er ugyldig. MustCompile () vil bli panikk. Kompilering anbefales hvis du bryr deg om ytelse og planlegger å bruke samme regex flere ganger. La oss forandre vår kamp() hjelperfunksjon for å ta en kompilert regex. Vær oppmerksom på at det ikke er behov for å sjekke om feil fordi den kompilerte regexen må være gyldig.

func match (r * regexp.Regexp, tekststreng) matchet: = r.MatchString (tekst) hvis matchet fmt.Println ("√", r.String (), ":", tekst) else fmt. Println ("X", r. String (), ":", tekst) 

Her er hvordan du kompilerer og bruker samme kompilerte regex flere ganger:

func main () es: = '(\ bcats? \ b) | (\ brats? \ b)' e: = regexp.MustCompile (es) match og katter ") match (e," Kataloget er klart. Det er hotdog tid! ") match (e," Det er en hund spise hund verden. ") Output: √ (\ bcats? \ b) | \ b) | (\ brats? \ b): Det regner hunder og katter X (\ bcats? \ b) | (\ bdogs? \ b) | (\ brats? \ b): Kataloget er klart. Det er hotdog tid! √ (\ bcats? \ B) | (\ bdogs? \ B) | (\ brats? \ B): Det er en hund spiser hundens verden. 

Finne

Regexp-objektet har a mye av FindXXX () metoder. Noen av dem returnerer første kamp, ​​andre returnerer alle kamper, og likevel returnerer andre en indeks eller indekser. Interessant nok matcher navnene på alle 16 funksjonsmetodene følgende regex: Finn (alle)? (String)? (Submatch)? (Index)?

Hvis 'Alle' er til stede, returneres alle kampene vs. venstre side. Hvis 'String' er til stede, er målteksten og returverdiene strenge vs byte-arrays. Hvis 'Submatch' er til stede, returneres submatcher (grupper) vs. bare enkle kamper. Hvis 'Indeks' er til stede, returneres indeksene i målteksten vs. de faktiske kampene.

La oss ta en av de mer komplekse funksjonene til oppgaven og bruke FindAllStringSubmatch () metode. Det tar en streng og et tall n. Hvis n er -1, vil det returnere alle matchende indekser. Hvis n er et ikke-negativt heltall, vil det returnere de n venstre sidene. Resultatet er et stykke snøskiver. 

Resultatet av hver submatch er hele matchen etterfulgt av den fanget gruppen. For eksempel, vurder en liste over navn hvor noen av dem har titler som "Mr.", "Mrs." eller "Dr.". Her er en regex som fanger tittelen som en submatch og deretter resten av navnet etter et mellomrom: \ b (Mr \. | Mrs \. Dr \.). *.

func main () re: = regexp.MustCompile ('\ b (Mr \. | Mrs \. | Dr \.). *') fmt.Println (re.FindAllStringSubmatch ("Dr. Dolittle", -1)) Fmt.Println (re.FindAllStringSubmatch ('Mrs. Doubtfire Mr. Anderson', -1)) Utgang: [[Dr. Dolittle Dr.]] [[Mrs. Tvilstilfelle Mrs.] [Mr. Anderson Mr.]] 

Som du kan se i utgangen, blir hele fangen tatt først og deretter bare tittelen. For hver linje tilbakestilles søket.

Erstatte

Å finne kamper er bra, men ofte må du kanskje erstatte kampen med noe annet. Regexp-objektet har flere ReplaceXXX () metoder som vanlig for å håndtere strenger vs. byte arrays og bokstavelig erstatning vs. utvidelser. I den store boken 1984 av George Orwell er partiets slagord skrevet på den hvite pyramiden av sannhetens tjeneste: 

  • Krig er fred 
  • Frihet er slaveri 
  • Uvitenhet er styrke 

Jeg fant et lite essay om prisen på frihet som bruker noen av disse vilkårene. La oss rette et stykke av det i henhold til partiet doublespeak ved hjelp av Go regexes. Vær oppmerksom på at noen av målordene for utskifting bruker annen kapitalisering. Løsningen er å legge til det uforsiktige flagget (Jeg?) i begynnelsen av regexen. 

Siden oversettelsen er forskjellig avhengig av saken, trenger vi en mer sofistikert tilnærming enn bokstavelig erstatning. Heldigvis (eller ved design) har Regexp-objektet en erstatningsmetode som aksepterer en funksjon den bruker til å utføre den faktiske utskiftningen. La oss definere vår erstatningsfunksjon som returnerer oversettelsen med riktig tilfelle.

(strengen): "fred", "krig": "fred", "krig": "fred", "frihet": "slaveri", " FREEDOM ":" SLAVERY "," Freedom ":" Slaveri "," uvitenhet ":" styrke "," IGNORANCE ":" STYRKE "," Uvitenhet ":" Styrke ", r, ok: = d ok return r else return s 

Nå kan vi utføre den faktiske erstatningen:

func main () text: = 'PRISEN OM FRIHET: Amerikanere i krig Amerikanere har gått i krig for å vinne sin uavhengighet, utvide sine nasjonale grenser, definere sine friheter og forsvare sine interesser over hele verden.' ekspr: = '(? i) (krig | frihet | uvitenhet)' r: = regexp.MustCompile (expr) resultat: = r.ReplaceAllStringFunc (tekst, erstatter) fmt.Println (resultat) Output: SLAVERY PRIS: Amerikanere i fred amerikanerne har gått til fred for å vinne sin uavhengighet, utvide sine nasjonale grenser, definere sine slaverier og forsvare sine interesser over hele verden. 

Utgangen er noe usammenhengende, som er kjennetegnet for god propaganda.

gruppering

Vi så hvordan vi kan bruke gruppering med submatcher tidligere. Men det er noen ganger vanskelig å håndtere flere submatcher. Oppkalte grupper kan hjelpe mye her. Slik navngir du submatch-gruppene og fyller i en ordbok for enkel tilgang ved navn:

func main () e: = '(? P\ w +) (? P.+ )? (? P\ w +) 'r: = regexp.MustCompile (e) navn: = r.SubexpNames () fullNames: = [] streng ' John F. Kennedy ',' Michael Jordan ' for _, fullName: = rekkevidde fullnavn resultat : = r.FindAllStringSubmatch (fullName, -1) m: = map [string] streng  for i, n: = rekkevidde resultat [0] m [navn [i]] = n fmt.Println : ", m [" første "]) fmt.Println (" middle_name: ", m [" middle "]) fmt.Println (" etternavn: ", m [" siste "]) fmt.Println Utgang: Fornavn: John Mellomnavn: F. Etternavn: Kennedy Fornavn: Michael Mellomnavn: Etternavn: Jordan

Å håndtere nye linjer

Hvis du husker, sa jeg at prikken karakteren samsvarer med et hvilket som helst tegn. Vel, jeg løy. Den stemmer ikke overens med den nye linjen (\ n) karakter som standard. Det betyr at kampene dine ikke vil krysse linjer med mindre du spesifiserer det eksplisitt med spesialflagget (S?) som du kan legge til i begynnelsen av din regex. Her er et eksempel med og uten flagg.

func main () text: = "1111 \ n2222" expr: = [] streng ". *", "(? s). *" for _, e: = rekkevidde expr r: = regexp.MustCompile e) resultat: = r.FindString (tekst) resultat = strenger.Replace (resultat, "\ n", '\ n', -1) fmt.Println (e, ":", resultat) fmt.Println  Utgang:. *: 1111 (? S). *: 1111 \ n2222 

En annen vurdering er om å behandle ^ og $ spesialtegn som begynnelse og slutt på hele teksten (standard) eller som begynnelse og slutt på hver linje med (? M) flagg.  

Konklusjon

Regelmessige uttrykk er et kraftig verktøy når du arbeider med halvstrukturert tekst. Du kan bruke dem til å validere tekstinngang, rydde opp, forvandle den, normalisere den og generelt håndtere mye mangfold ved hjelp av kortfattet syntaks. 

Go gir et bibliotek med et brukervennlig grensesnitt som består av et Regexp-objekt med mange metoder. Gi det en prøve, men pass opp for fallgruvene.