08. MODUL · PYTHON

Szótárak.

Kulcs–érték párok

1. fejezet

Adat név szerint: a szótár

A listában az elemeket sorszámuk (indexük) alapján érjük el. Sokszor azonban természetesebb egy név szerint keresni: egy diák jegye a diák neve alapján, egy termék ára a termék neve alapján, egy szó jelentése a szó alapján. Erre való a szótár.

Szótár (dict)

A szótár kulcs–érték párokat tárol. Minden kulcshoz pontosan egy érték tartozik, és a kulcsok egyediek. Az értéket a kulcs alapján érjük el: arak["alma"]. Kulcs csak megváltoztathatatlan érték lehet (szöveg, szám, megváltoztathatatlan elemekből álló tuple), érték bármi.

diak = {
    "nev": "Kiss Anna",
    "kor": 17,
    "varos": "Pécs",
}
print(diak["nev"])
print(diak["kor"] + 1)
print(len(diak))
print(diak)

Kövesd lépésenként: a Memóriában a szótár egy kétoszlopos táblázatként jelenik meg, bal oldalon a kulcsokkal, jobb oldalon az értékekkel. A szótár megőrzi a beszúrás sorrendjét, de indexe nincs: a diak[0] nem az első elemet jelenti, hanem a 0 kulcsot keresi.

2. fejezet

Lekérés: [], get() és in

Nem létező kulcsra a szotar[kulcs] KeyError-t ad. Ha nem vagyunk biztosak benne, hogy a kulcs létezik, két biztonságos út van:

  • kulcs in szotar – megvizsgálja, hogy a kulcs benne van-e (a kulcsok között keres, nem az értékek között);
  • szotar.get(kulcs) – az értéket adja, nem létező kulcsnál pedig None-t; szotar.get(kulcs, alap) ilyenkor az alap értéket.
arak = {"alma": 450, "körte": 620, "banán": 590}
termek = input("Melyik termék ára érdekel? ")
if termek in arak:
    print(f"A(z) {termek} ára {arak[termek]} Ft/kg.")
else:
    print("Ilyen termékünk nincs.")
print("Szilva:", arak.get("szilva"))
print("Szilva:", arak.get("szilva", "nincs készleten"))
arak = {"alma": 450, "körte": 620}
print(arak["szilva"])

Mit ír ki?

d = {"a": 1, "b": 2}
print("a" in d, 1 in d, d.get("c", 0))
  1. True True 0
  2. True False 0
  3. True False None

Az in a kulcsok között keres: az "a" kulcs, az 1 csak érték, ezért False. A get("c", 0) a nem létező kulcsra a megadott alapértéket, a 0-t adja.

3. fejezet

Hozzáadás, módosítás, törlés

MűveletHatás
d[kulcs] = ertekha a kulcs új: hozzáadja; ha már létezik: felülírja az értékét
del d[kulcs]törli a párt (nem létező kulcsnál KeyError)
d.pop(kulcs)törli és visszaadja az értéket; d.pop(kulcs, alap) nem ad hibát
d.update(masik)átveszi a másik szótár összes párját (a meglévőket felülírja)
d.clear()minden párt töröl
keszlet = {}
keszlet["alma"] = 120
keszlet["körte"] = 40
keszlet["alma"] = keszlet["alma"] - 20
keszlet.update({"szilva": 75, "körte": 55})
eladva = keszlet.pop("szilva")
print("Szilvát vittek:", eladva)
print(keszlet)

Az értékadás nem ellenőrzi, hogy a kulcs létezik-e: ha elírod (keszlet["Alma"] = 50), egy új kulcs jön létre, és a régi "alma" változatlan marad. A kulcsoknál a kis- és nagybetű is számít.

Mit ír ki?

d = {}
for betu in "anna":
    d[betu] = d.get(betu, 0) + 1
d["a"] = 5
print(d)
  1. {'a': 5, 'n': 2}
  2. {'a': 2, 'n': 2}
  3. {'n': 2, 'a': 5}

A ciklus után a szótár {'a': 2, 'n': 2}. A d["a"] = 5 létező kulcsra mutat, ezért nem új párt hoz létre, hanem felülírja az értéket – a kulcs a helyén marad, így a sorrend is (a szótár a kulcsok első beszúrásának sorrendjét őrzi).

