Dział V

Algorytmy Tekstowe (Stringi)

Przetwarzanie napisów, szyfrowanie, anagramy i wyszukiwanie wzorców. Kluczowe techniki pracy z tekstem, hasełami i ciągami DNA.

Wstęp: Świat ukryty w napisach

Praca z tekstem na maturze to nie tylko wyświetlanie komunikatów. To zaawansowane operacje na ciągach DNA, łamanie szyfrów i analiza struktur haseł. W Pythonie napisy (stringi) są obiektami niemodyfikowalnymi (immutable), co oznacza, że każda zmiana tworzy tak naprawdę nowy napis. Zrozumienie tablicy ASCII oraz metod manipulacji tekstem to klucz do zdobycia kompletu punktów w zadaniach ze słowami.

1

Podstawowe operacje: Podciągi i Łączenie

W Pythonie tekst (typ str) to w rzeczywistości ciąg znaków, który pod wieloma względami zachowuje się jak lista. Z jedną ważną różnicą – jest niemodyfikowalny (nie możesz zmienić jednej litery w środku słowa, musisz stworzyć nowe słowo). Zobaczmy, jakimi narzędziami dysponujesz na maturze.

Matematyka na tekstach (+ oraz *)
Python pozwala na używanie operatorów matematycznych do manipulowania stringami. To niesamowicie przyspiesza formatowanie wyników.

# Konkatenacja (Łączenie)
poczatek = "Ala ma"
koniec = "kota"
zdanie = poczatek + " " + koniec

# Powielanie (Mnożenie)
kreska = "-" * 20
print(kreska) # Wypisze 20 myślników!

Podciągi: Operator IN oraz Slicing
Sprawdzanie, czy fragment tekstu (podciąg) występuje w większej całości, zajmuje w C++ kilkanaście linijek. W Pythonie to jedno słowo: in.

dna = "TGACTAGC"

# Błyskawiczne szukanie wzorca
if "ACT" in dna:
    print("Znaleziono kodon ACT!")

# Wycinanie podciągu (Slicing)
fragment = dna[2:5] # "ACT"

🔄
Palindromy i pułapka ASCII

Znasz już sposób sprawdzania palindromów jedną linijką: s == s[::-1]. Pamiętaj jednak o krytycznej pułapce! Dla komputera "K" (ASCII 75) oraz "k" (ASCII 107) to dwa zupełnie różne znaki. Słowo "Kajak" nie przejdzie testu na palindrom, jeśli go wcześniej nie ujednolicisz.

slowo = "Kajak"
# 1. Zmieniamy wszystko na małe litery (.lower())
# (Możesz też użyć .upper() dla wielkich)
czyste = slowo.lower()

# 2. Bezpieczny test
if czyste == czyste[::-1]:
    print("Prawidłowy palindrom!")

2

Anagramy: Porównywanie składu słów

Dwa słowa są anagramami, jeśli jedno powstało z liter drugiego poprzez zmianę ich kolejności (np. "ekran""nerka"). W praktyce oznacza to, że oba napisy muszą mieć tę samą długość oraz dokładnie taki sam zbiór i liczbę liter.

Metoda 1: Sortowanie (Elegancja)
Najprostszy sposób: jeśli oba słowa posortujemy alfabetycznie, to anagramy staną się identycznymi napisami. To rozwiązanie jest bardzo krótkie i trudne do pomylenia.

def czy_anagram_sort(s1, s2):
    if len(s1) != len(s2):
        return False
    return sorted(s1) == sorted(s2)

Złożoność czasowa: O(NlogN)O(N \log N) (wynika z szybkości sortowania).

Metoda 2: Liczenie znaków (Wydajność)
Bardziej profesjonalne podejście polega na zliczeniu wystąpień każdej litery. Jeśli licznik dla każdej litery w obu słowach jest taki sam – mamy anagram.

from collections import Counter

def czy_anagram_count(s1, s2):
    if len(s1) != len(s2):
        return False
    return Counter(s1) == Counter(s2)

Złożoność czasowa: O(N)O(N) (tylko jeden przebieg przez napisy).

