Sanakirja

Tarkastellaan esimerkkiä, jossa haluamme toteuttaa puhelinluettelon. Luettelo koostuu pareista, jotka sisältävät henkilön nimen ja hänen puhelinnumeronsa. Haluamme, että rakenteesta pystyy helposti etsimään halutun henkilön puhelinnumeron, siihen pystyy lisäämään uusia puhelinnumeroita ja poistamaan vanhentuneita puhelinnumeroita. Yksinkertaisuuden vuoksi oletamme, että luettelossamme ei ole useita samannimisiä henkilöitä ja että jokaisella henkilöllä on vain yksi puhelinnumero.

Kuvattu rakenne olisi mahdollista toteuttaa listojen avulla. Kunkin henkilön nimi ja puhelinnumero muodostaisi yhden listan, ja itse puhelinluettelo olisi lista, jonka alkiot olisivat nimi–puhelinnumero-listoja. Tämän rakenteen ongelma on kuitenkin se, että halutun henkilön puhelinnumeron haku siitä olisi hidasta. Jos haluaisimme etsiä Matti Meikäläisen puhelinnumeron, ohjelman pitäisi käydä koko puhelinluettelolistaa läpi niin kauan, että se löytäisi nimen Matti Meikäläinen. Pahimmillaan koko puhelinluettelo pitäisi käydä läpi. Jos nimet olisi tallennettu puhelinluetteloon aakkosjärjestyksessä, hakua voitaisiin nopeuttaa selvästi käyttämällä binäärihakua, mutta tällöin taas uusien nimien lisääminen puhelinluetteloon aakkkosjärjestyksen mukaisille paikoilleen olisi hidasta (kaikkia lisättävän nimen jälkeen tulevia alkioita pitäisi siirtää listassa loppuun päin).

Python tarjoaa toisen rakenteen, jossa on mahdollista toteuttaa tehokkaasti alkion haku avaimen perusteella (puhelinluetteloesimerkissä henkilön nimi toimii avaimena) sekä uusien alkioiden haku ja vanhojen poisto. Tätä rakennetta kutsutaan sanakirjaksi (engl. dictionary). Sanakirjan tehokas toteuttaminen perustuu hajautusrakenteeseen, jota käsitellään tarkemmin Tietorakenteet ja algoritmit -kurssilla.

Tyhjän sanakirjan voi luoda aaltosulkujen {} avulla, esimerkiksi

puh_luettelo = {}

Sanakirjaa luodessa voi myös antaa samalla jo sanakirjaan lisättäviä avain–arvo-pareja. (Puhelinluetteloesimerkissä avain on nimi ja arvo nimeen liittyvä puhelinnumero). Avain ja arvo erotetaan toisistaan kaksoispisteellä ja eri parit toisistaan pilkulla:

puhelinluettelo = {"Teekkari Teemu" : "050-12345", "Fyysikko Tiina" : \
"045-234567", "Kemisti Kalle" : "040-765432"}

luo uuden sanakirjan, joka sisältää kolme nimi–puhelinnumero-paria.

Johonkin avaimeen liittyvän arvon saa selville ilmauksella sanakirja[avain], esimerkiksi

print(puhelinluettelo["Fyysikko Tiina"])

tulostaa

045-234567

Sanakirjan on kuitenkin tällöin sisällettävä hakasulkujen sisällä annettu avain. Muuten aiheutuu virhetilanne, esimerkiksi käskyn

print(puhelinluettelo["Virtanen Maija"])

suorittaminen edellä luodulle sanakirjalle saa aikaan ohjelman kaatumisen ja tulostuksen

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Virtanen Maija'

Operaattorin in avulla voidaan kuitenkin tutkia ensin, onko avain sanakirjassa. Jos muuutuja puhelinluettelo viittaa edellä luotuun sanakirjaan, niin rivien

nimi = "Virtanen Maija"
if nimi in puhelinluettelo:
    print(puhelinluettelo[nimi])
else:
    print("Nimea ei loydy luettelosta")

