LINUXSOFT.cz Přeskoč levou lištu
Uživatel: Heslo:  
   CZUKPL

> Perl (20) - Regulární výrazy - magické závorky

Dnešní díl popisuje použití kulatých závorek pro změnu priority, seskupování a pamatování.

5.12.2005 06:00 | Jiří Václavík | Články autora | přečteno 29807×

V regulárních výrazech mají závorky hned několik důležitých a jinak nenahraditelných významů. Mezi jejich hlavní funkce patří seskupení, priorita a paměť. Rozeberme si postupně tyto tři vlastnosti.

Priorita

Stejně jako pro operátory existuje tabulka priorit i pro metaznaky. Určuje, ke kterým znakům se váže určitý metaznak. Čím výše v tabulce, tím je metaznak prioritnější.

MetaznakyVýznam
( ), (?: )závorky
*, ?, +, {}, *?, ??, +?, {}?kvantifikátory
aa, \x, ^, $ apod.posloupnost znaků, kotvy
|alternativy

Priorita se dá měnit kulatými závorkami.

Seskupování

Kvantifikátor jsme zatím používali pouze pro znak nebo třídu znaků, která ho předcházela. Pomocí závorek můžeme docílit toho, aby se vztahoval na nějakou delší část regulárního výrazu. Srovnejme si tyto 2 vzory:

  /(xy){3}/;
  /xy{3}/;

V 1. případě jsou znaky xy seskupeny. To znamená, že se kvantifikátor vztahuje na celý řetězec xy v závorkách. Lze to vysvětlit také jako změnu priority - kvantifikátor má menší prioritu než závorky a proto je aplikován na celý uzávorkovaný výraz. Takovému regulárnímu výrazu vyhoví řetězec, který obsahuje xyxyxy. Další řádek je stejný až na to, že chybí závorky a znaky xy seskupeny nejsou. Kvantifikátor je prioritnější než posloupnost znaků a vztahuje se pouze na znak y. Vzoru tedy vyhoví xyyy.

Pamatování obsahu závorek

Vše, co uzávorkujete kulatými závorkami si Perl, pokud mu to nezakážete, pamatuje. To je vlastnost, se kterou lze dělat opravdu nevídané věci.

Výraz $text =~ /(...)/ vrací úsek textu, který vyhověl regulárnímu výrazu. Tímto způsobem můžeme do pole velice pohodlně získat třeba seznam slov nebo čísel. Právě seřazený výtah čísel ze zadaného textu tiskne následující ukázka.

  $, = ", ";
  $text = "333 stříbrných stříkaček stříkalo přes 33 stříbných střech";
  @cisla = ($text =~ /(\d+)/g);
  print sort @cisla; #tiskne 33, 333

Obsah závorky se ukládá okamžitě a takto uložený řetězec se dá použít ještě ve vzoru. To, co je v 1. závorce je přístupné v \1, další závorka \2 a tak dále až do \99 (dále ne, protože by se takový zápis překrýval s osmičkovým zápisem znaku. \100 je zavináč, \101 je znak A apod.). Tento mechanizmus nám otevírá spoustu dalších možností.

Pokusme se zapsat vzor, který vyhoví řetězci o 5 stejných číslicích. Jinými slovy vzor, kterému, pokud začíná na 1, vyhoví pouze 11111, pokud začíná na 2, vyhoví pouze 22222, atd. Pokud vzor nezačíná číslicí, nevyhoví nikdy.

Jako 1. znak hledáme číslici. Zápis množiny číslic musí být v závorkách, abychom její hodnou neztratili. Dále použijeme zapamatovanou hodnotu jako množinu znaků a otestujeme ji 4x. Nakonec označíme začátek a konec řetězce.

  print "MATCHED" if "55555" =~ /^(\d)\1{4}$/; #vyhovuje
  print "MATCHED" if "555555" =~ /^(\d)\1{4}$/;#nevyhovuje
  print "MATCHED" if "11112" =~ /^(\d)\1{4}$/; #nevyhovuje
  print "MATCHED" if "xxxxx" =~ /^(\d)\1{4}$/; #nevyhovuje

Tato vlastnost se hodí zejména při nahrazování, které bude rozebráno v příštím díle.

Podobně lze pomocí závorek získávat hodnoty i mimo samotný vzor. Obsahy jsou přístupné ve speciálních proměnných $1, $2$99. Nyní z řetězce opět vyseparujeme všechna čísla. Použijeme cyklus, každou iteraci bude 1 číslo zapamatováno a v těle cyklu vytištěno.

  while ("333 stříbrných stříkaček stříkalo přes 33 stříbných střech" =~ /(\d+)/g){
      print "Číslo: $1\n";
  }