🧠
Pamiętaj o czyszczeniu danych!

Na maturze słowa w pliku dane.txt mogą być oddzielone tabulatorami lub mieć zbędne spacje na końcu. Zanim zaczniesz sprawdzać, czy słowa są anagramami, zawsze wykonaj na nich operację .strip().



Jeśli zadanie mówi, że wielkość liter nie ma znaczenia, dodaj również .lower(). Porównywanie "A" z "a" bez tej metody zawsze zwróci False.

Szybki test logiczny: Pierwsze spojrzenie

Zanim uruchomisz ciężkie algorytmy sortowania, zawsze sprawdź najprostszy warunek: długość napisów. Jeśli len(s1) != len(s2), to napisy na pewno nie są anagramami. To "tanie" sprawdzenie, które pozwala pominąć zbędne obliczenia dla tysięcy błędnych par w pliku.

3

Szyfrowanie: Tablica ASCII, Cezar i Vigenère

Algorytmy kryptograficzne na maturze z informatyki opierają się na jednej kluczowej koncepcji: litery to tak naprawdę liczby zapisane w tablicy ASCII. Jeśli zrozumiesz, jak matematycznie manipulować tymi liczbami, napisanie dowolnego szyfru podstawieniowego zajmie Ci kilka minut.

Klucz do szyfrów: ord() i chr()

Szyfrowanie polega na przesuwaniu liter. Aby dodać do litery wartość liczbową, musimy najpierw zamienić ją na kod ASCII.

  • ord(znak) – zwraca kod ASCII. Dla 'A' jest to 65, a dla 'Z' to 90.
  • chr(kod) – zamienia liczbę z powrotem na znak.

# Jak bezpiecznie zamienić 'A' (65) na pozycję 0 w alfabecie?
litera = 'C'
pozycja = ord(litera) - 65
print(pozycja) # Zwróci 2, bo 'C' jest trzecią literą alfabetu (0, 1, 2)

Szyfr Cezara🏛️

Każdy znak przesuwamy o stałą wartość klucza kk. Problem pojawia się na końcu alfabetu (np. przesunięcie 'Z' o 1 powinno dać 'A'). Rozwiązuje to genialny wzór z użyciem modulo 26 (ilość liter w alfabecie angielskim).

E(x)=(x+k)(mod26)E(x) = (x + k) \pmod{26}

def cezar(tekst, k):
    wynik = ""
    for znak in tekst:
        # 1. Zamień znak na pozycję od 0 do 25
        poz = ord(znak) - 65
        # 2. Przesuń i zawróć alfabet (modulo 26)
        nowa_poz = (poz + k) % 26
        # 3. Wróć do tablicy ASCII (dodaj 65)
        wynik += chr(nowa_poz + 65)
    return wynik

Szyfr Vigenère'a (Cezar na sterydach)

W Szyfrze Cezara kluczem jest jedna liczba (np. k=3k = 3). W szyfrze Vigenère'a kluczem jest słowo (np. "TAJNE"). Kolejne litery klucza decydują o tym, o ile przesunąć daną literę tekstu jawnego. Gdy klucz się kończy, powtarzamy go od początku.

def vigenere(tekst, klucz):
    wynik = ""
    for i in range(len(tekst)):
        # Jakie jest przesunięcie 'k' w tym kroku?
        # Modulo pozwala zapętlić klucz!
        litera_klucza = klucz[i % len(klucz)]
        k = ord(litera_klucza) - 65

        # Reszta to klasyczny Cezar
        poz = ord(tekst[i]) - 65
        nowa_poz = (poz + k) % 26
        wynik += chr(nowa_poz + 65)
    return wynik

Na czym to polega?
Załóżmy, że klucz to "ABC". Ich pozycje w alfabecie to odpowiednio: 0, 1, 2.
  • Pierwszą literę tekstu przesuwamy o 0.
  • Drugą literę tekstu przesuwamy o 1.
  • Trzecią literę tekstu przesuwamy o 2.
  • Czwartą literę... znowu o 0 (tu działa magia i % len(klucz)).

