DJFZ-2023/lab-02/lab-02.ipynb

16 KiB
Raw Blame History

Języki formalne i złożoność obliczeniowa

Część 1: Wprowadzenie do biblioteki re

Wyrażenia regularne będziemy robić na podstawie języka python3. Dokumentacja: https://docs.python.org/3.11/library/re.html

Użyteczna strona do testowania wyrażeń regularnych: https://regex101.com/

Podstawowe funkcje

Match

match - zwraca dopasowanie od początku stringa

import re
wynik = re.match(r'Ala', 'Ala ma kota. Kot ma Alę.')
print(wynik)
print(f"Dopasowano: '{wynik.group()}', początek: {wynik.start()}, koniec: {wynik.end()}")

search - zwraca pierwsze dopasowanie w napisie

# search
wynik = re.search(r'ni', 'Może nie najtaniej, ale jako tako.')

if wynik:
    print(f"Dopasowano: '{wynik.group()}', początek: {wynik.start()}, koniec: {wynik.end()}")
else:
    print('Nie znaleziono szukanego ciągu znaków.')
tekst = "Kto zakłóca ciszę nocną musi ponieść karę: 100 batów!"

wzorzec = re.compile('ci')

wynik = wzorzec.search(tekst)

if wynik:
    print(f"Dopasowano: '{wynik.group()}', początek: {wynik.start()}, koniec: {wynik.end()}")
else:
    print('Nie znaleziono szukanego ciągu znaków.')

findall

findall - zwraca listę wszystkich dopasowań

# findall
wzorzec = re.compile('o')

wynik = wzorzec.findall(tekst)

if len(wynik) > 0:
    print(type(wynik))
    print(f'Dopasowano: {wynik}')
else:
    print(f'{wynik} Nie znaleziono szukanego ciągu znaków.')
wzorzec = re.compile('o')

wynik = wzorzec.finditer(tekst)
for w in wynik:
    print(type(w))
    print(f'Dopasowano: "{w.group()}", początek: {w.start()}, koniec: {w.end()}')

_Uwaga: pobierając z iteratora elementy, usuwamy je, nie można zatem ponownie się do nich odwołać.

Użycie przedrostka 'r' w wyrażeniach regularnych w Pythonie

W Pythonie przedrostek r przed łańcuchem znaków oznacza "surowy" łańcuch znaków (ang. _raw string). Dlaczego jest to przydatne w kontekście wyrażeń regularnych?

Wyrażenia regularne często używają znaków specjalnych, takich jak \d, \w, \b itp. W standardowych łańcuchach znaków w Pythonie, znaki te mają specjalne znaczenie. Na przykład, \t oznacza tabulację, a \n oznacza nową linię.

Jeśli chcemy użyć takiego wyrażenia regularnego w Pythonie, musielibyśmy podwajać znaki ukośnika, aby uniknąć konfliktu z wbudowanymi sekwencjami ucieczki w łańcuchach znaków, np. \\\\d, \\\\w.

# Bez użycia surowego łańcucha znaków
result1 = re.findall("\\\\d+", "123 abc 456")
print(result1)

# Używając surowego łańcucha znaków
result2 = re.findall(r"\d+", "123 abc 456")
print(result2)

sub

Kolejną użyteczną metodą jest sub(), która pozwala na zmianę wzorca na inny ciąg znaków:

wzorzec = re.compile(r'ni')

zmieniony = wzorzec.sub('Ni!', tekst)
print(zmieniony)

split

split - dzieli napis na podstawie wzorca

wzorzec = re.compile(r' ')

wynik = wzorzec.split(tekst)
print(f'Uzyskano {len(wynik)} wyniki/ów.')
for w in wynik:
    print(w)

Metaznaki

[] - zbiór znaków

. - jakikolwiek znak

^ - początek napisu

$ - koniec napisu

? - znak występuje lub nie występuje

* - zero albo więcej pojawień się

+ - jeden albo więcej pojawień się

{} - dokładnie tyle pojawień się

| - lub

() - grupa

\ - znak ucieczki

\d digit

\D nie digit

\s whitespace

\S niewhitespace

wzorzec = re.compile(r'.')
print(wzorzec.findall(tekst))
# jeden lub wiecej / zero lub wiecej

tekst = "BCAAABGTAABBBCCTTSAGG4324242"
print(f'Łańcuch: {tekst}')
wzorzec = re.compile(r'X+')
print(f'Jeden lub więcej X: {wzorzec.findall(tekst)}')
wzorzec = re.compile(r'X*')
print(f'Zero lub więcej X: {wzorzec.findall(tekst)}')
# zero lub jeden

print(f'Łańcuch: {tekst}')
wzorzec = re.compile(r'.?')
print(wzorzec.findall(tekst))
wzorzec = re.compile(r'.?T')
print(wzorzec.findall(tekst))
print(f'Łańcuch: {tekst}')
wzorzec = re.compile(r'A+')
print(f'Dopasowanie zachłanne: {wzorzec.findall(tekst)}')
wzorzec = re.compile(r'A+?')
print(f'Dopasowanie leniwe: {wzorzec.findall(tekst)}')
print(f'Łańcuch: {tekst}')

# dokladnie 3 dopasowania
wzorzec = re.compile(r'A{3}')
print(f'{wzorzec.findall(tekst)}')

# pomiedzy 2 i 3 dopasowania
wzorzec = re.compile(r'A{2,3}')
print(f'{wzorzec.findall(tekst)}')

