10 - Podstawowe kolekcje cz. II - słowniki i zbiory
Słowniki
Listy i krotki, które poznaliśmy w poprzedniej lekcji, posiadają przypisane liczby (indeksy) do każdego elementu. Słowniki zamiast indeksów mają klucze, do których przypisane są wartości. Klucze są unikatowe (podobnie zresztą jak indeksy w listach), czyli nie może występować więcej niż jeden taki sam klucz w danym słowniku. Kluczem może być łańcuch znaków, ale także liczba i inne typy niemodyfikowalne. Wartości mogą się powtarzać. W przeciwieństwie do list kolejność elementów w słownikach nie jest uporządkowana, tzn. jeśli na przykład będziemy iterować klucze w słowniku, to niekoniecznie uzyskamy je w takiej kolejności, w jakiej były w nim umieszczane.
Tworzenie słowników
Utwórzmy pierwszy słownik, w którym przyporządkujemy do siebie symbole nukleotydów do ich znaczenia:
nukleotydy = {'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina',}
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Dla większej przejrzystości kodu można poszczególne pary klucz-wartość umieścić w osobnych liniach:
nukleotydy = {
'A': 'Adenina',
'C': 'Cytozyna',
'G': 'Guanina',
'T': 'Tymina',
}
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Jak widać, słownik tworzymy podobnie jak listę albo krotkę, jednak słownik jest ograniczony parą nawiasów klamrowych {}
a zamiast pojedynczych wartości mamy pary klucz: wartość
, przedzielone przecinkami. Zwróć uwagę na przecinek po ostatniej parze, nie jest on obowiązkowy, ale jeśli będziemy chcieli w kodzie dodać kolejne wartości, to nie trzeba będzie pamiętać o rozpoczęciu wpisywania od umieszczenia przecinka.
Innym sposobem tworzenia słowników jest użycie funkcji dict()
. W tym przypadku musimy pilnować, żeby klucze były nazywane zgodnie z zasadami określonymi dla zmiennych (nie mogą np. zawierać spacji):
nukleotydy = dict(
A = 'Adenina',
C = 'Cytozyna',
G = 'Guanina',
T = 'Tymina',
)
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Pusty słownik możemy uzyskać, używając pary nawiasów klamrowych lub funkcji dict()
.
slownik_1 = {}
print(slownik_1)
slownik_2 = dict()
print(slownik_2)
{}
{}
Liczbę elementów w słowniku, można sprawdzić za pomocą funkcji len()
:
print(len(nukleotydy))
4
Uzyskiwanie elementów słownika
Pierwszy ze sposobów uzyskiwania wartości ze słownika przypomina otrzymywanie wartości z listy z użyciem indeksu, tyle że w przypadku słownika podajemy nie indeks (którego nie ma), tylko klucz:
print(nukleotydy['G'])
Guanina
Jeśli podamy klucz, którego nie ma, to uzyskamy komunikat o błędzie:
print(nukleotydy['X'])
---------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-33-2da619e44e5c> in <module>
----> 1 print(nukleotydy['X'])
KeyError: 'X'
Można tego uniknąć, sprawdzając najpierw, czy klucz znajduje się w słowniku i podjąć próbę uzyskania wartości w przypadku jego obecności:
nukleotyd = input("Podaj symbol:")
if nukleotyd in nukleotydy:
print(f"Symbol '{nukleotyd}' oznacza: {nukleotydy[nukleotyd]}")
else:
print(f"Nie mam informacji o symbolu '{nukleotyd}'")
Podaj symbol: X
Nie mam informacji o symbolu 'X'
Drugim sposobem pobrania wartości dla klucza jest użycie metody get()
:
print(nukleotydy.get('A'))
Adenina
Sprawdźmy, co się stanie, jeśli klucza nie będzie w słowniku:
print(nukleotydy.get('X'))
None
Jak widać, w tym przypadku nie pojawia się błąd, uzyskujemy wartość None
.
Warto wspomnieć, że nie jest to obiekt typu String
, ale obiekt typu NoneType
, do którego jeszcze wrócimy przy okazji omawiania funkcji i metod.
type(nukleotydy.get('X'))
NoneType
Możemy nieco zmodyfikować powyższy kod, aby dostosować go do metody get()
:
nukleotyd = input("Podaj symbol:")
if nukleotydy.get(nukleotyd) != None:
print(f"Symbol '{nukleotyd}' oznacza: {nukleotydy.get(nukleotyd)}")
else:
print(f"Nie mam informacji o symbolu '{nukleotyd}'")
Podaj symbol: G
Symbol 'G' oznacza: Guanina
Metoda get()
posiada też możliwość ustawienia elementu, który zostanie zwrócony, jeśli podany klucz nie znajduje się w słowniku:
print(nukleotydy.get('X', "Nie ma X-a"))
Nie ma X-a
Warto zauważyć, ze zwracana wartość nie musi być łańcuchem znaków, może to być obiekt innego typu, np. liczba całkowita:
wynik = nukleotydy.get('X', 1)
print(wynik)
print(type(wynik))
1
<class 'int'>
Wszystkie klucze słownika można otrzymać, używając metody keys()
:
print(nukleotydy.keys())
dict_keys(['A', 'C', 'G', 'T'])
Podobnie, wartości uzyskamy stosując metodę values()
:
print(nukleotydy.values())
dict_values(['Adenina', 'Cytozyna', 'Guanina', 'Tymina'])
Do pobrania wszystkich par klucz-wartość, przyda nam się metoda items()
:
print(nukleotydy.items())
dict_items([('A', 'Adenina'), ('C', 'Cytozyna'), ('G', 'Guanina'), ('T', 'Tymina')])
W tym ostatnim przypadku otrzymaliśmy krotki z parami klucz-wartość.
Powyższe metody możemy użyć do iteracji przez klucze, wartości czy pary klucz-wartość, o czym za chwilę.
Dodawanie, modyfikacja i usuwanie elementów słownika
Nowe pary klucz-wartość, można dodać w ten sposób:
nukleotydy['U'] = 'Uran'
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina', 'U': 'Uran'}
Należy przy tym uważać, ponieważ jeśli podamy klucz, który już istnieje, to przypisana mu wartość ulegnie zmianie. Jest to oczywiście sposób modyfikacji par klucz-wartość:
nukleotydy['U'] = 'Uracyl'
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina', 'U': 'Uracyl'}
W celu usunięcia par klucz-wartość można użyć polecenia del
:
del nukleotydy['U']
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Innym sposobem jest zastosowanie metody pop()
, która jednocześnie zwraca wartość wg. klucza i usuwa parę klucz-wartość:
print(nukleotydy.pop('A'))
print(nukleotydy)
Adenina
{'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Zawartość całego słownika można usunąć za pomocą metody clear()
:
nukleotydy.clear()
print(nukleotydy)
{}
Słowniki i iteracja
Powyżej poznaliśmy metody keys()
, values()
oraz items()
, które umożliwiają iterację po kluczach, wartościach i parach klucz-wartość.
Najpierw stwórzmy ponownie słownik:
nukleotydy = {
'A': 'Adenina',
'C': 'Cytozyna',
'G': 'Guanina',
'T': 'Tymina',
}
Teraz iterujmy po kluczach:
for klucz in nukleotydy.keys():
print(klucz)
A
C
G
T
Warto zauważyć, że ten sam efekt można osiągnąć bez metody keys()
:
for klucz in nukleotydy:
print(klucz)
A
C
G
T
Oczywiście, skoro mamy klucz, można pobrać i wartość:
for klucz in nukleotydy.keys():
print(f"symbol: {klucz}, znaczenie: {nukleotydy[klucz]}")
symbol: A, znaczenie: Adenina
symbol: C, znaczenie: Cytozyna
symbol: G, znaczenie: Guanina
symbol: T, znaczenie: Tymina
Iteracja po wartościach będzie wyglądała podobnie:
for wartosc in nukleotydy.values():
print(wartosc)
Adenina
Cytozyna
Guanina
Tymina
Kolejne krotki z kluczami i wartościami pobierzemy z użyciem metody items()
:
for krotka in nukleotydy.items():
print(krotka)
('A', 'Adenina')
('C', 'Cytozyna')
('G', 'Guanina')
('T', 'Tymina')
Możemy oczywiście uzyskać poszczególne elementy krotek:
for krotka in nukleotydy.items():
print(f"symbol: {krotka[0]}, znaczenie: {krotka[1]}")
symbol: A, znaczenie: Adenina
symbol: C, znaczenie: Cytozyna
symbol: G, znaczenie: Guanina
symbol: T, znaczenie: Tymina
Jednak prościej i bardziej przejrzyście będzie to zrobić tak:
for klucz, wartosc in nukleotydy.items():
print(f"symbol: {klucz}, znaczenie: {wartosc}")
symbol: A, znaczenie: Adenina
symbol: C, znaczenie: Cytozyna
symbol: G, znaczenie: Guanina
symbol: T, znaczenie: Tymina
Łączenie słowników
Metoda update()
pozwala połączyć dwa słowniki:
nukleotydy = {
'A': 'Adenina',
'C': 'Cytozyna',
'U': 'Uran',
}
nukl_2 = {
'G': 'Guanina',
'T': 'Tymina',
'U': 'Uracyl',
}
nukleotydy.update(nukl_2)
print(nukleotydy)
{'A': 'Adenina', 'C': 'Cytozyna', 'U': 'Uracyl', 'G': 'Guanina', 'T': 'Tymina'}
Zauważ, że jeśli w drugim słowniku znajduje się para z tym samym kluczem co w pierwszym, to go zastępuje.
Można połączyć dwa lub więcej słowników innym sposobem:
n_1 = {
'A': 'Adenina',
'U': 'Uran',
}
n_2 = {
'C': 'Cytozyna',
'U': 'Uranium',
}
n_3 = {
'G': 'Guanina',
'T': 'Tymina',
'U': 'Uracyl',
}
nukleotydy = {**n_1, **n_2, **n_3}
print(nukleotydy)
{'A': 'Adenina', 'U': 'Uracyl', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Kopiowanie słowników
Do kopiowania słowników, można użyć metody copy()
:
nukleotydy = {
'A': 'Adenina',
'C': 'Cytozyna',
'G': 'Guanina',
'T': 'Tymina',
}
kopia = nukleotydy.copy()
print(f"{nukleotydy = }")
print(f"{kopia = }")
nukleotydy['U'] = 'Uracyl'
print(f"{nukleotydy = }")
print(f"{kopia = }")
nukleotydy = {'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
kopia = {'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
nukleotydy = {'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina', 'U': 'Uracyl'}
kopia = {'A': 'Adenina', 'C': 'Cytozyna', 'G': 'Guanina', 'T': 'Tymina'}
Ten sposób kopiowania ma ograniczenia, o czym będzie mowa w kolejnej lekcji.
Zbiory
Zbiory (ang. sets) są kolekcjami zawierającymi unikalne wartości. Przypominają zatem słowniki, ale z samymi kluczami. Podobnie też je tworzymy:
nukleotydy = {'A', 'C' , 'G', 'T'}
print(nukleotydy)
{'T', 'C', 'A', 'G'}
Pusty zbiór można utworzyć funkcją set()
:
zbior = set()
print(zbior)
print(type(zbior))
set()
<class 'set'>
Zastanów się, czemu utworzenie pustego zbioru z użyciem polecenia zbior = {}
nie przyniesie zamierzonego efektu.
Funkcja set()
umożliwia też utworzenie zbioru dzięki konwersji np. list, krotek, czy nawet łańcuchów znaków. Przy konwersji usuwane są zduplikowane elementy:
lista = [23, 22, 12, 3, 22]
zbior_1 = set(lista)
print(lista)
print(zbior_1)
lancuch = 'GTCCGATTACC'
zbior_2 = set(lancuch)
print(lancuch)
print(zbior_2)
[23, 22, 12, 3, 22]
{3, 12, 22, 23}
GTCCGATTACC
{'A', 'C', 'T', 'G'}
Zauważ, że jest to sposób uzyskania unikalnych wartości.
Inne operacje na zbiorach
Na zbiorach możemy też stosować takie funkcje i metody jak len()
, add()
, remove()
czy copy()
:
planety = {'Merkury', 'Wenus', 'Ziemia', 'Mars', 'Jowisz', 'Saturn', 'Uran', 'Neptun'}
print(f"Planety: {planety}")
print(f"Liczba planet: {len(planety)}")
planety.add('Pluton')
print(f"Planety: {planety}")
planety.remove('Pluton')
print(f"Planety: {planety}")
planety_2 = planety.copy()
print(f"Kopia: {planety_2}")
Planety: {'Jowisz', 'Saturn', 'Neptun', 'Mars', 'Ziemia', 'Wenus', 'Uran', 'Merkury'}
Liczba planet: 8
Planety: {'Jowisz', 'Saturn', 'Neptun', 'Mars', 'Ziemia', 'Pluton', 'Wenus', 'Uran', 'Merkury'}
Planety: {'Jowisz', 'Saturn', 'Neptun', 'Mars', 'Ziemia', 'Wenus', 'Uran', 'Merkury'}
Kopia: {'Jowisz', 'Saturn', 'Neptun', 'Mars', 'Ziemia', 'Wenus', 'Uran', 'Merkury'}
Zauważ, że podobnie jak w przypadku słowników, kolejność elementów w zbiorze nie musi odpowiadać kolejności dodawania.
Iteracja elementów też wygląda podobnie:
for planeta in planety:
print(planeta)
Merkury
Wenus
Ziemia
Neptun
Mars
Saturn
Uran
Jowisz
Tak można sprawdzić, czy dany element występuje w zbiorze:
if 'Pluton' in planety:
print('Pluton jest planetą!')
else:
print('Pluton nie jest planetą!')
Pluton nie jest planetą!
Niemodyfikowalne zbiory
Można też stworzyć zbiór, który już nie będzie mógł podlegać zmianom. Służy do tego funkcja frozenset()
:
planety = frozenset(['Merkury', 'Wenus', 'Ziemia', 'Mars', 'Jowisz', 'Saturn', 'Uran', 'Neptun'])
print(type(planety))
print(planety)
<class 'frozenset'>
frozenset({'Merkury', 'Wenus', 'Ziemia', 'Neptun', 'Mars', 'Saturn', 'Uran', 'Jowisz'})
Sprawdźmy, czy rzeczywiście zbioru nie można zmienić:
planety.add('Pluton')
----------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-136-b652a3251a9e> in <module>
----> 1 planety.add('Pluton')
AttributeError: 'frozenset' object has no attribute 'add'