4. fejezet

Bejárás: keys(), values(), items()

A for k in d a kulcsokon megy végig. Ha az értékek kellenek, a d.values()-n, ha mindkettő, a d.items()-en – ez utóbbi párokat (tuple-öket) ad, amelyeket a ciklus fejében rögtön kicsomagolunk.

jegyek = {"Anna": 5, "Bence": 3, "Csilla": 4}
for nev in jegyek:
    print(nev, end=" ")
print()
print(list(jegyek.keys()))
print(list(jegyek.values()), "átlag:", sum(jegyek.values()) / len(jegyek))
for nev, jegy in jegyek.items():
    print(f"{nev}: {jegy}")
for nev in sorted(jegyek):
    print(nev, jegyek[nev])

Bejárás közben ne adj hozzá és ne törölj kulcsot ugyanabból a szótárból – RuntimeError-t kapsz. Ha törölni kell, gyűjtsd ki előbb egy listába a törlendő kulcsokat, és utána töröld őket. A meglévő kulcsok értékét viszont bejárás közben is szabad módosítani.

5. fejezet

A legfontosabb minta: számlálás szótárral

Hányszor fordul elő egy-egy szó a szövegben? Hány tanuló kapott egyes, kettes, … ötös jegyet? A szótár kulcsai a „kategóriák”, az értékei a darabszámok. Minden elemnél eggyel növeljük a hozzá tartozó számlálót – ha a kulcs még nincs a szótárban, 0-ról indulva:

szoveg = input("Szöveg: ")
gyakorisag = {}
for szo in szoveg.lower().split():
    gyakorisag[szo] = gyakorisag.get(szo, 0) + 1
print(gyakorisag)

leggyakoribb = None
for szo, db in gyakorisag.items():
    if leggyakoribb is None or db > gyakorisag[leggyakoribb]:
        leggyakoribb = szo
print(f"Leggyakoribb: „{leggyakoribb}” ({gyakorisag[leggyakoribb]}×)")

A kulcssor a gyakorisag.get(szo, 0) + 1: ha a szó már szerepelt, a régi számlálóhoz ad egyet; ha nem, a 0 alapértékhez – így az első előfordulásnál 1 lesz. Kövesd lépésenként, és figyeld, ahogy a szótár sorai megjelennek és nőnek. A leggyakoribb szó keresése a 3. modul szélsőérték-mintája, csak most a szótár párjain. A leggyakoribb szó keresésénél a leggyakoribb is None feltétel az első kört ismeri fel: az is azt vizsgálja, hogy a két oldal ugyanaz az objektum-e – a None-ra mindig így kérdezünk rá.

A collections modul Counter osztálya pontosan ezt a számlálást végzi el egy sorban (Counter(szavak).most_common(3)). A kézi változatot azért érdemes ismerni, mert a minta sok más feladatban is visszatér (összegzés kategóriánként, csoportosítás).

6. fejezet

Összetett adatok: szótárban lista, listában szótár

Az érték bármi lehet – lista vagy újabb szótár is. Így írhatók le a valódi, többszintű adatok. Az elérés láncolt indexeléssel történik, kívülről befelé.

naplo = {
    "Anna": {"matematika": [5, 4, 5], "magyar": [4, 4]},
    "Bence": {"matematika": [3, 2], "magyar": [5, 4, 5]},
}
print(naplo["Anna"]["matematika"])
print(naplo["Anna"]["matematika"][-1])
naplo["Bence"]["matematika"].append(4)

for nev, tantargyak in naplo.items():
    for targy, jegyek in tantargyak.items():
        atlag = sum(jegyek) / len(jegyek)
        print(f"{nev:7} {targy:11} {jegyek}  átlag: {atlag:.2f}")

A lépésenkénti nézetben jól látszik a szerkezet: a naplo szótár értékei újabb szótárakra mutatnak, azok értékei pedig listákra.

Listában szótárak: rekordok

Táblázatszerű adatokat (sorok, mindegyikben ugyanazok a mezők) gyakran szótárak listájaként tárolunk – minden szótár egy „rekord”:

