Kako koristiti regularne izraze za zamjenu tokena u žicama u Javi

1. Pregled

Kad trebamo pronaći ili zamijeniti vrijednosti u nizu na Javi, obično koristimo regularne izraze. Oni nam omogućuju utvrđivanje podudara li se neki ili svi nizovi s uzorkom. Mogli bismo lako primijeniti istu zamjenu na više tokena u nizu znakom zamjeni sve metoda u oba Podudaranje i Niz.

U ovom uputstvu istražit ćemo kako primijeniti drugačiju zamjenu za svaki token pronađen u nizu. To će nam olakšati zadovoljavanje slučajeva korištenja poput bijega određenih znakova ili zamjene rezerviranih vrijednosti.

Također ćemo pogledati nekoliko trikova za podešavanje naših regularnih izraza kako bismo ispravno identificirali tokene.

2. Pojedinačno obrađivanje utakmica

Prije nego što možemo izgraditi svoj algoritam zamjene po žetonu, moramo razumjeti Java API oko regularnih izraza. Riješimo škakljiv problem podudaranja pomoću grupa za hvatanje i nehvatanje.

2.1. Primjer naslovnog slučaja

Zamislimo da želimo izgraditi algoritam za obradu svih naslovnih riječi u nizu. Te riječi započinju jednim velikim slovom, a zatim završavaju ili se nastavljaju samo malim slovima.

Naš input mogao bi biti:

"Prve 3 kapitalne riječi! Zatim 10 TLA-a, pronašao sam"

Iz definicije naslovne riječi, ovo sadrži podudaranja:

  • Prvi
  • Kapital
  • Riječi
  • Ja
  • Pronađeno

A regularni izraz za prepoznavanje ovog uzorka bio bi:

"(? <= ^ | [^ A-Za-z]) ([A-Z] [a-z] *) (? = [^ A-Za-z] | $)"

Da bismo to razumjeli, podijelimo ga na njegove sastavne dijelove. Krenut ćemo od sredine:

[A-Z]

prepoznat će jedno veliko slovo.

Dopuštamo riječi s jednim znakom ili riječi iza kojih slijedi malo slovo, pa:

[a-z] *

prepoznaje nula ili više malih slova.

U nekim bi slučajevima gornje dvije klase znakova bile dovoljne za prepoznavanje naših tokena. Nažalost, u našem primjeru teksta postoji riječ koja počinje s više velikih slova. Stoga, moramo izraziti da se jedno veliko početno slovo koje nalazimo mora prvo pojaviti nakon ne-slova.

Slično tome, dok dopuštamo jednu veliku početnu riječ, moramo izraziti da pojedinačno veliko slovo koje pronađemo ne smije biti prvo od riječi s velikim velikim slovom.

Izraz [^ A-Za-z] znači "bez slova". Jednu od ovih stavili smo na početak izraza u grupu koja ne obuhvaća:

(? <= ^ | [^ A-Za-z])