suoritus saa aikaan tulostuksen

Nimea ei loydy luettelosta

Rivien

nimi = "Teekkari Teemu"
if nimi in puhelinluettelo:
    print(puhelinluettelo[nimi])
else:
    print("Nimea ei loydy luettelosta")

Suoritus puolestaan aiheuttaa tulostuksen

050-12345

Sanakirjaan voidaan lisätä sijoituskäskyn avulla uusia avain–arvo-pareja sekä muuttaa aikaisemmin lisättyihin avaimiin liittyviä arvoja. Jos esimerkiksi suoritetaan seuraavat käskyt, kun muuttuja puhelinluettelo viittaa edellä luotuun sanakirjaan, rivien

puhelinluettelo["Rakentaja Niina"] = "0400-123"
puhelinluettelo["Kemisti Kalle"] = "041-56789"
print(puhelinluettelo)

Tulostus on

{'Teekkari Teemu': '050-12345', 'Fyysikko Tiina': '045-234567', 'Kemisti Kalle':
'041-56789', 'Rakentaja Niina': '0400-123'}

Kaikki sanakirjaan kuuluvat avaimet voi käydä läpi for-käskyn avulla samaan tapaan kuin listan alkiot:

for nimi in puhelinluettelo:
    print(nimi)

tulostaa

Teekkari Teemu
Fyysikko Tiina
Kemisti Kalle
Rakentaja Niina

ja

for nimi in puhelinluettelo:
    print(f"{nimi:16s} {puhelinluettelo[nimi]:12s}")

tulostaa

Teekkari Teemu   050-12345
Fyysikko Tiina   045-234567
Kemisti Kalle    041-56789
Rakentaja Niina  0400-123

Sanakirjasta voi poistaa jonkin avaimen ja siihen liittyvän arvon del-operaattorilla:

del puhelinluettelo["Kemisti Kalle"]
print(puhelinluettelo)

Tulostaa

{'Teekkari Teemu': '050-12345', 'Fyysikko Tiina': '045-234567',
'Rakentaja Niina': '0400-123'}

Pythonin versiosta 3.7 lähtien sanakirjoissa säilyy se järjestys, jossa avaimet on lisätty sanakirjaan. Tätä vanhemmissa Python-versioissa avain-arvo-pareja säilytetään sanakirjassa satunnaisessa järjestyksessä. Kun esimerkiksi käydään läpi toistokäskyssä kaikki sanakirjan avaimet yllä esitettyjen esimerkkien mukaisesti, avaimet ja niihin liittyvät arvot voidaan tulostaa täysin eri järjestyksessä kuin mitä ne on alunperin lisätty sanakirjaan. Jos on mahdollisuus siihen, että ohjelmaa ajetaan Pythonin vanhemmilla versioilla, pitää ohjelma kirjoittaa niin, että siinä ei missään vaiheessa oleteta avainten olevan sanakirjassa jossain määrätyssä järjestyksessä.

Lopuksi esitetään esimerkkinä sanakirjoista yksinkertainen puhelinluettelo-ohjelma, joka lukee ensin käyttäjältä joukon nimiä ja puhelinnumeroita. Se tallentaa ne sanakirjarakenteeseen. Tämän jälkeen ohjelma esittää käyttäjälle valikon, josta käyttäjä voi valita haluamansa toimenpiteen (puhelinnumeron etsimisen, puhelinnumeron muuttamisen tai ohjelman lopettamisen). Ohjelma suorittaa käyttäjän valitseman toimenpiteen ja esittää valikon sen jälkeen käyttäjälle aina uudelleen niin kauan, että käyttäjä valitse ohjelman lopettamisen.

Tällaisessa puhelinluettelo-ohjelmassa ei ole vielä paljon järkeä, koska käyttäjä joutuu itse antamaan kaikki luetteloon lisättävät nimet ja numerot ohjelman jokaisella suorituskerralla. Ohjelmaa on kuitenkin helppo muuttaa niin, että nimet ja puhelinnumerot luetaankin tiedostosta.