termekek = [
    {"nev": "laptop", "ar": 250000, "keszlet": 5},
    {"nev": "egér", "ar": 5000, "keszlet": 0},
    {"nev": "monitor", "ar": 65000, "keszlet": 12},
]
osszertek = 0
for t in termekek:
    if t["keszlet"] == 0:
        print(f"Elfogyott: {t['nev']}")
    osszertek += t["ar"] * t["keszlet"]
print(f"A raktár értéke: {osszertek} Ft")

Az f-stringben a szótárkulcs idézőjele legyen más, mint az f-string idézőjele: f"{t['nev']}". (Python 3.12 óta az azonos idézőjel is működik, de a régebbi verziók – és sok olvasó – számára ez zavaró.)

Másolás többszintű adatnál

A copy() a szótárnál is sekély másolatot készít: új külső szótár jön létre, de az értékként tárolt listák közösek maradnak. Kövesd lépésenként, és figyeld a Memóriában a nyilakat:

jegyek = {"Anna": [5, 4], "Bence": [3]}
masolat = jegyek.copy()
masolat["Cili"] = [5]          # új kulcs: csak a másolatban
masolat["Anna"].append(2)      # a közös listát módosítja!
print(jegyek)
print(masolat)

Az új kulcs csak a másolatba került, Anna új jegye viszont mindkét szótárban megjelent, mert a két "Anna" kulcs ugyanarra a listára mutat. Ha a belső listákat is le kell másolni, arra a copy modul deepcopy() függvénye való (mély másolat).

7. fejezet

Gyakorló feladatok

Telefonkönyv

A program elején adott egy telefonkönyv szótárként. Kérj be egy nevet, és írd ki a hozzá tartozó számot – vagy azt, hogy nincs ilyen név.

Név: Bence
Bence száma: 30/234-5678

Ismeretlen névnél: Nincs ilyen név a telefonkönyvben.

telefonkonyv = {"Anna": "20/123-4567", "Bence": "30/234-5678", "Csilla": "70/345-6789"}
telefonkonyv = {"Anna": "20/123-4567", "Bence": "30/234-5678", "Csilla": "70/345-6789"}
nev = input("Név: ")
if nev in telefonkonyv:
    print(f"{nev} száma: {telefonkonyv[nev]}")
else:
    print("Nincs ilyen név a telefonkönyvben.")
Tipp

if nev in telefonkonyv: – az in a kulcsok között keres.

Betűgyakoriság

Kérj be egy szöveget, és számold meg szótárral, melyik betű hányszor fordul elő (a szóközt hagyd ki, kis- és nagybetűt ne különböztesd meg). Az eredményt rendezve (sorted()) írd ki, soronként betű: db alakban.

Szöveg: alma a fa alatt
a: 5
f: 1
l: 2
m: 1
t: 2
szoveg = input("Szöveg: ").lower()
gyakorisag = {}
for betu in szoveg:
    if betu != " ":
        gyakorisag[betu] = gyakorisag.get(betu, 0) + 1
for betu in sorted(gyakorisag):
    print(f"{betu}: {gyakorisag[betu]}")
Tipp

Számlálás szótárral: gy[betu] = gy.get(betu, 0) + 1. Rendezve: for betu in sorted(gy):.

Kosár összege

Adott egy árlista. Olvass be termékneveket üres sorig (ugyanaz többször is szerepelhet). Az ismeretlen termékre írd ki: Nincs ilyen termék: dinnye, és hagyd ki. A végén írd ki a kosár végösszegét.

Termék: alma
Termék: alma
Termék: kenyér
Termék: dinnye
Nincs ilyen termék: dinnye
Termék: tej
Termék: 
Fizetendő: 2069 Ft
arak = {"alma": 450, "kenyér": 690, "tej": 479, "sajt": 1290}
arak = {"alma": 450, "kenyér": 690, "tej": 479, "sajt": 1290}
osszeg = 0
termek = input("Termék: ")
while termek != "":
    if termek in arak:
        osszeg += arak[termek]
    else:
        print("Nincs ilyen termék:", termek)
    termek = input("Termék: ")
