11 - Złożone struktury danych
Poznane dotychczas struktury danych takie jak listy, krotki, słowniki i zbiory mogą zawierać nie tylko łańcuchy znaków czy liczby, ale także te wymienione struktury.
Tworzenie bardziej złożonej struktury danych
Utwórzmy zatem taką złożoną strukturę danych, zawierającą dane zasad azotowych. Umieścimy w niej, dla każdej zasady występującej w kwasach nukleinowych, informacje o jej pełnej, trójliterowej i jednoliterowej nazwie, typie zasady oraz jednoliterowych oznaczeń zasad, z którymi się dana zasada paruje (zasady komplementarne). Dane dla każdej zasady będą przechowywane w słownikach, w których kluczem będzie nazwa rodzaju informacji (pola) zapisana jako łańcuch znaków, a wartością będzie łańcuch znaku poza informacją dotyczącą komplementarnych zasad. Ponieważ w przypadku adeniny, w zależności od tego, czy mamy do czynienia z DNA lub RNA, są możliwe dwie zasady komplementarne, w tym ostatnim przypadku dane będziemy przechowywać w krotce. Wszystkie słowniki z danymi dla poszczególnych zasad, umieścimy w liście. Zatem otrzymamy listę, zawierającą słowniki, które zawierają łańcuchy znaków i krotki.
# Tworzymy kolekcję: lista zawierająca słowniki
# w słowniki znajdują się pary klucz-wartość
# Kluczami są łańcuchy znaków,
# wartościami są łańcuchy znaków lub krotki
zasady = [
{'pelna': 'adenina', '3_literowa': 'Ade',
'1_literowa': 'A', 'typ': 'purynowa', 'para': ('T', 'U')},
{'pelna': 'guanina', '3_literowa': 'Gua',
'1_literowa': 'G', 'typ': 'purynowa', 'para': ('C')},
{'pelna': 'cytozyna', '3_literowa': 'Cyt',
'1_literowa': 'C', 'typ': 'pirymidynowa', 'para': ('G')},
{'pelna': 'tymina', '3_literowa': 'Thy',
'1_literowa': 'T', 'typ': 'pirymidynowa', 'para': ('A')},
{'pelna': 'uracyl', '3_literowa': 'Ura',
'1_literowa': 'U', 'typ': 'pirymidynowa', 'para': ('A')},
]
Budowę struktury danych, którą utworzyliśmy, można przedstawić na schemacie:
Pobieranie danych ze złożonej struktury
Skoro skonstruowaliśmy tak złożoną strukturę danych, to czas ją wykorzystać. Dopiszmy kod, który pozwoli wypisać informację o każdej zasadzie z wprowadzonej przez użytkownika sekwencji.
# Pobranie sekwencji od użytkownika
sekwencja_1 = input("Podaj sekwencję: ")
# Pobieranie kolejnych liter z sekwencji
for litera in sekwencja_1:
# Pobieranie kolejnych słowników z danymi
for zasada in zasady:
# Jeśli wartość pola z nazwą 1-literową jest taka jak litera
if zasada['1_literowa'] == litera:
# Wydrukuj informacje
print(f"{zasada['1_literowa']} {zasada['3_literowa']} "+
f"{zasada['pelna']} zasada {zasada['typ']}")
Podaj sekwencję: GAACT
G Gua guanina zasada purynowa
A Ade adenina zasada purynowa
A Ade adenina zasada purynowa
C Cyt cytozyna zasada pirymidynowa
T Thy tymina zasada pirymidynowa
Za razie nie została użyta informacja o zasadach komplementarnych. Uzupełnijmy nasz program o sprawdzenie, czy druga, wpisana przez użytkownika sekwencja zawiera pasujące zasady. Program będzie wypisywał w kolejnych wierszach kolejne zasady, jeśli będą komplementarne, to umieści między nimi znak -
, jeśli nie, to między nimi pojawi się spacja.
Jeśli sekwencje będą się różniły długością, to będą one porównywane od pierwszej zasady aż do ostatniej w krótszej sekwencji. Pozostałe zasady w sekwencji dłuższej nie będą drukowane.
# Pobranie sekwencji od użytkownika
sekwencja_1 = input("Podaj pierwszą sekwencję: ")
# Pobranie drugiej sekwencji od użytkownika
sekwencja_2 = input("Podaj drugą sekwencję: ")
# Pobieramy długość obu sekwencji
dlugosc_1 = len(sekwencja_1)
dlugosc_2 = len(sekwencja_2)
# "licznik"
i = 0
# Pętla porównuje sekwencje do ostatniej zasady
# w krótszej sekwencji
while i < dlugosc_1 and i < dlugosc_2:
# Pobranie kolejnych liter z sekwencji
litera_1 = sekwencja_1[i]
litera_2 = sekwencja_2[i]
# Pobieranie kolejnych słowników z danymi
for zasada in zasady:
# Jeżeli 1-lierowa nazwa jest taka jak litera
if zasada['1_literowa'] == litera_1:
# Pobranie słownika z pasującymi danymi
dane = zasada
#Jeżeli litera z drugiej sekwencji znajduje się
# w krotce z zasadami komplementarnymi
if litera_2 in dane['para']:
# znak łączący zasady to -
komplementarne = '-'
else:
# znak łączący zasady to spacja
komplementarne = ' '
# Wydrukowanie zasad i łacznika
print(f"{litera_1}{komplementarne}{litera_2}")
# inkrementacja "licznika"
i += 1
Podaj pierwszą sekwencję: CATTAG
Podaj drugą sekwencję: GTATAC
C-G
A-T
T-A
T T
A A
G-C
Zobaczmy teraz jak można pobrać wybrane informacje z naszej struktury danych:
# Pobranie całego, pierwszego słownika
print(zasady[0])
# Pobranie wartości z pierwszego słownika dla klucza "pelna"
print(zasady[0]['pelna'])
# Pobranie drugiej wartości z krotki z pierwszego słownika dla klucza "para"
print(zasady[0]['para'][1])
{'pelna': 'adenina', '3_literowa': 'Ade', '1_literowa': 'A', 'typ': 'purynowa', 'para': ('T', 'U')}
adenina
U
Jak widać, zasada jest prosta: po nazwie struktury znajduje się jedna, lub więcej par nawiasów kwadratowych, w których odwołujemy się do kolejnych elementów zagnieżdżonych struktur.
Kopiowanie złożonych struktur danych
Utwórzmy teraz kolejną strukturę: w liście umieścimy słowniki z informacjami na temat kwasów nukleinowych:
kwasy = [
{'kwas': 'DNA', 'zasady': ('T', 'A', 'G', 'C')},
{'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')},
]
print(kwasy)
[{'kwas': 'DNA', 'zasady': ('T', 'A', 'G', 'C')}, {'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')}]
Spróbujmy teraz skopiować ją za pomocą poznanej wcześniej metody copy()
, a następnie zmieńmy jedną wartość w kopii i sprawdźmy zawartość obu list:
# Próba kopiowania
kwasy_kopia = kwasy.copy()
print("Przed zmianą:")
print(f"oryginał: {kwasy[1]}")
print(f"kopia: {kwasy_kopia[1]}")
# zmiana wartości
kwasy_kopia[1]['kwas'] = 'rna'
print("Po zmianie:")
print(f"oryginał: {kwasy[1]}")
print(f"kopia: {kwasy_kopia[1]}")
Przed zmianą:
oryginał: {'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')}
kopia: {'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')}
Po zmianie:
oryginał: {'kwas': 'rna', 'zasady': ('U', 'A', 'G', 'C')}
kopia: {'kwas': 'rna', 'zasady': ('U', 'A', 'G', 'C')}
Jak widać zmiana w “kopii” spowodowała również zmianę wartości w “oryginalnej” strukturze. Metoda copy()
nie sprawdza się w tak złożonych strukturach. W takich przypadkach najprościej jest zrobić to za pomocą metody deepcopy()
:
import copy
kwasy = [
{'kwas': 'DNA', 'zasady': ('T', 'A', 'G', 'C')},
{'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')},
]
# Próba kopiowania
kwasy_kopia = copy.deepcopy(kwasy)
print("Przed zmianą:")
print(f"oryginał: {kwasy[1]}")
print(f"kopia: {kwasy_kopia[1]}")
# zmiana wartości
kwasy_kopia[1]['kwas'] = 'rna'
print("Po zmianie:")
print(f"oryginał: {kwasy[1]}")
print(f"kopia: {kwasy_kopia[1]}")
Przed zmianą:
oryginał: {'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')}
kopia: {'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')}
Po zmianie:
oryginał: {'kwas': 'RNA', 'zasady': ('U', 'A', 'G', 'C')}
kopia: {'kwas': 'rna', 'zasady': ('U', 'A', 'G', 'C')}
Teraz mamy do czynienia z rzeczywistą kopią, zmiana w niej nie powoduje modyfikacji pierwotnej struktury. W powyższym kodzie zagadkowa pozostaje pierwsza linia zawierająca instrukcję import copy
. Dokładniej wyjaśnię jej sens w kolejnej lekcji, na razie wystarczy wiedzieć, że dzięki niej możemy używać obiektu copy
, który posiada wywoływaną metodę deepcopy()
.
Zadanie
Napisz program, który będzie zawierał zestaw danych dla kilku gatunków, na przykład: Zea mays, Triticum aestivum, Solanum tuberosum, Helianthus annuus, Coriandum sativum, Adenophora liliifolia. Dla każdego gatunku powinny być przechowywane przynajmniej następujące dane:
- Etykieta gatunkowa w języku polskim i łacińskim bez liter autora nazwy
- Nazwa rodzajowa w języku polskim i łacińskim
- Nazwa rodziny w języku polskim
- Nazwa rzędu w języku polskim
- Adres strony internetowej (URL) w Wikipedii z opisem danego gatunku
Można użyć informacji z Wikipedii.
Program powinien umożliwić użytkownikowi proste wyszukiwanie danych. Program powinien pytać użytkownika o ciąg znaków a następnie wyszukać podany ciąg znaków we wszystkich danych. Jeśli dane zostaną znalezione, powinna zostać wypisana informacja o znalezieniu (nazwa pola i jego wartość) a także wszystkie dane gatunku, w którym ciąg został znaleziony.
Dla uproszczenia, jeśli dany ciąg znajduje się w kilku polach to kilka razy wypisywane są dane dla gatunku.
Program powinien działać w pętli, użytkownik może wyszukiwać kolejne dane tak długo aż zamiast wpisania ciągu znaków, wciśnie klawisz Enter.
Wyszukiwanie powinno być niewrażliwe na wielkość znaków.
Przykładowa sesja użytkownika:
Wpisz szukane słowo, lub jego część. Jeśli chcesz zakończyć wciśnij Enter
szukaj: kuku
-----------------------------------
Szukane: kuku
Znaleziono: rodzaj_pl - Kukurydza
Nazwa polska: Kukurydza zwyczajna
Nazwa łacińska: Zea mays
Rodzina: wiechlinowate
Rząd: wiechlinowce
URL: https://pl.wikipedia.org/wiki/Kukurydza_zwyczajna
Znaleziono: url - https://pl.wikipedia.org/wiki/Kukurydza_zwyczajna
Nazwa polska: Kukurydza zwyczajna
Nazwa łacińska: Zea mays
Rodzina: wiechlinowate
Rząd: wiechlinowce
URL: https://pl.wikipedia.org/wiki/Kukurydza_zwyczajna
Wpisz szukane słowo, lub jego część. Jeśli chcesz zakończyć wciśnij Enter
szukaj: astr
-----------------------------------
Szukane: astr
Znaleziono: rodzina - astrowate
Nazwa polska: Słonecznik zwyczajny
Nazwa łacińska: Helianthus annuus
Rodzina: astrowate
Rząd: astrowce
URL: https://pl.wikipedia.org/wiki/S%C5%82onecznik_zwyczajny
Znaleziono: rzad - astrowce
Nazwa polska: Słonecznik zwyczajny
Nazwa łacińska: Helianthus annuus
Rodzina: astrowate
Rząd: astrowce
URL: https://pl.wikipedia.org/wiki/S%C5%82onecznik_zwyczajny
Znaleziono: rzad - astrowce
Nazwa polska: Dzwonecznik wonny
Nazwa łacińska: Adenophora liliifolia
Rodzina: dzwonkowate
Rząd: astrowce
URL: https://pl.wikipedia.org/wiki/Dzwonecznik_wonny
Wpisz szukane słowo, lub jego część. Jeśli chcesz zakończyć wciśnij Enter
szukaj:
-----------------------------------
KONIEC
Przykładowe rozwiązanie
# Dane
organizmy = [
{'rodzaj': 'Zea',
'gatunek': 'mays',
'rodzaj_pl': 'Kukurydza',
'gatunek_pl': 'zwyczajna',
'rodzina': 'wiechlinowate',
'rzad': 'wiechlinowce',
'url': 'https://pl.wikipedia.org/wiki/Kukurydza_zwyczajna'},
{'rodzaj': 'Triticum',
'gatunek': 'aestivum',
'rodzaj_pl': 'Pszenica',
'gatunek_pl': 'zwyczajna',
'rodzina': 'wiechlinowate',
'rzad': 'wiechlinowce',
'url': 'https://pl.wikipedia.org/wiki/Pszenica_zwyczajna'},
{'rodzaj': 'Solanum',
'gatunek': 'tuberosum',
'rodzaj_pl': 'Psianka',
'gatunek_pl': 'ziemniak',
'rodzina': 'psiankowate',
'rzad': 'psiankowce',
'url': 'https://pl.wikipedia.org/wiki/Ziemniak'},
{'rodzaj': 'Helianthus',
'gatunek': 'annuus',
'rodzaj_pl': 'Słonecznik',
'gatunek_pl': 'zwyczajny',
'rodzina': 'astrowate',
'rzad': 'astrowce',
'url': 'https://pl.wikipedia.org/wiki/S%C5%82onecznik_zwyczajny'},
{'rodzaj': 'Coriandrum',
'gatunek': 'sativum',
'rodzaj_pl': 'Kolendra',
'gatunek_pl': 'siewna',
'rodzina': 'selerowate',
'rzad': 'selerowce',
'url': 'https://pl.wikipedia.org/wiki/Kolendra_siewna'},
{'rodzaj': 'Adenophora',
'gatunek': 'liliifolia',
'rodzaj_pl': 'Dzwonecznik',
'gatunek_pl': 'wonny',
'rodzina': 'dzwonkowate',
'rzad': 'astrowce',
'url': 'https://pl.wikipedia.org/wiki/Dzwonecznik_wonny'},
]
while True:
print('Wpisz szukane słowo, lub jego część. Jeśli chcesz zakończyć wciśnij Enter')
# Pobieramy szukany ciąg, obcinamy białe znaki na poczatku i końcu
szukane = input('szukaj: ').strip()
print('-----------------------------------')
# Jeśli uzytkownik wcisnął Enter, ciąg znaków jest pusty
if szukane == '':
# Wyjście z pętli
break
print(f'Szukane: {szukane}')
# Sprawdzanie kolejnych słowników z danymi organizmów
for takson in organizmy:
# Kolejne pary klucz: wartość
for klucz, wartosc in takson.items():
# Sprawdzanie, czy wartość zawiera szukany ciąg znaków
# Wartość i szukany ciąg zmieniamy na małe litery
if wartosc.lower().count(szukane.lower()) >0 :
print(f'Znaleziono: {klucz} - {wartosc}')
print(f'Nazwa polska: {takson["rodzaj_pl"]} {takson["gatunek_pl"]}\n'
f'Nazwa łacińska: {takson["rodzaj"]} {takson["gatunek"]}\n'
f'Rodzina: {takson["rodzina"]}\n'
f'Rząd: {takson["rzad"]}\n'
f'URL: {takson["url"]}\n')
print('KONIEC')