Python - wprowadzenie

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:

Budowa struktury

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')
Last updated on 25 Nov 2020
Published on 25 Nov 2020