Skupina koja nije uhvatila, počevši od (?<=, radi a gledajte kako biste osigurali da se podudaranje pojavi na ispravnoj granici. Njegov kolega na kraju radi isti posao za likove koji slijede.

Međutim, ako riječi dodiruju sam početak ili kraj niza, tada to moramo uzeti u obzir, gdje smo dodali ^ | prvoj grupi da to znači "početak niza ili bilo koji ne-slovni znak", a dodali smo | $ na kraju posljednje grupe koja nije uhvatila da bi kraj niza bio granica .

Likovi pronađeni u grupama koje ne snimaju ne pojavljuju se u podudaranju kad koristimo pronaći.

Trebali bismo napomenuti da čak i jednostavan slučaj korištenja poput ovog može imati mnogo rubnih slučajeva, pa važno je testirati naše regularne izraze. U tu svrhu možemo pisati jedinstvene testove, koristiti ugrađene alate našeg IDE-a ili mrežni alat poput Regexr-a.

2.2. Testiranje našeg primjera

S našim primjerom teksta u konstanti tzv PRIMJER_INPUT i naš regularni izraz u a Uzorak pozvao TITLE_CASE_PATTERN, iskoristimo pronaći na Podudaranje razreda za izdvajanje svih naših podudaranja u jediničnom testu:

Podudaranje podudaranja = TITLE_CASE_PATTERN.matcher (EXAMPLE_INPUT); Lista odgovara = novi ArrayList (); while (matcher.find ()) {match.add (matcher.group (1)); } assertThat (podudara se) .containsExactly ("First", "Capital", "Words", "I", "Found");

Ovdje koristimo podudaranje funkcionirati na Uzorak proizvesti a Podudaranje. Tada koristimo pronaći metoda u petlji dok se ne prestane vraćati pravi ponoviti sve utakmice.

Svaki put pronaći vraća se pravi, Podudaranje stanje objekta postavljeno je tako da predstavlja trenutno podudaranje. Možemo pregledati cijelu utakmicu s grupa (0)ili pregledajte određene skupine za hvatanje s njihovim indeksom na temelju 1. U ovom slučaju postoji skupina za hvatanje oko dijela koji želimo, pa ga koristimo grupa (1) da bismo dodali utakmicu na naš popis.

2.3. Pregledavajući Podudaranje još malo

Do sada smo uspjeli pronaći riječi koje želimo obraditi.

Međutim, ako je svaka od ovih riječi bila žeton koji smo željeli zamijeniti, trebali bismo imati više informacija o podudaranju kako bismo izgradili rezultirajući niz. Pogledajmo neka druga svojstva Podudaranje to bi nam moglo pomoći:

while (matcher.find ()) {System.out.println ("Match:" + matcher.group (0)); System.out.println ("Start:" + matcher.start ()); System.out.println ("Kraj:" + podudaranje.end ()); }

Ovaj kod će nam pokazati gdje je svaki meč. Također nam pokazuje grupa (0) match, koji je sve uhvaćen:

Utakmica: Prvi početak: 0 Kraj: 5 Utakmica: Kapitalni početak: 8 Kraj: 15 Utakmica: Riječi Početak: 16 Kraj: 21 Utakmica: I Početak: 37 Kraj: 38 ... više

Ovdje možemo vidjeti da svako podudaranje sadrži samo riječi koje očekujemo. The početak svojstvo prikazuje indeks podudaranja na temelju nule unutar niza. The kraj prikazuje indeks lika odmah nakon. To znači da bismo mogli koristiti podniz (početak, kraj-početak) za izdvajanje svakog podudaranja iz izvornog niza. To je u biti kako skupina metoda to čini za nas.

Sad kad možemo koristiti pronaći za ponavljanje poklapanja, obradimo svoje tokene.

3. Zamjena pogodaka jedan po jedan

Nastavimo naš primjer koristeći naš algoritam za zamjenu svake naslovne riječi u izvornom nizu malim ekvivalentom. To znači da će se naš testni niz pretvoriti u:

"prve 3 velike riječi! pa 10 TLA, pronašao sam"

The Uzorak i Podudaranje klasa to ne može učiniti za nas, pa moramo konstruirati algoritam.

3.1. Zamjenski algoritam

Evo pseudo-koda za algoritam:

  • Počnite s praznim izlaznim nizom
  • Za svaku utakmicu:
    • U izlaz dodajte sve što je bilo prije utakmice i nakon bilo koje prethodne utakmice
    • Obradite ovo podudaranje i dodajte to na izlaz
    • Nastavite dok se sve podudarnosti ne obrade
    • U izlaz dodajte bilo što preostalo nakon zadnjeg podudaranja

Moramo napomenuti da je cilj ovog algoritma da pronađite sva nepodudarna područja i dodajte ih u izlaz, kao i dodavanje obrađenih podudaranja.

3.2. Zamjenjivač žetona u Javi

Svaku riječ želimo pretvoriti u mala slova, tako da možemo napisati jednostavnu metodu pretvorbe:

privatna statička pretvorba niza (niz znakova) {return token.toLowerCase (); }

Sada možemo napisati algoritam za ponavljanje utakmica. Ovo može koristiti a StringBuilder za izlaz:

int lastIndex = 0; StringBuilder output = novi StringBuilder (); Podudaranje podudaranja = TITLE_CASE_PATTERN.matcher (izvornik); while (matcher.find ()) {output.append (original, lastIndex, matcher.start ()) .append (convert (matcher.group (1))); lastIndex = podudaranje.end (); } if (lastIndex <original.length ()) {output.append (original, lastIndex, original.length ()); } vrati izlaz.toString ();

To bismo trebali primijetiti StringBuilder pruža praktičnu verziju dodati koji mogu izvući podnizove. Ovo dobro funkcionira s kraj vlasništvo Podudaranje kako bismo pokupili sve nepodudarne znakove od zadnjeg podudaranja.

4. Generaliziranje algoritma

Sad kad smo riješili problem zamjene nekih specifičnih tokena, zašto ne bismo pretvorili kôd u oblik u kojem se može koristiti za opći slučaj? Jedina stvar koja se razlikuje od jedne implementacije do sljedeće je regularni izraz koji se koristi i logika pretvaranja svakog podudaranja u njegovu zamjenu.

4.1. Upotrijebite unos funkcije i uzorka

Možemo koristiti Javu Funkcija objekt koji omogućava pozivatelju da pruži logiku za obradu svakog podudaranja. I možemo uzeti ulaz koji se zove tokenPattern kako biste pronašli sve tokene:

// isto kao i prije while (matcher.find ()) {output.append (original, lastIndex, matcher.start ()) .append (converter.apply (matcher)); // isto kao prije

Ovdje regularni izraz više nije teško kodiran. Umjesto toga, konverter funkciju osigurava pozivalac i primjenjuje se na svako podudaranje unutar pronaći petlja.

4.2. Testiranje opće verzije

Pogledajmo radi li opća metoda kao i izvorna:

assertThat (replaceTokens ("First 3 Capital Words! then 10 TLAs, I Found", TITLE_CASE_PATTERN, match -> match.group (1) .toLowerCase ())) .isEqualTo ("prve 3 glavne riječi! pa 10 TLA, pronašao sam ");

Ovdje vidimo da je pozivanje koda jednostavno. Funkciju pretvorbe lako je izraziti lambda. I test prolazi.

Sada imamo zamjenu tokena, pa pokušajmo s nekim drugim slučajevima korištenja.

5. Neki slučajevi upotrebe

5.1. Bijeg od posebnih znakova

Zamislimo da smo željeli upotrijebiti regularni izraz bijeg \ kako biste ručno citirali svaki znak regularnog izraza, umjesto da koristite citat metoda. Možda navodimo niz kao dio stvaranja regularnog izraza za prosljeđivanje u drugu knjižnicu ili uslugu, tako da blok-citiranje izraza neće biti dovoljno.

Ako uspijemo izraziti obrazac koji znači "znak regularnog izraza", lako ćemo upotrijebiti naš algoritam da bismo ih izbjegli svima:

Uzorak regexCharacters = obrazac.compile ("[]"); assertThat (replaceTokens ("Znak regularnog izraza poput [", regexCharacters, match -> "\" + match.group ())) .isEqualTo ("Znak regularnog izraza poput \ [");

Za svako podudaranje stavljamo predznak \ lik. Kao \ je poseban znak u Java stringovima, pobjegao je s drugim \.

Doista, ovaj je primjer pokriven dodatnim podacima \ znakova kao klasa znakova u uzorku za regexCharacters mora citirati mnoge posebne znakove. To pokazuje analizator regularnih izraza da ih koristimo u značenju njihovih literala, a ne kao sintaksu regularnog izraza.

5.2. Zamjena rezerviranih mjesta

Uobičajeni način izražavanja rezerviranog mjesta je upotreba sintakse like $ {name}. Razmotrimo slučaj upotrebe gdje predložak "Pozdrav, $ {name} u $ {company}" treba popuniti s mape zvane rezervirano mjestoVrijednosti:

Map placeholderValues ​​= novi HashMap (); placeholderValues.put ("ime", "Račun"); placeholderValues.put ("tvrtka", "Baeldung");

Sve što nam treba je dobar regularni izraz pronaći ${…} žetoni:

"\ $ \ {(? [[A-Za-z0-9 -_] +)}"

je jedna od mogućnosti. Mora citirati $ i početnu kovrčavu zagradu jer bi se inače tretirali kao sintaksa regularnog izraza.

U središtu ovog uzorka je skupina za hvatanje imena rezerviranog mjesta. Koristili smo klasu znakova koja dopušta alfanumeričke, crtice i donje crte, što bi trebalo odgovarati većini slučajeva korištenja.

Međutim, kako bismo kôd učinili čitljivijim, nazvali smo ovu skupinu za hvatanjerezerviranog mjesta. Pogledajmo kako koristiti tu imenovanu skupinu za hvatanje:

assertThat (replaceTokens ("Hi $ {name} at $ {company}", "\ $ \ {(? [A-Za-z0-9 -_] +)}", match -> placeholderValues.get (match .group ("placeholder")))) .isEqualTo ("Hi Bill at Baeldung");

Ovdje možemo vidjeti da dobivanje vrijednosti imenovane skupine iz Podudaranje samo uključuje upotrebu skupina s imenom kao ulaznim, a ne s brojem.

6. Zaključak

U ovom smo članku pogledali kako pomoću moćnih regularnih izraza pronaći tokene u našim žicama. Naučili smo kako pronaći metoda radi s Podudaranje da nam pokaže šibice.

Tada smo stvorili i generalizirali algoritam koji nam omogućuje zamjenu po žetonu.

Na kraju smo pogledali nekoliko uobičajenih slučajeva korištenja za izbjegavanje znakova i popunjavanje predložaka.

Kao i uvijek, primjeri koda mogu se naći na GitHubu.