Ohjelma on alla useassa osassa, jotta olisi mahdollisuus nähdä helpommin koodirivi ja sitä koskeva selitys yhtä aikaa.

# Funktio lukee kayttajalta nimia ja puhelinnumeroita.
# Se luo uuden sanakirjarakenteen ja tallentaa siihen
# luetut nimet ja puhelinnumerot. Funktio palauttaa
# taman sanakirjarakenteen.

def lue_puhelinnumerot():
    print("Anna lisattavat nimet ja numerot.")
    print("Nimi ja puhelinnumero samalla rivilla,")
    print("valissa kaksoispiste.")
    print("Lopeta tyhjalla rivilla.")
    puhelinluettelo = {}
    rivi = input()
    while len(rivi) > 0:
        tiedot = rivi.split(":")
        nimi = tiedot[0]
        numero = tiedot[1]
        puhelinluettelo[nimi] = numero
        rivi = input()
    return puhelinluettelo
Luodaan tyhjä sanakirja nimiä ja puhelinnumeroita varten.
Jaetaan käyttäjältä luettu rivi kahteen osaan. Lisätään sanakirjaan uusi pari, jossa avaimena on rivin ensimmäinen osa (nimi) ja arvona rivin toinen osa (puhelinnumero).
Kun käyttäjä on lopettanut antamalla tyhjän rivin, palautetaan sanakirjarakenne, johon nimi - puhelinnumero -parit on kerätty.
# Funktio saa parametrina puhelinluettelon sisaltavan
# sanakirjarakenteen. Se pyytaa kayttajalta yhden nimen
# ja etsii puhelinluettelosta sita vastaavan puhelinnumeron.

def etsi_numero(puhelintiedot):
    etsitty = input("Kenen numero haetaan? ")
    if etsitty in puhelintiedot:
        print("Numero on", puhelintiedot[etsitty])
    else:
        print("Nimea ei loydy luettelosta.")
Tutkitaan, onko käyttäjän antama nimi parametrina saadussa sanakirjarakenteessa.
Jos nimi (muuttujassa etsitty) kuuluu sanakirjan avaimiin, siihen liittyvä puhelinnumero saadaan näin.
# Funktio saa parametrina puhelinluettelon sisaltavan
# sanakirjarakenteen. Se pyytaa kayttajalta yhden nimen
# ja vaihtaa talle nimelle kayttajan antaman uuden
# puhelinnumeron.

def muuta_numero(numerotiedot):
    muutettava = input("Kenen numeron haluat muuttaa? ")
    if muutettava not in numerotiedot:
        print("Nimea ei loydy luettelosta.")
    else:
        uusi_numero = input("Anna taman henkilon uusi numero. ")
        numerotiedot[muutettava] = uusi_numero
Näin voidaan liittää uusi arvo sanakirjassa jo olevaan avaimeen. Avaimeen (nimeen) aikaisemmin liitetty arvo (puhelinnumero) häviää.
# Funktio tulostaa kayttajalle mahdolliset toiminnot
# ja lukee ja palauttaa hanen valitsemansa toiminnon
# numeron.

def pyyda_valinta():
    print()
    print("Valitse toiminto:")
    print("1. Hae puhelinnumero")
    print("2. Muuta puhelinnumero")
    print("3. Lopeta")
    valinta = int(input())
    return valinta


def main():
    luettelo = lue_puhelinnumerot()
    toiminto = pyyda_valinta()
    while toiminto != 3:
        if toiminto == 1:
            etsi_numero(luettelo)
        elif toiminto == 2:
            muuta_numero(luettelo)
        else:
            print("Vaara valinta!")
        toiminto = pyyda_valinta()
    print("Ohjelman suoritus paattyy.")

main()
Käyttäjälle tulostetaan valikko ja pyydetään häntä valitsemaan uusi toiminto. Pääohjelma kutsuu aina käyttäjän valitsemaa toimintoa vastaavaa funktiota niin kauan, että käyttäjä valitsee ohjelman lopettamisen.