Kolekcje: Listy, Słowniki i Zbiory
Wybór właściwej struktury danych decyduje o czasie działania programu. Kiedy lista, kiedy słownik, a kiedy zbiór — i dlaczego to zmienia złożoność.
Wprost o strukturę danych pytają rzadko, ale polecenie „wyjaśnij, dlaczego drugie rozwiązanie działa szybciej" wraca regularnie, a odpowiedź brzmi prawie zawsze tak samo: sprawdzenie „czy już było" kosztuje w liście przejście po wszystkich elementach, a w zbiorze albo słowniku jest natychmiastowe. Do tego dochodzi zliczanie słownikiem z metodą get. Te dwa schematy zamykają większość zadań tego typu. Zbiory i słowniki wprowadzamy w drugiej kolejności, dopiero gdy uczeń pewnie czuje listy — inaczej robi się mętlik. Największą różnicę widać potem w zadaniach na zliczanie liter albo zbitek znaków.
1. Trzy struktury, trzy zastosowania
Na maturze rzadko pytają wprost o strukturę danych — ale wybór złej potrafi zamienić program działający w sekundę w taki, który liczy minutami. To częsty temat pytania „dlaczego drugie rozwiązanie jest szybsze".
- Lista — kolejność ma znaczenie, elementy mogą się powtarzać. Dostęp po indeksie.
- Słownik — para klucz i wartość. Idealny do zliczania i wyszukiwania po nazwie.
- Zbiór — tylko unikalne elementy, bez kolejności. Do sprawdzania „czy już było".
2. Dlaczego zbiór bywa szybszy od listy
Sprawdzenie x in lista wymaga przejścia po kolei przez elementy, czyli
. To samo sprawdzenie na zbiorze albo słowniku działa
w czasie średnio stałym, .
# wolno: dla kazdego elementu przechodzimy cala liste
wynik = []
for x in dane:
if x not in wynik: # O(n) przy kazdym obiegu
wynik.append(x)
# szybko: sprawdzenie w zbiorze jest natychmiastowe
widziane = set()
wynik = []
for x in dane:
if x not in widziane: # O(1)
widziane.add(x)
wynik.append(x)Pierwsza wersja ma złożoność , druga . Przy dziesięciu tysiącach danych z arkusza maturalnego to różnica między ułamkiem sekundy a wyraźnym oczekiwaniem.
3. Słownik do zliczania
Zadania typu „która litera występuje najczęściej" albo „ile razy pojawia się każde słowo" rozwiązuje się słownikiem w kilku linijkach.
licznik = {}
for znak in tekst:
licznik[znak] = licznik.get(znak, 0) + 1
# element o najwiekszej wartosci
najczestszy = max(licznik, key=licznik.get)Metoda get z wartością domyślną pozwala uniknąć sprawdzania, czy klucz już
istnieje. To skraca kod i eliminuje najczęstszy błąd w tym zadaniu.
4. Sortowanie po kluczu — najczęstsza operacja w arkuszu
Dane z pliku wczytujesz zwykle jako listę krotek albo list. Polecenie „podaj trzy największe”, „uporządkuj malejąco według drugiej kolumny” czy „wypisz alfabetycznie” sprowadza się wtedy do jednej linijki z argumentem key.
dane = [("Kowalski", 45, "Krakow"), ("Nowak", 78, "Gdansk"), ...]
# wedlug drugiej kolumny, malejaco
dane.sort(key=lambda w: w[1], reverse=True)
# nie zmieniajac oryginalu
posortowane = sorted(dane, key=lambda w: w[1], reverse=True)
# dwa kryteria naraz: miasto rosnaco, w ramach miasta punkty malejaco
dane.sort(key=lambda w: (w[2], -w[1]))
# trzy najwieksze
najlepsi = sorted(dane, key=lambda w: w[1], reverse=True)[:3]
# slownik posortowany wedlug wartosci
for klucz, ile in sorted(licznik.items(), key=lambda p: p[1], reverse=True):
print(klucz, ile)sort czy sorted? Pierwsza zmienia listę w miejscu i zwraca None — zapis dane = dane.sort() kasuje dane, to klasyczna pomyłka. Druga zwraca nową listę i nie rusza oryginału.-w[1]) odwraca porządek tylko tej jednej wartości, gdy reverse odwróciłby wszystkie kryteria naraz. Działa oczywiście tylko na liczbach.5. Krotki, kopie i odrobina składni, która skraca kod
# rozpakowanie krotki - czytelniej niz w[0], w[1], w[2] for nazwisko, punkty, miasto in dane: ... # wyrazenie listowe zamiast petli z append parzyste = [x for x in liczby if x % 2 == 0] kwadraty = [x*x for x in liczby] # enumerate - indeks i wartosc naraz for i, wartosc in enumerate(liczby): ... # zip - dwie listy rownolegle for nazwa, cena in zip(nazwy, ceny): ... # suma, max, min, dlugosc - nie pisz tego recznie print(sum(liczby), max(liczby), min(liczby), len(liczby)) print(liczby.index(max(liczby))) # pozycja najwiekszego
b = a daje drugą nazwę dla tej samej listy — zmiana przez b widoczna jest w a. Kopię robi b = a[:] albo b = a.copy(). Przy tablicy dwuwymiarowej i to nie wystarczy: [[0]*3]*3 tworzy trzy odwołania do tego samego wiersza, więc zmiana jednej komórki zmienia całą kolumnę. Poprawnie: [[0]*3 for _ in range(3)].(a, b).a & b to część wspólna, a | b suma, a - b różnica. Polecenie „którzy klienci występują w obu plikach” to jedna linijka, a nie podwójna pętla.6. Pułapki
- Zbiór gubi kolejność i duplikaty. Jeśli zadanie wymaga zachowania kolejności wystąpień, potrzebujesz zbioru i listy naraz, jak w przykładzie wyżej.
- Modyfikowanie listy w trakcie iterowania po niej daje pominięte elementy. Buduj nową listę zamiast usuwać z bieżącej.
- Kluczem słownika nie może być lista, bo jest zmienna. Krotka może.
- Sortowanie po wartościach słownika wymaga wskazania klucza sortowania — samo
sorted()uporządkuje klucze, nie wartości.
Zestaw algorytmów wracających w arkuszach, z gotowym kodem i wyjaśnieniem, kiedy który wybrać.
Korepetycje z informatyki
Zdajesz maturę rozszerzoną z informatyki?
Prowadzimy indywidualne przygotowanie do matury z informatyki — algorytmy, Python i C++, bazy danych i arkusz kalkulacyjny. Zajęcia online, na arkuszach CKE. Zobacz program i cennik.
Zobacz korepetycje z informatykiUczysz się w technikum informatycznym? Prowadzimy też przygotowanie do kwalifikacji INF.02, INF.03 i INF.04. Zobacz egzaminy zawodowe →
Nadal czujesz się niepewnie?
To tylko jeden z pewniaków. Na kursie przechodzimy przez nie wszystkie, krok po kroku, aż poczujesz ten spokój.
Pomóżcie mi zdać maturę!