Pořadí (u zápisu \n i $n) se uděluje na základě pozice otevírací závorky. Tento systém řeší případné víceúrovňové zanoření. Nějaké podřetězce tak mohou být uloženy i vícekrát. Nic tedy nebrání například zápisu ((\d)) - ta stejná číslice bude uložena do více proměnných. Zanořování si ilustrujeme na následujícím úseku kódu. Ten vypíše první a poslední číslici nejméně trojmístného zadaného čísla a také toto celé číslo.

  <STDIN> =~ /^((\d)\d+(\d))$/;
  print "1. číslice: $2\n";
  print "poslední číslice: $3\n";
  print "celé číslo: $1\n";

Poznámka - Pokud řetězec nebude vyhovovat vzoru, proměnné zůstanou prázdné - obsah závorek se ukládá, jen pokud byl test vyhodnocen pravdivě. Problémy pak mohou nastat, pokud v proměnných $1, $2 atd. zbyly nějaké hodnoty z předchozího testu. Jde o velice nenápadnou chybu a může trvat dlouho, než je odhalena.

Poznámka - Jaký je tedy rozdíl mezi \n i $n? \n je do paměti ukládáno okamžitě s uzavírací závorkou, takže je možné tímto způsobem zapamatovanou hodnotu použít ještě ve vzoru. Oproti tomu proměnné $n se načtou vždy až po úspěšném srovnání řetězce se vzorem.

