Wyjściowka nr. 5 :?
Uniwersytet Gdański - Instytut Matematyki - Zakład Informatyki - Strona domowaPytania i zadania
Odpowiedz na zebrane poniżej zadania.
Odpowiedzi wyślij mailem pod tyt. [kolo 5] grupaN Imię Nazwisko (N-numer grupy)
Przestrzegaj limitu czasu pisania - max 30 minut.
Max punktów do zdobycia: 13
Zadania dla grupy 1
Start: 2007-03-28 15:00, czas: 30 minut czasu delty.- (2p) Opracuj wyrażenie regularne które z dowolnego dokumentu HTML zwróci jedynie listę użytych w tym dokumencie otwierających tagów HTML (bez nawiasów kątowych). Przypadki np. <br /> także można uznać za otwierające. Przykład: z tagów <html><head><meta ... /><body...><p ...><br /></p></body...> ma powstać lista: html head meta body p br.
Rozwiązaniem tego zadania może być wyrażenie regularne, które wygląda tak:
@lista_tagów = $_ =~ m{<(\w+).*?>}gsm;
Oczywiście, cała treść dokumentu HTML powinna być zawarta wewnątrz zmiennej domyślnej, żeby takie wyrażenie mogło działać. Wczytanie dokumentu do zmiennej skalarnej można wykonać na przykład przez wywołanie zewnętrznego polecenia (np. $_ = `cat plik.html`;) albo poprzez lokalne oddefiniowanie zmiennej $/ określającej tekst pomiędzy wierszami i wczytanie tekstu np. tak:local $/ = undef; $_ = <$f> if open $f,'plik.html';
W zadaniu chodziło tylko o podanie wyrażenia regularnego. - (1p) Zaproponuj mechanizm który akceptuje każdy tekst ze słowem 'informatyka', w którym nie występuje jednocześnie wyraz 'matematyka'.
Całkiem sporo osób zaproponowało tutaj najlepsze rozwiązanie - użycie dwóch wyrażeń zamiast jednego. Zatem prawidłowe rozwiązanie może wyglądać np. tak:
if( /informatyka/ and !/matematyka\b/ ){...}
Zakładamy oczywiście, że tekst badany jest umieszczony w zmiennej domyślnej. Niepotrzebne są modyfikatory /.../i, ponieważ w zadaniu podano treść wyrazów i nie wspomniano, że wielkość liter ma być ignorowana. Ich obecność w rozwiązaniu była przeze mnie ignorowana :) Inne wersje rozwiązania, z np. .* są zbędne w tym miejscu, ale również nie były traktowane jako błąd. Podobnie, używanie zanegowanych asercji wyprzedzających, z wyjątkiem zapisu (?!<.*), który jest błędny. Granica słowa zabezpiecza przed błędną reakcją na tekst 'informatyka gardzi matematykami' (dziękuję Ł.L za wskazanie błędu!). - (1p) Przy pomocy wyrażenia regularnego sprawdź, czy podany z STDIN dowolny tekst zawiera dodatnią liczbę rzeczywistą większą od pi z dokładnością do dwóch miejsc po przecinku. Wprowadzony tekst może, ale nie musi, zawierać liczbę rzeczywistą z wykładnikiem.
W zadaniu chodzi o wykrycie, czy dowolny wiersz wejściowy zawiera liczbę rzeczywistą większą od 3.14. Liczby rzeczywiste, które spełniają ten warunek mogą być zapisane na różne sposoby, np. 1.00e10, 200.1, itp. Oto wyrażenie regularne, które akceptuje wiersze tylko z liczbami w postaci xxx, xxx.yy, xxxEyy, xxx.yyEzz, .yyEzz, itp.
if( m{([-+]?(?:\d*\.|\d+\.?)\d*(?:[eE][-+]?\d+)?)} and $1>3.14 ){...}
Wyrażenie to jest bardzo proste, a cały pomysł opiera się na tym, żeby zwyczajnie dopasować się do liczby rzeczywistej (dowolnej), przechwycić ją nawiasami i potem testować wartość zmiennej $1. Metoda ta jest skuteczna i prosta. Gdybyśmy chcieli napisać pełne wyrażenie regularne, które rzeczywiście sprawdzi to, o co chodzi w zadaniu, należałoby napisać coś znacznie bardziej skomplikowanego. Cześć wyróżniona na czerwono dopasowuje się do opcjonalnego znaku przed liczbą. Część wyróżniona na niebiesko dopasowuje się do ciąg dowolnych cyfr z następującą za nimi kropką, lub do kropki z następującymi za nią cyframi. Ta część wyrażenia jest ujęta w grupujące nawiasy w celu stworzenia alternatywy. Dalsza część zawiera wzorzec dopasowujący się do ciągu opcjonalnych cyfr tworzących część ułamkową liczby, a następnie do opcjonalnego wykładnika potęgowego, który złożony może być z litery E lub e, opcjonalnego znaku + lub - oraz ciągu cyfr. - (1p) Do jakiej części tekstu "aaacccdddeee" dopasuje się wyrażenie w postaci /a*c*d*/?
Zadanie to zostało rozwiązane prawidłowo prawie przez wszystkich, a to zapewne dzięki jego prostocie. W rzeczy samej, nietrudno sprawdzić że tekstem uzyskanym z dopasowania jest po prostu część napisu, równa aaacccddd.
- (1p) Podaj przynajmniej jeden sposób na uzyskanie listy znaków z dowolnego napisu za pomocą wyrażeń regularnych perla (i być może dodatkowych elementów języka).
Listę znaków można uzyskać na kilka sposobów. Najprostszym i "najczystszym" jest napisanie:
( m{.}g )
Wartość tego wyrażenia w kontekście listy jest po prostu listą pojedynczych znaków. Dlatego można wynik przypisać do tablicy lub iterować po nim bezpośrednio jakiejś pętli, np.:@tablica_liter = $_ =~ m{.}g; foreach( m{.}g ){...}
- (1p) Jak działa modyfikator /m w operatorach dopasowania wzorca?
Modyfikator ten zmienia działanie operatora dopasowania m// (oraz s///, który jest operatorem podstawienia), gdy dopasowuje się on do tekstu zawierającego znaki nowego wiersza (gdy zmienna $/ jest równa "\n"). Powoduje to pracę operatorów w trybie wielowierszowym. Przykładowo, wyrażenie /(.+)$/g wykonane na tekście, który zawiera znaki nowego wiersza, np. "abc\n123\n456" da w efekcie dopasowanie do fragmentu 456. Gdy włączy się tryb wielowierszowy, znak $ zaczyna pasować nie tylko na końcu napisu, ale także w każdym miejscu, gdzie znajduje się znak nowego wiersza, a co za tym idzie, z wyrażenia regularnego zostaną zwrócone trzy wiersze, zamiast jednego: abc, 123 oraz 456.
- (2p) Jakim wyrażeniem regularnym można pobrać z pliku INI wszystkie opcje z wartościami w postaci strumienia opcja1 wartosc1 opcja2 wartosc2 opcja3 wartosc3..., niezależnie od obecności sekcji? Zaproponuj proste wyrażenie regularne, które to wykona, zakładając że w pliku nie ma żadnych komentarzy, pustych linii ani zbędnych spacji.
Operację tę wykona bardzo proste wyrażenie regularne:
$_ = `cat plik.ini`; @strumien = m{(\w+)=(\w+)}g;
... które po prostu dopasuje się do napisów rozdzielonych znakiem =, z pominięciem wszystkiego innego. Separatorem są znaki nowego wiersza, więc koniec każdej "wartości" nie łączy się z początkiem następnej "opcji". Zapis w postaci "tablica = wyrażenie" działa tu jakbyśmy napisali "tablica = $_ =~ wyrazenie". - (1p) Zaproponuj mechanizm oparty o wyrażenia regularne, który z długiego ciągu cyfr zwróci grupy cyfr ustawione po 5 obok siebie. Przykładowo, ciąg 12345678901234567890 powinien zostać podzielony na odcinki 12345 67890 12345 67890.
Oczywiście podział taki robi się za pomocą wyrażenia:
@podzielone = "12345678901234567890" =~ m{\d{5}}g;
Taki podział będzie tworzył tylko i wyłącznie piątki cyfr. Aby dozwolić na dodanie do listy podzielonych cyfr także "ogonka", który powstanie w przypadku niepodzielnej przez 5 liczby cyfr, wyrażenie można nieco "zrelaksować" dodając możliwość dopasowania nie do wyłącznie pięciu, ale do od jednej do pięciu cyfr, za pomocą zachłannego kwantyfikatora {1,5} (wstawionego w miejsce {5}). - (1p) Do tablicy @tab wczytano fragment pliku. Użyj polecenia grep /wzorzec/,@tab; aby wydobyć z tablicy wszystkie wiersze, które zawierają 1) dowolny skrót pisany wielkimi literami, 2) najdłuższe wyrażenie ujęte w nawiasy, które mieści się w jednym wierszu.
To zadanie jest nieco mylące, ponieważ zapisano je tak, jakby za pomocą operatora grep należało wydobyć fragmenty napisów. Wystarczyło zaproponować np. takie rozwiązanie:
1)grep /[A-Z]+/,@tab;
2)grep /\(.*\)/,@tab;
- (2p) Zadanie dowolne.
W tym zadaniu nikt w szczególny sposób się nie wykazał. Nieliczne osoby zaproponowały jakieś programy zerżnięte z sieci, lub w pośpiechu napisane na kolanie. Żadne pytanie nie było na tyle na poziomie, żeby można było powiedzieć, że ktoś wie, co pisze.
Zadania dla grupy 2
Start: 2007-03-29 16:30, czas: 30 minut czasu delty.- (1p) Wyjaśnij, dlaczego i do jakiej części tekstu "zii zii ziiizzz" dopasuje się wyrażenie w postaci /[ijk]*\s.*$/.
W tym dość prostym zadaniu chodziło o wskazanie, która część wzorca dopasuje się do której części napisu, i dlaczego. Dopasowanie nastąpi do podkreślonego fragmentu tekstu: zii zii ziiizzz, dlatego, że obecna w wyrażeniu klasa znakowa dopasuje się do znaków i, następnie spacja z klasy znakowej \s dopasuje się do spacji w napisie i potem .* pochłonie zachłannie resztę tekstu.
- (2p) Do tablicy @tab wczytano fragment pliku. W jaki sposób za pomocą map, grep oraz wyrażeń regularnych wydobyć z tego pliku listę wyłącznie samogłosek ustawionych w kolejności ich pojawiania się?
Mając tablicę @tab z pewną liczbą wierszy należy pamiętać, że przetworzyć trzeba wszystkie wiersze. Wystarczy w tym celu użyć map i napisać:
map{ /[aeyiou]/g } @tab;
i w ten sposób wygenerowaną listę pojedynczych znaków zapisać np. do tablicy lub przeiterować po nich, wypisując np. na standardowe wyjście:local $,=" "; print map{ /[aeyuio]/g } @tab;
Nie ma tu żadnej magii - jest to bardzo proste wyrażenie. Polecenie map będzie pobierało kolejne elementy tablicy @tab, aby wykonać na nich operację podaną w nawiasach klamrowych. Wewnątrz nawiasów, wartość pobrana z tablicy tymczasowo jest umieszczana w zmiennej domyślnej, $_, dlatego wyrażenie regularne można zapisać w najprostszy możliwy sposób. Dodatkowo, wyrażenie /./g zawiera kropkę, która oznacza dowolny znak. Jest jedna taka kropka, zatem wyrażenie dopasuje się do jednego znaku (pierwszego z lewej w napisie). Byłoby to wszystko, gdyby nie to, że w wyrażeniu jest modyfikator - /g, który powoduje, że całe wyrażenie jest dopasowywane tyle razy, ile jest to możliwe. A więc kropka dopasuje się po kolei do wszystkich znaków. Najciekawszym efektem jest jednak zwrócona wartość z dopasowania. Otóż dopasowanie /./g w kontekście listy zwraca dopasowany tekst! A ponieważ dopasowała się po kolei cała grupa znaków, są one zwracane w postaci listy, i to wszystkie naraz. Dlatego każdy wiersz tablicy @tab zostanie zamieniony na listę znaków i wszystkie one zwrócone będą z map. - (1p) Opracuj wyrażenie regularne które z dowolnego dokumentu XML zwróci wyłącznie nazwy takich tagów, które mają parametry.
Wystarczy wyrażenie w postaci:
@nazwy = $_ =~ m{<(\w+)\s+\w.+?>}g;
Dopasowanie jest zakotwiczone pomiędzy znakami < oraz >, i przechwycone spomiędzy tych znaków będą tylko ciągi alfanumeryczne po których występuje dowolny biały znak (spacja, znak nowego wiersza, tabulator, itp.). Zakotwiczenie prawostronne na znaku > jest niekonieczne, ale wskazane, ponieważ bez niego można byłoby złapać również tekst umieszczony po znaku < nie tworzący tagu XML, np. z wyrażenia '5<6 to prawda'. (Poprawka regexpa by M.M.). Niestety, takie rozwiązanie nie sprawdzi się przy tagach zawierających przestrzenie nazw, np. <std:tagxml> i innych. Dlatego właściwym rozwiązaniem może być np.:@nazwy = $_ =~ m{<(\w+:?\w*)\s+\w.+?>}g;
(Poprawka by P.B. - dziękuję) - (1p) Wyjaśnij szczegółowo, do czego dokładnie dopasuje się wyrażenie w postaci m/(a+){2}/ w napisie "aaaaa" i dlaczego?
To zadanie jest sformułowane dość nieprezycyjnie, ponieważ wyrażenie regularne dopasowuje się do całego tekstu, natomiast przechwycone przez nawiasy zostanie tylko jedno 'a'. Działa tu zasada wyjaśniana na wykładzie, gdzie kwantyfikator umieszczony poza nawiasem przechwytującym, nie powiela tekstu przechwytywanego przez ten nawias. Zatem litera 'a' przechwycona przez nawias to ostatnie 'a' z napisu. Natomiast zmienna przechowująca cały tekst dopasowany to $&. Zaliczane były obie odpowiedzi, zarówno 'a', jak i 'aaaaa', ze względu na słabe sprecyzowanie pytania.
- (1p) Czym różni się działanie polecenia tr/a-z/A-Z/ od polecenia s/a-z/A-Z/ ?.
Operator transliteracji tr wykona inną operację od operatora podstawienia s. Pierwszy z nich zamieni litery małe na litery wielkie o ile znajdzie małe litery w tekście, a drugi podstawi dosłowny tekst "a-z" tekstem "A-Z", o ile zostanie w napisie znalezione wystąpienie tekstu "a-z". W dodatku podstawione zostanie tylko pierwsze wystąpienie takiego tekstu. To są generalnie najważniejsze różnice. Natomiast operator s z modyfikatorem /g NIE jest równoważny operatorowi transliteracji, jak z zapałem dowodziły niektóre osoby. Podstawienie wyrażenia a-z przez s w zapisie s/a-z/A-Z/g prowadzić będzie tylko do zamiany wszystkich wystąpień tekstu dosłownego "a-z" (słownie: a myślnik z), na tekst dosłowny "A-Z". I tyle. Co więcej, zapisanie wyrażenia z klasami znakowymi w postaci s/[a-z]/[A-Z]/g RÓWNIEŻ nie zadziała. Nie zadziała dlatego, że wyrażenie regularne w operatorze s można podać TYLKO w pierwszej części operatora, w drugiej podaje się tekst do zamiany. Zatem napis np. "abc", po wykonaniu na nim operacji s/[a-z]/[A-Z]/g będzie zamieniony na "[A-Z][A-Z][A-Z]". Naprawdę. Sprawdźcie sami.
- (1p) Jak działa modyfikator /cg w operatorach dopasowania wzorca?
Modyfikator ten blokuje resetowanie pozycji w dopasowywanym napisie ustalonej po udanym dopasowaniu poprzedniego wykonania tego samego wyrażenia, w przypadku gdy wyrażenie dopasowywane w tym napisie się nie dopasowało w bieżącym wykonaniu. Jest to dość rzadki przypadek zastosowania operatora m{} dopasowywanego do tego samego tekstu wielokrotnie w pętli. Blokowanie pozycji zapobiega powtórnej analizie tekstu i dopasowywaniu od początku napisu. Wyrażenia regularne po udanym dopasowaniu napisu "napis" ustawiają pewien ukryty znacznik zwracany przez funkcję pos("napis"). Jeżeli wyrażenie regularne nie dopasuje się do napisu, znacznik zwracany przez pos jest resetowany... Chyba że w wyrażeniu podano modyfikator /cg. Odpowiedzi na to pytanie były bardzo niedokładne, w części nieprawidłowe i generalnie widać, że bardzo mało osób rozumie o co tu naprawdę chodzi. Można się tego pytania spodziewać na egzaminie końcowym :P
- (3p) Utwórz wyrażenie regularne, które dopasowywać się będzie do polskich sylab dwu i trzyliterowych. Przyjmij, że sylaba składać się musi z jednej lub dwóch spółgłosek oraz co najmniej jednej samogłoski. Dokonaj za pomocą swojego wyrażenia podziału tekstu "Nad morzem jest kopakabana i zupa tam jest ok" na sylaby, które zwróć w postaci listy.
W tym przypadku należy połączyć ze sobą za pomocą alternatywy różne wyrażenia regularne, aby uzyskać plastyczność pozwalającą na wydobycie różnych sylab. Przypadków sylab w języku polskim jest sporo, dlatego tutaj ich liczba została ograniczona do form xy lub yx, xxy, xyx, yxx, gdzie x to samogłoska, a y to spółgłoska. Wyrażenie, które dopasowuje samogłoskę to prosta klasa znakowa [aeyiou] (wszystko co jest samogłoską). Natomiast spółgłoski to [^\W\daeyiou] (która zawiera "wszystko co jest literą, nie cyfrą i nie samogłoską". Zatem ustawiając te klasy znakowe obok siebie można łatwo zdefiniować wzory na sylaby. Chociaż nietrudno zbudować wprost wyrażenie regularne, które ma wszystko od razu w sobie, można także skorzystać z własności wyrażeń polegającej na tym, że zmienne perla mogą być w nich interpolowane jak w napisach, i zbudować wyrażenie z elementów składowych:
$A = qr/[aeyiou]/i; # samogłoski $S = qr/[^\W\daeyiou]/i; # spółgłoski $WYRAŻENIE = qr/ ( $S$A$S | $S$S$A | $A$S$S | $A$S | $S$A ) /x;
Wyrażenie zbudowane jest z podwyrażeń: $A dopasowującego samogłoski, oraz $P dopasowującego spółgłoski. Połączenie tych podwyrażeń tworzy $WYRAŻENIE. Zupełnie jak domek zbudowany z klocków. :) Zastosowanie tego wyrażenia wraz z modyfikatorem /g do tekstu podanego w zadaniu generuje natychmiast listę sylab: "Nad mor zem jes kop ak ab an zup tam jes ok". Zwrócić tu uwagę należy (jak napisano mi w mailu), że dłuższe podwyrażenia powinny występować we wzorcu wcześniej, gdyż krótsze dopasowywane przed dłuższymi nie dopuszczą do dopasowania tych dłuższych (gorliwość maszyny dopasowującej - akceptuje pierwsze dopasowanie, i wygrywa to nad zachłannością). - (1p) Za pomocą jakiego wyrażenia regularnego można sprawdzić, czy w napisie wystąpiły WSZYSTKIE znaki z klasy znakowej [\w]?.
Całe szczęście, że zadanie to jest tylko za jeden punkt, ponieważ NIKT go nie zrobił prawidłowo. Generuje zatem najmniejszą możliwą stratę niż gdyby zadanie było np. za 3 punkty. Zapytany tuż po kolokwium o rozwiązanie nie potrafiłem go podać, gdyż podczas pisania zadania miałem jakiś pomysł na rozwiązanie lecz okazał się on nieprawidłowy. W efekcie zadanie okazało się trudniejsze do zrobienia, ale nadal możliwe, niestety - przy mistrzowskim poziomie opanowania materiału... Na co oczywiście nie mogę liczyć :) A przyczyna trudności leży w wymaganiach: oto należy sprawić, aby dopasowały się wszystkie znaki z danej klasy. Nie dowolne. Wszystkie! Nietrudno napisać wyrażenie które dopasuje się do znaków w podanej kolejności, np. alfabetycznej. Ale co jeżeli ta kolejność może być dowolna? To stanowiło główną trudność. I generowanie wszystkich możliwych ustawień liter, w przypadku tak dużej klasy jak \w mija się z celem, gdyż samo wyrażenie zajmowałoby więcej niż zajmują wszystkie dyski na świecie razem wzięte (62! to w końcu dość sporo, rok 2007 :]). Wyrażenie, które dopasowuje się do napisów zawierających wszystkie znaki z klasy \w niestety jest troszę bardziej skomplikowane niż wyrażenia których wymagam normalnie od studentów. Niemniej materiał i wiadomości potrzebne do wykonania tego zadania zostały przedstawione publicznie, zatem to zadanie, mimo nieco trudniejszego charakteru także wpisuje się w kolokwium. Od razu tłumaczę, że wyrażenia w postaci\w+, [\w]+ i podobne nie zadziałają. Klasa znakowa dopasowuje się już wtedy, gdy wystąpi w tekście chociaż jeden znak z jej zestawu. Nie można także napisać (a|b|c|d|......|z) z tego samego powodu: znak pionowej kreski oznacza LUB, a więc znowu nie ma wymagania wystąpienia wszystkich podanych znaków. Jedyne co jest gwarantowane to dowolna kolejność znaków, ale nie ich wspólne wystąpienie. Brakuje czegoś, co oznaczać mogłoby ORAZ. Jednym z rozwiązań jest zastosowanie mnóstwa jednoliterowych wyrażeń:
if( /a/ and /b/ and /c/ and /d/ and /e/ and /f/ and /g/ and /h/ and /i/ and /j/ and /k/ and /l/ and /m/ and /n/ and /o/ and /p/ and /q/ and /r/ and /s/ and /t/ and /u/ and /v/ and /w/ and /x/ and /y/ and /z/ and /A/ and /B/ and /C/ and /D/ and /E/ and /F/ and /G/ and /H/ and /I/ and /J/ and /K/ and /L/ and /M/ and /N/ and /O/ and /P/ and /Q/ and /R/ and /S/ and /T/ and /U/ and /V/ and /W/ and /X/ and /Y/ and /Z/ and /1/ and /2/ and /3/ and /4/ and /5/ and /6/ and /7/ and /8/ and /9/ and /0/ and /_/ ){...}
nie jest to jednak wygodny sposób, chociaż stosunkowo prosty i zrozumiały. Jest to właśnie taka wersja rozwiązania za 1 punkt... :) Co więcej, jest dość szybki gdyż łańcuch operatorów and natychmiast przerwie sprawdzanie, gdy zawiedzie chociaż jeden z nich. Warto przed wykonaniem tylu dopasowań użyć study, aby przyspieszyć analizę tekstu przez niezakotwiczone wzorce. Jedyny mankament to pewna niezgodność rozwiązania z treścią zadania: chodziło o jedno wyrażenie regularne. Może to mój błąd że tak sformułowałem treść, ale gdyby choć jedna osoba napisała mi powyższe rozwiązanie, zostałoby ono zaliczone. Gdyby trzymać się jednak ściśle treści zadania, szybko okazywało się, że wykracza ono poza ramy czasowe typowego zadania kolokwialnego (bardziej nadając się na pytanie egzaminacyjne). Sposób polega na zastosowaniu wyrażenia regularnego z odrobiną kodu perla, który wykona dodatkowe operacje trudne do zaszyfrowania w samym wyrażeniu. Kod ten jest wykonywany przy przechodzeniu przez każdy znak w trakcie dopasowywania, dlatego wyrażenie samo siebie zapętla rekurencyjnie:$R=qr{ (\w)(?{$k{$+}++;$x=(@q=keys%k)<63?$R:''})(??{$x}) }x;
Sekret jego działania polega na tym, że w trakcie dopasowywania się do napisu, BUDOWANA jest treść wyrażenia, które jest dopasowywane. A więc wyrażenie to na podstawie znaków napotkanych stworzy samo siebie, dobierając odpowiednio znaki z klasy znakowej \w. Kluczowym testem jest liczba przechwyconych do tej pory znaków: dopasowywana jest klasa \w a następnie dopasowany znak (pamiętany w $+) trafia do hasza %k, gdzie bonusowo zliczana jest liczba wystąpień, ale wystarczy także napisać że $k{$+}=1. Hasz jest potrzebny po to, aby określić, czy na bieżącym etapie natrafiono już na odpowiednią liczbę znaków. Ponieważ wyrażenie dopasowuje się do \w, to tylko znaki z tej klasy znakowej mogą wchodzić do hasza (inne są pomijane). W haszu klucze nie mogą się powtarzać, zatem określenie że znaleźliśmy wszystkich przedstawicieli klasy \w jest możliwe po prostu przez policzenie kluczy do tej pory do hasza wstawionych. Wyrażenie po sprawdzeniu ile jest już znaków tworzy samo siebie w zmiennej $x albo pusty napis i po wykonaniu całego kodu wykonuje samo siebie przez umieszczenie asercji redukującej się do wyrażenia regularnego (??{$x}). Na dowód sprawności tego rozwiązania proponuję przestudiować przypadek, gdy wyrażenie dopasowuje się do napisu zbudowanego ze znaków tworzących klasę znakową \w:$_ = join '', a..z, A..Z, 0..9, _;
- (2p) Zadanie dowolne.
Zadania dla grupy 3
Start: 2007-03-23 13:00, czas: 30 minut czasu delty.- (1p) Opracuj wyrażenie regularne, które pasuje do napisu wtedy, gdy występują w nim samogłoski AEY.
To zadanie było trywialne. Wystarczyło napisać: /[AEY]/ i już. Inne rozwiązanie (podesłane przez studenta) może wyglądać tak: /A.*E.*Y|A.*Y.*E|E.*A.*Y|E.*Y.*A|Y.*A.*E|Y.*E.*A/ i jako takie było także zaliczane. W końcu, gdyby zupełnie dosłownie rozumieć treść zadania (w którym nie określono prezycyzyjnie jak mają te samogłoski występować), można także napisać: /AEY/ i potem upierać się, że to także jest poprawna odpowiedź. Była zaliczana podobnie jak dwie poprzednie :)
- (1p) Przy pomocy wyrażenia regularnego sprawdź, czy podany z STDIN dowolny tekst nie jest pusty (czyli czy nie naciśnięto tylko ENTER zamiast coś wpisać).
Można zaproponować rozwiązanie, które w pętli wczytuje odpowiedź użytkownika, do chwili, gdy ten nie poda czegoś, co JEST napisem, z wyjątkiem napisu złożonego z białych znaków:
while( <STDIN> =~ /^\s*$/ ) { ... }
Działanie tego wyrażenia polega na tym, że jest ono prawdziwe w pętli dopóty, dopóki z <STDIN> nie zostanie wczytany jakikolwiek tekst. Blokowana jest nawet możliwość podania znaku końca pliku - Ctrl+D także jest wczytywane przez pętlę i wykonuje się jej następna iteracja. Dopiero podanie niebiałego znaku pozwala na wyjście. Inne zastosowanie:if( <STDIN> !~ /^\s*$/ ) { ... }
w tym przypadku wykonamy kod if'a, gdy tekst nie będzie pusty, czyli nie będzie złożony wyłącznie z opcjonalnych znaków białych. - (1p) Użyj mechanizmu wyrażeń regularnych, aby wydobyć drugie słowo z drugiego zdania tekstu, który znajduje się w zmiennej $tekst i ma długość w znakach przekraczającą tysiące znaków. Tekst ten zawiera więcej niż dwa zdania, i posiada wyłącznie znaki które należą do klasy znakowej [\w\s.,].
Zadanie to zostało rozwiązane przez nadspodziewanie niewiele osób, a przecież było dziecinnie proste. Wszystko co ważne, zostało określone w zadaniu. Drugie zdanie znajduje się w tekście po pierwszym zdaniu. Dzięki temu możemy się spodziewać, że będzie ono umieszczone w tekście po znaku kropki. Drugi wyraz w tym zdaniu to wyraz umieszczony po znaku kropki, spacjach, jednym wyrazie i spacjach. Oto wyrażenie regularne, które dopasuje się do drugiego słowa w drugim zdaniu.
$slowo = $tekst =~ /\.\s*\w+\s+(\w+)/;
Jednakże kontekst skalarny, narzucony przez operator przypisania =, wymusza tutaj zmianę zachowania wyrażenia regularnego i w efekcie do zmiennej $slowo przypisana zostanie liczba udanych dopasowań (czyli 1). Aby pobrać rzeczywiste słowo należy wymusić kontekst listy, a to można zrobić obejmując wyrażenie dopasowujące nawiasami i zaindeksować z nich pierwszy element:$slowo = ( $tekst =~ /\.\s*\w+\s+(\w+)/ )[0];
I tyle. - (1p) Do jakiej części tekstu dopasuje się wyrażenie w postaci /^home|work$/ gdy dopasowywanym tekstem jest "_abc_home_abc_123_work_123" (spacje pokazano jako kreski)?
Do żadnej. Zakotwiczenia umieszczone w wyrażeniu skutecznie uniemożliwią dopasowanie któregokolwiek ze słów w środku napisu.
- (1p) Jakie wyrażenie należy wpisać w poleceniu split, aby podzielić wiersz wczytany z pliku CSV na części zawierające dane? (podział wg. separatorów)
Nic prostszego: wystarczy wiedzieć, co ma być separatorem. Gdyby to był np. tabulator, wyrażenie miałoby postać:
@lista = split /\t/,$wiersz;
W ogólnym przypadku, gdy np. możliwych jest kilka różnych separatorów, można zastosować interpolowane wyrażenie regularne:@lista = split /$separator/, $wiersz
gdzie zmienna $separator jest zdefiniowana np. w taki sposób: $separator = qr/[\s,'"]/; - (2p) Do tablicy @tab wczytano cały plik. Użyj polecenia grep /wzorzec/,@tab; aby wydobyć z tablicy wszystkie wiersze, które są 1) niepuste, 2) nie są komentarzem zaczynającym się od znaku # w pierwszej kolumnie.
Wiersze, które są niepuste, muszą zawierać przynajmniej jeden znak (lub więcej). Wiersze, które nie zaczynają się od komentarza w pierwszej kolumnie, nie mogą zawierać znaku # w tej pierwszej kolumnie. Obie grupy zatem można dopasować jednym wyrażeniem, które w dodatku jest bardzo proste:
if( /^[^#]/ ){ ... }
- (2p) Opracuj wyrażenie regularne podstawiające, które zmodyfikuje temat wiadomości podany w formie napisu i usunie z niego wszystkie części "Re:", "Od:", "Fwd:" stojące na początku tytułu. Przykładowe napisy:
$temat = "Re: what about: your viagra orders";
$temat = "Od: Re: Re: the new one";
$temat = "Fwd: Forwarded message from: gatez@microsoft.com";
Należy stworzyć listę słów do usunięcia, ograniczonych prawostronnie dwukropkiem i wstawić je do wyrażenia zastępującego:$temat =~ s/(?:Re|Od|Fwd):\s*//g;
Niestety, taka akcja jest niebezpieczna, ponieważ przez nią można usunąć praktycznie dowolne słowo z podanej listy, o ile występuje za nim dwukropek, i słowo to wcale nie musi znajdować się na początku napisu. Aby wymusić usuwanie wyrazów wyłącznie z początku napisu, należy dodać zakotwiczenie:$temat =~ s/^(?:Re|Od|Fwd):\s*//g;
Wtedy jednakże usuwany zostaje tylko JEDEN wyraz, zamiast wszystkich, mimo dodanego modyfikatora /g! Czary? :) Zapewne uważny student od razu będzie wiedział o co chodzi, ale wszystkim innym na pewno umknie fakt, że kazaliśmy zamienić tylko JEDNO wystąpienie dowolnego wyrazu z listy. Modyfikator /g nie ma tu NIC do rzeczy. Prawidłowo wyrażenie powinno wyglądać tak:$temat =~ s/^(?:(?:Re|Od|Fwd):\s*)*//;
- (1p) Jak zamienić znaki ą, ę, ś na odpowiadające im znaki a, e, s? Podaj przynajmniej jeden sposób.
Prosto: tr/ąęś/aes/.
- (1p) Czym różni się działanie wyrażenia $a = $_ =~ /wzorzec/g od działania wyrażenia @a = $_ =~ /wzorzec/g?
Oba wyrażenia robią dokładnie to samo, różnica jest w wyniku, który jest zwracany ze względu na kontekst. Pierwszy przypadek umieści w zmiennej $a wartość równoznaczną z prawdą (1) lub fałszem (zwykle pusty napis), drugi wstawi do tablicy @a wszystkie dopasowania framentów tekstu, które pasowały do wzorca.
- (2p) Zadanie dowolne.
Zadania dla grupy 4
Start: 2007-03-23 15:30, czas: 30 minut czasu delty.- (1p) Opracuj wyrażenie regularne, które pasuje do napisu tylko wtedy, gdy występują w nim same spółgłoski LUB napis ten zawiera wyłącznie same samogłoski.
Rozwiązanie sprowadza się do napisania wyrażenia dopasowującego specyficzne klasy znakowe: /^(?:[aeyiou]+|[^\Waeyiou\d]+)$/.
- (1p) Przy pomocy wyrażenia regularnego sprawdź, czy podany z STDIN dowolny tekst zawiera dodatnią liczbę całkowitą.
Można było wykonać najprostsze wyrażenie regularne, które to sprawdzało: /^\+?\d+$/. Bardziej zaawansowane wersje powinny sprawdzać, czy nie jest to liczba rzeczywista przez kontrolę występowania znaków [Ee] za cyframi lub kropki dziesiętnej.
- (1p) Użyj mechanizmu wyrażeń regularnych, aby podzielić dany tekst na pojedyncze znaki, podobnie jak to robi np. split //,$tekst. Oczywiście nie używaj split :)
Trywialne: listę znaków otrzymać można z wyrażenia m{.}g.
- (1p) Do tablicy @tab fragment pliku. Użyj polecenia grep /wzorzec/,@tab; aby wydobyć z tablicy wszystkie wiersze, które są 1) puste, 2) zawierają komentarz po znaku # ustawionym gdziekolwiek.
Można wykorzystać na przykład takie polecenia:
@puste = grep /^$/, @tab;
oraz:@z_komentem = grep /#/,@tab;
- (2p) Opracuj wyrażenie regularne podstawiające, które zmodyfikuje dowolny tekst w języku polskim, zawierający słowa "python" w różnych odmianach i zastąpi je przez słowo "perl" w takich samych odmianach. Przykładowo: "użytkownicy pythona wierzą pythonowi" powinno być zamienione na "użytkownicy perla wierzą perlowi", itp.
Zadanie jest łatwiejsze niż jego treść. Wystarczy wyrażenie s/python/perl/g :) Update: T.W. zwrócił mi uwagę, że rozwiązanie tak postawione jest złe, ponieważ oba wspomniane wyrazy odmieniają się w języku polskim inaczej! Problem nie leży w rdzeniu słowa, ale w końcówkach, a mianowicie, w miejscowniku i wołaczu będzie różnica: perlu/pythonie. Dlatego wyrażenie regularne musi uwzględnić ten przypadek i nie będzie juz tak proste:
s/(pythonie|python)/if( $1 eq "pythonie") { "perlu";} else {"perl"; } /ge
Korzystamy z podstawienia ewaluowanego, dlatego kod perla zniknie po wykonaniu modyfikatora /e, zastąpiony odpowiednim słowem w odpowiedniej formie gramatycznej. Dziękuję T.W. za wskazanie tego błędu :) - (1p) Do jakiej części tekstu wielowierszowego dopasuje się wyrażenie w postaci /\A.*/?
Tutaj było sporo różnych wersji, zależnie od stopnia zdesperowania i pomysłowości osobnika. Odpowiedź może być jednak tylko jedna i to taka: wyrażenie dopasuje się do pierwszego wiersza jeżeli napis będzie wielowierszowy, lub do całego napisu, jeżeli napis będzie jednowierszowy. I to wszystko.
- (1p) Jak za pomocą wyrażenia regularnego (lub ściślej - za pomocą operatorów związanych z wyrażeniami regularnymi) - policzyć ilość znaków w napisie?
Na przykład: $liczba = $napis =~ tr/[^\0x0]/[^\0x0]/c.
- (1p) Jak działa modyfikator /s w operatorach dopasowania wzorca?
Rozszerza klasę znaków do których pasuje metaznak kropki, to znaczy, powoduje, że kropka zaczyna pasować także do znaku nowego wiersza.
- (2p) Jakie wyrażenie należy wpisać w poleceniu split, aby z fragmentu tekstu XML w postaci <a>AA</a><b>BB</b><c>CC</c> wydobyć zawartość tagów XML? Czyli - jak uzyskać listę podnapisów "AA", "BB", "CC", itp?
Operator split dzieli napis wg. podanego separatora. Należy zatem tak zdefiniować wyrażenie regularne, aby split uznał tagi XML za separatory. Nic prostszego:
@napisy = split /<\/?\w+>/, $xml;
Oczywiście wyrażenie to działa tylko w tym prostym przypadku. - (2p)Zadanie dowolne.
[c] Piotr Arłukowicz, materiały z tej strony udostępnione są na licencji GNU.