# 2 lub wiecej dopasowan
wzorzec = re.compile(r'A{2,}')
print(f'{wzorzec.findall(tekst)}')

# 3 lub mniej dopasowan
wzorzec = re.compile(r'A{,3}')
print(f'{wzorzec.findall(tekst)}')
# poczatek lub koniec lancucha

tekst = "Ale pięknie pachnie! Maciek, co gotujesz?"

# poczatek lancucha
wzorzec = re.compile(r'^Ale')
print(f'{wzorzec.findall(tekst)}')

# poczatek lancucha
wzorzec = re.compile(r'^ale')
print(f'{wzorzec.findall(tekst)}')

# koniec lancucha
wzorzec = re.compile(r'Ale$')
print(f'{wzorzec.findall(tekst)}')

# koniec lancucha + znak ucieczki
wzorzec = re.compile(r'gotujesz\?$')
print(f'{wzorzec.findall(tekst)}')
# grupy znakow
wzorzec = re.compile(r'[A-Z]')
print(f'Duże litery: {wzorzec.findall(tekst)}')

wzorzec = re.compile(r'[a-z]')
print(f'Małe litery: {wzorzec.findall(tekst)}')

wzorzec = re.compile(r'[A-z]')
print(f'Małe i duże litery: {wzorzec.findall(tekst)}')

wzorzec = re.compile(r'[aeiou]')
print(f'Samogłoski: {wzorzec.findall(tekst)}')
# wzorzec(?=X) - dopasowanie, jeśli po nim występuje X

tekst = "ACABADAHSAIIIQIIINSAODIANSAAGAGAGGGGPAAG"

print(f'Łańcuch: {tekst}')
wzorzec = re.compile(r'G+(?=A)')
print(f'{wzorzec.findall(tekst)}')
tekst = "Ale się zrobiła świąteczna atmosfera."
wzorzec = re.compile(r'Ale|świąteczna|zrobiła')
print(f'{wzorzec.findall(tekst)}')

Znaki specjalne

\s - biały znak

\S - nie-biały znak

\d - cyfra

\D - nie-cyfra

\w - znaki alfanumeryczne (litery i cyfry) oraz

\W - znaki nie-alfanumeryczne i nie

\b - początek lub koniec ,,słowa

\B - nie początek lub koniec ,,słowa''

[a-z] - małe litery

[A-Z] - wielkie litery

[0-9] - cyfry

wzorzec = re.compile(r'\w+')
print(f'Wyrazy: {wzorzec.findall(tekst)}')

Część II: Zadania praktyczne

Zadanie 1: Wyszukiwanie numerów telefonu

Napisz wyrażenie regularne, które znajdzie wszystkie numery telefonu w tekście. Zakładamy, że numer telefonu ma format xxx-xxx-xxx lub xxx xxx xxx.

tekst = """
Jan: 123-456-789
Anna: 987 654 321
Karol: 456-789-123
Zbyszek: 53252525342252
Tytus: aaaa666432
"""

wzorzec = re.compile(r"")
print(f'Numery: {wzorzec.findall(tekst)}')
tekst = """
jan.kowalski@gmail.com
anna.zielinska@amu.edu.pl
karol.nowak@interia.pl
hello world
@test.pl
x@x
fff22@gmail.com
"""

wzorzec = re.compile(r"")
print(f'Adresy: {wzorzec.findall(tekst)}')

Część III Zadanie domowe

Zadanie 1: Wyszukiwanie kodów pocztowych (2 pkt)

Napisz wyrażenie regularne, które znajdzie wszystkie polskie kody pocztowe w tekście. Zakładamy, że kod pocztowy ma format XX-XXX, gdzie X to cyfra.

Zadanie 2: Weryfikacja adresu URL (2 pkt)

Zaimplementuj wyrażenie regularne, które sprawdzi, czy dany ciąg jest poprawnym adresem URL. Zakładamy, że adres URL zaczyna się od http:// lub https://, po którym następuje nazwa domeny i opcjonalnie ścieżka.

Zadanie 3: Weryfikacja numeru PESEL (2 pkt)

Napisz wyrażenie regularne, które sprawdzi, czy dany ciąg jest poprawnym numerem PESEL. PESEL składa się z 11 cyfr.

Zadanie 4: Wyszukiwanie tagów HTML (3 pkt)

Zaimplementuj wyrażenie regularne, które znajdzie wszystkie tagi (otwierające i zamykające) w tekście HTML.

Zadanie 5: Weryfikacja hasła (4 pkt)

Napisz wyrażenie regularne, które sprawdzi, czy dane hasło spełnia następujące kryteria:

Ma co najmniej 8 znaków.

Zawiera co najmniej jedną dużą literę.

Zawiera co najmniej jedną małą literę.

Zawiera co najmniej jedną cyfrę.

Zawiera co najmniej jeden ze znaków specjalnych: @, #, $, %, &, *, !.

Rozwiązania zadań należy wysłać na adres email: miczar1@amu.edu.pl z tytułem: [DJFZ][NrIndeksu] Zadanie domowe Zajęcia 2. Jako załącznik należy wysłać 5 plików .py (zadanie1.py, zadanie2.py itd.) z rozwiązaniem. Skrpyty na wejściu mają przyjmować plik tekstowy in.txt a następnie zapisać rezultaty do pliku out.txt (każdy pasujący element to jeden wiersz) lub (True/False jak w poprzednim zadaniu) Przykładowe wywołanie skryptu: python zadanie1.py in.txt.