Ukládání do \n, resp. $n lze zabránit speciální syntaxí pro zápis závorek. Místo levé (otevírací) závorky použijte posloupnost znaků (?:. V takovém případě se nic ukládat nebude a proměnná $n se bude chovat jako ostatní nedefinované proměnné, což dokazuje kód:

  "123456789" =~ /^(?:\d*)$/;
  print "Hodnota uložena\n" if defined $1;

Vždy, když není pamatování potřeba, je lepší použít právě verzi bez zapamatování. Zvyšuje se tím rychlost vyhodnocení regulárního výrazu.

Hezky lze také využít pamatování v souvislosti s kontextem. Tato vlastnost umožňuje například snadno vyseparovat z rodného čísla den, měsíc a rok narození. Stačí jen uzavřít příslušné části regulárního výrazu do závorek a regulární výraz přiřadit do nějakého seznamu.

  $rc = "3012013522";
  ($rok, $mesic, $den) = $rc =~ /^(\d\d)(\d\d)(\d\d)\d{4}$/;
  print "Narozen $den.$mesic.19$rok.";

Poznámka - Příklad je samozřejmě zjednodušený - nebere v úvahu ženská rodná čísla a narozené jindy než mezi roky 1900 a 1999.

Nejdříve se vyhodnotil pravý operand přiřazení. Tak jsme získali seznam, který jsme přiřadili do už pojmenovaného seznamu. Problém opět nastává, pokud srovnávaný řetězec nevyhoví regulárnímu výrazu. Lze to však řešit například umístěním podmínky za příkaz.

  print "Narozen $den.$mesic.19$rok." if ($rok, $mesic, $den) = $rc =~ /^(\d\d)(\d\d)(\d\d)\d{4}$/;

Speciální proměnné

V proměnné $& je část testovaného řetězce, která se shoduje se vzorem. Tímto způsobem si můžeme mimo jiné hezky ilustrovat hladovost kvantifikátorů.

  "číslo 101 je prvočíslo" =~ /\d+.{2,10}/;
  print $&;
  
  "číslo 101 je prvočíslo" =~ /\d+.{2,10}?/;
  print $&;

1. zápis je hladový. Spolkne 10 znaků za číslem 101: "101 je prvočí". Pokud za kvantifikátor přidáme otazník, stává se sytým a pohltí pouze nejmenší možný počet znaků, které vyhoví: "101 j".

Další proměnné $` a $' tisknou tu část testovaného řetězce, kterou nevytiskla proměnná $&. $` tiskne část před ("číslo ") a $' část za ("e prvočíslo").

  "číslo 101 je prvočíslo" =~ /\d+.{2,10}?/;
  print $`;
  print $';

V proměnné $+ je uložen poslední závorkami zapamatovaný řetězec.

Poznámka - Je dobré mít na paměti, že proměnné $&, $`, $' a $+ značně zpomalují program.

Speciální proměnná @- si pamatuje v prvku s indexem 0 první pozici v testovaném řetězci, která vyhovuje vzoru. V dalších prvcích pak pozice začátků zapamatovaných podřetězců. Proměnná @+ dělá to samé, ale pamatuje si místo začátků konce.

Funkce split a regulární výrazy

Oddělovač, který je parametrem této funkce, lze uvést i jako regulární výraz. To je v mnoha případech opravdu užitečné. Mějme nějaký text, ze kterého načteme do pole seznam slov.

  $, = "\n";
  @pole = split (/\W+/, "Text, ve kterém je interpunkce!");
  print @pole;

Vše mezi slovy (tedy oddělovače - mezery a interpunkce - lépe řečeno vše, co vyhovuje vzoru) je navždy ztraceno. Být tomu tak vždy nemusí. Proč, to si budeme demonstrovat na jiném příkladu. Budeme mít nějaký řetězec, ve kterém jsou promíchány čísla a právě čísla zvolíme jako oddělovač.

  $, = " "; $\ = "\n";
  print split (/\d+/, "abc01def2ghijk34567l89mnop");
  print split (/(\d+)/, "abc01def2ghijk34567l89mnop");
  print split (/(?:\d+)/, "abc01def2ghijk34567l89mnop");

V 1. případě se vytiskne seznam získaných podřetězců tak, jak jsme to dosud znali. Zajímavé to začíná být až na dalším řádku. Vzor je uveden v závorkách, které zde mají ten efekt, že se do výsledného seznamu tisknou i oddělovače! Skutečným oddělovačem je tedy (v tomto konkrétním případě) hranice mezi podřetězcem vyhovujícím vzoru a jiným znakem. Nic z původního řetězce tak není ztraceno. Posledním případem je speciální druh uzávorkování, které této vlastnosti zamezuje - oddělovače se chovají jako v 1. případě.

  $ perl split.pl
  abc def ghijk l mnop
  abc 01 def 2 ghijk 34567 l 89 mnop
  abc def ghijk l mnop
  $

Je též možné ozávorkovat pouze část vzoru. V tom případě bude do výsledného seznamu přiřazena příslušná část oddělovače.

  print split (/(\d+)45/, "***12345***12345***123***12345678***");

Podtržené podřetězce ukazují oddělovače. Ve výsledném seznamu ale bude jejich část (znaky 45) odmazána, protože jsou mimo závorky (částí oddělovače ale samozřejmě zůstávají).

Dále můžete také vyzkoušet závorky různě vnořovat, zdvojovat apod. V takových případech budou ve výsledném seznamu všechny uzávorkované řetězce. A to i přesto, že některé části pak budou ve výsledném seznamu vícekrát.

Verze pro tisk

pridej.cz

 

DISKUZE

Nejsou žádné diskuzní příspěvky u dané položky.



Příspívat do diskuze mohou pouze registrovaní uživatelé.
> Vyhledávání software
> Vyhledávání článků

17.9.2014 14:44 /MaReK Olšavský
SAP otevřel svou jQuery UI knihovnu OpenUI5 (kódové jméno „Phoenix“) pod licencí Apache 2.0. Nadstavbě jQuery UI přibyl silný konkurent.
Přidat komentář

17.9.2014 7:29 /MaReK Olšavský
Vyšel Minix 3.3.0, pro uživatele GNU/Linxu důležitý tím, že právě na Minixu začal vývoj jádra Linux. Mezi zajímavými novinkami je podpora architektury ARM Cortex-A8 (třeba BeagleBoard/BeagleBone), nebo vylepšená kompatibilita s aplikacemi z NetBSD, ale zatím není dostupný X11 server. Najde své uživatele?
Přidat komentář

17.9.2014 7:29 /MaReK Olšavský
Vývojáři Mozilly pracující na novém programovacím jazyku Rust mají plán pro verzi 1.0 i pro kroky následující. Asi se blíží vydání první „finální“ verze.
Přidat komentář

17.9.2014 7:29 /MaReK Olšavský
oddělení „The Qt Company“ do samostatné firmy, plně vlastněné mateřskou Digií, píše LinuxGizmos. Vývoj Qt změnil několik vlastníků, za poslední roky, a uvedl i nové produkty za příznivější cenu.
Přidat komentář

16.9.2014 5:08 /MaReK Olšavský
VESA uvolnila technický standard DisplayPort 1.3. DP 1.3 má šířku pásma 32,1 Gb/s, což je dostatečný tok na 5k display při frekvenci 60 Hz. Technicky by se mohlo HDMI, populární především u spotřební elektroniky, stát historickou kapitolou (DP není zatížen licenčními poplatky).
Přidat komentář

16.9.2014 5:08 /MaReK Olšavský
SUSE (a další značky včetně Novellu) mají zase jiného majitele, Attachmate byla koupena společností Micro Focus za US$ 2,3 mld. Micro Focus se dosud věnoval enteprise sféře a nástroji Visual COBOL.
Přidat komentář

16.9.2014 5:08 /MaReK Olšavský
Finální verze Enlightenment 19 byla dokončena a hlavním tahákem je plná podpora nastupujícího Waylandu. Z dalších změn může zaujmout nový modul dlaždic, modul pro integraci správce balíků, nebo návrat live pageru (známého z E16). Po dlouhých letech stagnace se Enlightenment začal zajímavě vyvíjet.
Přidat komentář

15.9.2014 22:25 /Petr Ježek
Enlightenment E19 přistál v repozitáři Testing pro Archlinux. Testování lze jen doporučit.
Přidat komentář

   Více ...   Přidat zprávičku

> Poslední diskuze

16.9.2014 3:49 / MaReK Olšavský
Re: Těžko

15.9.2014 14:35 / Petr Ježek
Těžko

14.9.2014 11:04 / Petr Ježek
DE, ne WM

12.9.2014 12:09 / Thomas Jones
Great

9.9.2014 15:58 / František Kučera
Re: Díky za článek

Více ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2014) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze | zákony online