Błędy językowe, literówki i inne pomyłki w tekście zwykle biorą się z niewiedzy lub nieuwagi piszącego, ale czasem mogą mieć poważne konsekwencje – dlatego programy komputerowe oferują narzędzia do automatycznego przetwarzania tekstu: wyszukiwanie, podkreślanie powtórzeń, zamianę fraz. W tym materiale nauczysz się, jak komputer zapisuje tekst oraz jak napisać własne algorytmy porównywania i wyszukiwania tekstu w Pythonie.
strCele lekcji – po zrealizowaniu materiału uczeń:
str w Pythonie oraz funkcje chr, ord, len, metody find i rfind,Komputer nie „widzi” liter ani emoji – wszystko, co wyświetla się na ekranie, jest w pamięci zapisane jako liczby. Każdy znak (litera, cyfra, znak interpunkcyjny, symbol, emoji) ma przypisany swój kod liczbowy, czyli pozycję w ogromnej, uporządkowanej tablicy zwanej tablicą Unicode. W 2020 roku liczba zakodowanych w niej znaków przekraczała 280 000 – są tam litery wszystkich alfabetów świata, symbole matematyczne i muzyczne, a także setki emoji.
Podstawowe znaki, które można wprowadzić bezpośrednio z klawiatury (litery alfabetu łacińskiego, cyfry, znaki interpunkcyjne i symbole typu @, ^), znajdują się w początkowej części tablicy Unicode, na pozycjach od 0 do 127. Ten fragment nazywa się tablicą ASCII (ang. American Standard Code for Information Interchange) i pochodzi jeszcze z lat 60. XX wieku, gdy opracowywano go na potrzeby telekomunikacji.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 3_ | spacja | ! | " | # | $ | % | & | ' | ( | ) |
| 4_ | * | + | , | - | . | / | 0 | 1 | 2 | 3 |
| 6_ | @ | A | B | C | D | E | F | G | H | I |
| 9_ | ` | a | b | c | d | e | f | g | h | i |
Fragment tablicy ASCII. Numer znaku odczytujemy jak liczbę w systemie szesnastkowym: wiersz to cyfra dziesiątek, kolumna to cyfra jedności. Np. znak @ ma kod 64 (w tabeli: wiersz „6_”, kolumna 4), a spacja ma kod 32.
str w PythonieW Pythonie teksty przetwarza się za pomocą typu danych str (od ang. string). Napis przechowywany jest jako ciąg znaków, do których mamy dostęp poprzez indeksy – liczone od 0. Pierwszy znak napisu tekst to tekst[0], a ostatni – tekst[-1] (indeksy ujemne liczone są od końca).
Do konwersji między znakiem a jego kodem liczbowym służą dwie funkcje wbudowane:
ord(znak) – zwraca kod liczbowy podanego znaku (np. ord('A') ⇒ 65),chr(kod) – zwraca znak o podanym kodzie liczbowym (np. chr(65) ⇒ 'A') – to funkcja odwrotna do ord.Przydatna jest też funkcja len(napis), która zwraca liczbę znaków napisu.
for i in range(32, 127):
znak = chr(i)
print(znak, end=" ")
if i % 16 == 15:
print() # nowa linia co 16 znaków
Wyjaśnienie: pętla for przechodzi po kolejnych kodach od 32 do 126. Funkcja chr(i) zamienia kod na znak, który wypisujemy z odstępem (end=" "). Warunek i % 16 == 15 co 16 znaków przechodzi do nowej linii, żeby wynik był czytelny.
ord)znak = " "
kod = ord(znak)
kod2 = ord("~")
while kod <= kod2:
print(znak, end=" ")
if kod % 16 == 15:
print()
kod = kod + 1
znak = chr(kod)
Wyjaśnienie: zamiast z góry znać zakres liczbowy (32–126), tym razem podajemy zakres jako pierwszy i ostatni znak (spacja i ~) i obliczamy ich kody funkcją ord. Pętla while działa dopóki nie dojdziemy do drugiego znaku.
ord w Pythonie).@ oraz kod znaku spacji, korzystając z powyższej tablicy ASCII, a następnie sprawdź wynik funkcją ord w Pythonie.chr i ord.strKlasa str udostępnia gotowe metody ułatwiające pracę z tekstem, np.:
napis.find(wzorzec) – zwraca indeks pierwszego wystąpienia wzorca w napisie (licząc od lewej), albo -1, jeśli wzorca nie ma,napis.rfind(wzorzec) – zwraca indeks ostatniego wystąpienia wzorca (szuka od prawej strony).Dla uproszczenia przyjmujemy, że poprawny adres e‑mail to napis, w którym:
@, który jest co najmniej trzecim znakiem napisu,@ występuje kropka ., będąca trzecim lub czwartym znakiem od końca napisu i nie występująca bezpośrednio po @.Przykłady poprawnych adresów: [email protected], [email protected]. Niepoprawne: [email protected] (za krótka nazwa użytkownika), nk@[email protected] (dwa znaki @), n.@pl (brak @), [email protected] (kropka zaraz po @).
CzyPoprawnyAdres (metody find i rfind)def CzyPoprawnyAdres(adres):
dl = len(adres)
i = adres.find("@") # etap 1: pierwsze wystąpienie @
if i < 2 or i == -1:
return False
j = adres.rfind("@") # etap 2: ostatnie wystąpienie @
if i != j: # jeśli i != j, są co najmniej dwa @
return False
k = adres.rfind(".") # etap 3: ostatnia kropka
if k == -1:
return False
if not (k == dl - 3 or k == dl - 4):
return False
if k - i <= 1: # etap 4: kropka tuż po @?
return False
return True
if __name__ == "__main__":
adres_1 = input("Podaj adres e-mail: ")
print("Poprawny" if CzyPoprawnyAdres(adres_1) else "Niepoprawny")
Wyjaśnienie kolejnych etapów:
@ od lewej. Jeśli go nie ma (i == -1) albo jest na pozycji 0 lub 1, adres jest niepoprawny.@ od prawej. Jeśli pozycje i i j się różnią, w napisie jest więcej niż jeden znak @..pl, .com).@ (musi być między nimi co najmniej jeden znak).Wiele serwisów internetowych przy zakładaniu konta prosi o dwukrotne podanie adresu e‑mail, żeby wykryć literówkę.
def CzyPoprawnyAdres(adres):
dl = len(adres)
i = adres.find("@")
if i < 2 or i == -1:
return False
j = adres.rfind("@")
if i != j:
return False
k = adres.rfind(".")
if k == -1 or not (k == dl - 3 or k == dl - 4):
return False
if k - i <= 1:
return False
return True
adres_1 = input("Podaj adres e-mail: ")
adres_2 = input("Powtórz adres e-mail: ")
if adres_1 == adres_2:
print("Hasło do serwisu wysłaliśmy na adres " + adres_1 if CzyPoprawnyAdres(adres_1) else "Adres ma niepoprawną strukturę.")
else:
print("Podane adresy e-mail są różne!")
Kolejne zadanie: mamy listę słów wpisywanych alfabetycznie, jedno pod drugim, zakończoną trzema gwiazdkami (***). Chcemy usunąć zbędne, sąsiadujące powtórzenia (skoro lista jest posortowana, powtórzenia tego samego produktu zawsze sąsiadują ze sobą).
Sposób rozwiązania: dopóki na wejściu pojawiają się nowe wyrazy, porównujemy ostatnio wczytany wyraz z poprzednim i w razie potrzeby pomijamy powtórzenie, a nieusunięte wyrazy dołączamy do listy wynikowej.
N = 20
wynik = []
i = 1
nowy = input()
wynik.append(nowy)
stary = nowy
nowy = input()
while nowy != "***" and i < N:
if nowy != stary:
i = i + 1
wynik.append(nowy)
stary = nowy
nowy = input()
for j in wynik:
print(j)
Wyjaśnienie: zmienna wynik to pusta lista, do której metodą append dopisujemy kolejne, nowe słowa. Zmienna i liczy elementy na liście (zabezpieczenie przed przekroczeniem N). W pętli while sprawdzamy, czy ostatnio wczytany napis różni się od poprzedniego – jeśli tak, dopisujemy go do wynik.
CzyPoprawnyAdres i sprawdź jej działanie dla adresów: [email protected], [email protected], ab@cdpl, [email protected].chleb, chleb, masło, masło, ser, szynka, szynka, ***.Wyszukiwanie wzorca w tekście (ang. pattern matching) polega na znalezieniu miejsca, w którym w danym tekście występuje wskazany łańcuch znaków (wzorzec) – o ile w ogóle występuje. To dokładnie to, co robimy „na oko”, szukając ukrytych wyrazów w wykreślance, albo to, co w tle robi wyszukiwarka w edytorze tekstu czy w przeglądarce (Ctrl+F).
Najprostszy (tzw. naiwny) algorytm wyszukiwania wzorca przesuwa wzorzec wzdłuż tekstu o jeden znak i za każdym razem porównuje go znak po znaku z odpowiednim fragmentem tekstu.
Oznaczmy długość tekstu literą n, a długość wzorca literą m. Lista kroków algorytmu:
TEKST = "ALA ALBO ADA"
def Porownaj(wzorzec):
n = len(TEKST)
m = len(wzorzec)
for poz in range(0, n - m + 1):
j = 0
while j < m and TEKST[poz + j] == wzorzec[j]:
j = j + 1
if j == m:
return poz
return -1
print("Tekst:", TEKST)
print("Podaj wzorzec:", end=" ")
wzorzec = input()
print("Pozycja:", Porownaj(wzorzec))
Wyjaśnienie: zewnętrzna pętla for (linie z poz) przesuwa punkt startu porównania po kolejnych znakach tekstu. Wewnętrzna pętla while porównuje kolejne znaki wzorca z fragmentem tekstu – działa dopóki znaki się zgadzają (TEKST[poz+j] == wzorzec[j]) i nie doszliśmy do końca wzorca. Jeśli licznik j osiągnął długość wzorca m, oznacza to pełne dopasowanie – zwracamy poz, czyli liczbę znaków tekstu poprzedzających wzorzec.
Przykładowo, dla tekstu ALA ALBO ADA i wzorca ALBO, algorytm zwróci wartość 4 (bo cztery znaki: A, L, A, spacja, poprzedzają dopasowanie).
napis.find(wzorzec), która realizuje to samo zadanie – w kolejnych latach nauki poznasz bardziej zaawansowane algorytmy dopasowywania wzorca.ZA SZCZYTEM ZASZCZYT i wzorca ZASZCZYT. Ile prób dopasowania trzeba wykonać?ALA oraz ALBO.ord(znak) zwraca kod znaku, a chr(kod) – znak o danym kodzie. Funkcja len(napis) zwraca długość napisu.find zwraca indeks pierwszego wystąpienia wzorca w napisie, a rfind – indeks jego ostatniego wystąpienia; obie zwracają -1, gdy wzorca nie znaleziono.