print(f"Fizetendő: {osszeg} Ft")
Tipp

Végjelig olvasás üres szöveggel; a cikluson belül if termek in arak: döntsön az összeadásról.

Szótár megfordítása

Írj megfordit(szotar) függvényt, amely egy új szótárat ad vissza, amelyben a kulcsok és az értékek helyet cserélnek (feltételezheted, hogy minden érték különböző). Az eredeti szótár ne változzon.

megfordit({"egy": 1, "kettő": 2})   →   {1: 'egy', 2: 'kettő'}
def megfordit(szotar):
    pass

print(megfordit({"egy": 1, "kettő": 2}))
def megfordit(szotar):
    uj = {}
    for kulcs, ertek in szotar.items():
        uj[ertek] = kulcs
    return uj

print(megfordit({"egy": 1, "kettő": 2}))
Tipp

Új, üres szótárból indulj, járd be az eredetit items()-szel, és minden párt fordítva tegyél bele: uj[ertek] = kulcs.

Jegyeloszlás

Olvass be egy sornyi érdemjegyet (1–5). Számold meg szótárral, melyik jegyből hány van, és írd ki 5-östől 1-esig az összes jegyet (azokat is, amelyekből egy sincs), majd a leggyakoribb jegyet. Holtversenyben a jobb jegy nyerjen.

Jegyek: 5 4 5 3 4 5 2
5: 3 db
4: 2 db
3: 1 db
2: 1 db
1: 0 db
Leggyakoribb: 5
db = {}
for d in input("Jegyek: ").split():
    jegy = int(d)
    db[jegy] = db.get(jegy, 0) + 1
leggyakoribb = 5
for jegy in range(5, 0, -1):
    print(f"{jegy}: {db.get(jegy, 0)} db")
    if db.get(jegy, 0) > db.get(leggyakoribb, 0):
        leggyakoribb = jegy
print("Leggyakoribb:", leggyakoribb)
Tipp

A hiányzó jegyeknél a db.get(jegy, 0) ad 0-t. Ha 5-től lefelé haladsz, és csak akkor cserélsz, ha szigorúan nagyobb darabszámot találsz, holtversenyben automatikusan a jobb jegy marad.

Osztálynapló átlagai

Írj atlagok(naplo) függvényt. A naplo egy szótár, amelyben a tanulók nevéhez a jegyeik listája tartozik. A függvény adjon vissza egy új szótárat a tanulók nevével és a jegyeik átlagával, két tizedesjegyre kerekítve (round(x, 2)). Akinek nincs jegye, annak az átlaga None legyen.

atlagok({"Anna": [5, 4, 5], "Bence": [3, 2], "Csaba": []})
→ {'Anna': 4.67, 'Bence': 2.5, 'Csaba': None}
def atlagok(naplo):
    pass

print(atlagok({"Anna": [5, 4, 5], "Bence": [3, 2], "Csaba": []}))
def atlagok(naplo):
    eredmeny = {}
    for nev, jegyek in naplo.items():
        if jegyek:
            eredmeny[nev] = round(sum(jegyek) / len(jegyek), 2)
        else:
            eredmeny[nev] = None
    return eredmeny

print(atlagok({"Anna": [5, 4, 5], "Bence": [3, 2], "Csaba": []}))
Tipp

Járd be a naplót items()-szel; az üres lista hamis, így if jegyek: eldönti, van-e jegy.

8. fejezet

Összefoglalás

EszközLényeg
{"kulcs": ertek}szótár; a kulcsok egyediek, megváltoztathatatlanok
d[k], d.get(k, alap)lekérés; nem létező kulcsnál KeyError / az alapérték
k in da kulcsok között keres
d[k] = v, del d[k], pop, updatehozzáadás vagy felülírás, törlés
keys(), values(), items()bejárás: for k, v in d.items():
d[k] = d.get(k, 0) + 1számlálás szótárral

Gyakorold tovább: készíts egy egyszerű szókártya-játékot: egy magyar–angol szótárból a program véletlen sorrendben kérdezze ki a szavakat (random.shuffle a kulcsok listáján), számolja a jó válaszokat, a hibásakat pedig a végén ismételtesse meg.