Tip CKE: Czasami CKE narzuca, by ignorować w przesunięciach spacje (spacja nie zjada litery z klucza). Wtedy zamiast pętli for i in range... używamy osobnego ręcznego licznika indeks_klucza += 1.

🔓
Deszyfrowanie (Odszyfrowywanie)

Jeśli egzaminator poprosi Cię o odszyfrowanie wiadomości zaszyfrowanej Cezarem lub Vigenère'em, nie musisz pisać nowej funkcji! Wystarczy zmienić w kodzie jeden znak matematyczny. Przy zmiennej nowa_poz zmieniamy plus na minus:



nowa_poz = (poz - k) % 26.



Python doskonale radzi sobie z modulo na liczbach ujemnych i sam zawróci pozycję z powrotem na koniec alfabetu!

4

Wyszukiwanie wzorca: Algorytm Naiwny

Zadanie polega na znalezieniu wszystkich wystąpień krótkiego słowa (tzw. Wzorca, z ang. Pattern) wewnątrz długiego tekstu. Chociaż w Pythonie możesz to zrobić wbudowaną metodą, CKE regularnie sprawdza, czy rozumiesz matematyczne podwaliny tego procesu.

Sposób 1: Slicing (Szybki kod Pythonowy)

Polega na stworzeniu "okienka" o długości wzorca, które przesuwamy po tekście litera po literze. W każdym kroku sprawdzamy, czy wycinek tekstu pod okienkiem jest równy naszemu wzorcowi.

def szukaj_slicing(tekst, wzorzec):
    licznik = 0
    n = len(tekst)
    m = len(wzorzec)

    # Zatrzymujemy się, gdy do końca zostało mniej niż długość wzorca
    for i in range(n - m + 1):
        # Wycinamy fragment tekstu i porównujemy
        if tekst[i : i + m] == wzorzec:
            licznik += 1

    return licznik

Sposób 2: Klasyczny (Wymagany w Teorii)🎓

Wiele języków programowania nie obsługuje tak wygodnego slicingu. CKE często prosi o symulację z dwiema pętlami: zewnętrzna przesuwa wzorzec, a wewnętrzna sprawdza litera po literze.

def szukaj_klasycznie(tekst, wzorzec):
    n = len(tekst)
    m = len(wzorzec)
    for i in range(n - m + 1):
        zgodne = True
        for j in range(m):
            # Jeśli trafimy na różnicę, przerywamy pętlę wewnętrzną
            if tekst[i + j] != wzorzec[j]:
                zgodne = False
                break
        if zgodne:
            print(f"Znaleziono na indeksie {i}")

Wizualizacja "Przesuwanego Okienka" (Sliding Window)
Algorytm naiwny działa poprzez sprawdzanie każdej możliwej pozycji dopasowania. Jeśli trafiamy na niezgodność – przesuwamy wzorzec tylko o jedną pozycję w prawo. Oznacza to, że jego złożoność czasowa w najgorszym przypadku to O(NM)O(N \cdot M) (gdzie NN to długość tekstu, a MM długość wzorca).
T = A B A C A B A B C
W = A B A B (Błąd na 4. literze)
W =   A B A B (Błąd na 1. literze)
W =     A B A B (Błąd na 2. literze)
W =         A B A B ✓ SUKCES! (i=4)
💡
Legalny Cheat: Sposób Ultra-Szybki (Wbudowane metody)

Jeżeli w części praktycznej masz za zadanie wyłącznie policzyć ile razy wzorzec występuje w tekście (bez nakładania się na siebie, co jest standardem maturalnym), zrób to jedną wbudowaną funkcją:

tekst = "ABABABA"
ile_razy = tekst.count("ABA")
print(ile_razy) # Zwróci 2

Algorytmy Tekstowe Gotowe! 🧬

Znasz już wszystkie techniki manipulacji stringami – od szyfrowania Cezara po analizę wystąpień wzorców. To potężna baza punktowa. Jesteśmy gotowi, by przejść do ostatniego technicznego etapu: Optymalizacji i Sortowania.

Przejdź do: Optymalizacja i Sortowanie ➡️