Python-sanakirjan päivitys. Elementtien poistaminen Python-sanakirjasta kriteerien perusteella. Tietoihin pääsy avaimella

Sanakirja kielellä Python ohjelmointi kutsutaan järjestämättömäksi tietojoukoksi mielivaltainen tyyppi avaimella sisäänpääsyllä. Tällaisen kokoelman elementit ovat objektipareja, joista jokainen sisältää avaimen ja arvon. Sanakirjojen käyttöä varten on käytettävissä toimintoja, jotka muuttavat niiden sisältöä ja suorittavat niille erilaisia ​​toimintoja. Se voidaan muuntaa muiksi tietotyypeiksi, kuten merkkijonoksi.

Luominen

Ennen kuin alat työskennellä sanakirjan kanssa, sinun on luotava se. Tämä voidaan tehdä peruskielityökaluilla määrittämällä vapaa muuttuja mielivaltainen määrä esinepareja. Elementit on suljettava aaltosulkeilla, ja avaimen ja arvon välissä on oltava kaksoispiste. Seuraava esimerkki havainnollistaa sellaisen sanakirjan luomista, joka sisältää avaimet numeroina ja arvot merkkijonoina.

A = (1: "yksi", 2: "kaksi", 3: "kolme") tulosta(a) (1: "yksi", 2: "kaksi", 3: "kolme")

Voit näyttää sanakirjan sisällön vakiotoiminto tulosta, määrittämällä halutun tietojoukon argumenttina. Sanakirjan täyttämiseen käytetään myös sanelumenetelmää, vastaanottaa mielivaltaisen määrän avainarvopareja. Tässä tapauksessa vain merkkijono voi olla avain, kuten seuraavassa koodiesimerkissä näkyy.

A = dict(yksi = 1, kaksi = 2, kolme = 3) tulosta(a) ("yksi": 1, "kaksi": 2, "kolme": 3)

Kuten viimeksikin, tulostustoiminto näyttää sanakirjan sisällön a. Tässä tapauksessa on objektipareja, jotka esitetään myös numeroina ja merkkijonoina.

Elementin lisääminen

Python 3:ssa sanakirjan sisältöä voidaan muuttaa milloin tahansa tarpeidesi mukaan. Esimerkiksi, jotta voit lisätä uuden objektiparin kokoelmaan, sinun tarvitsee vain määrittää uusi avain hakasulkeissa sekä sitä vastaava arvo.

A = (1: "yksi", 2: "kaksi", 3: "kolme") a = "neljä" tulosta(a) (1: "yksi", 2: "kaksi", 3: "kolme", ​​4 : "neljä")

Yllä oleva koodi käyttää määritysoperaattoria uuden parin (4: "neljä") sijoittamiseen aiemmin luodun kokoelman loppuun a.

Sanakirjojen yhdistäminen

Jos tietoja on siirrettävä sanakirjasta toiseen, kannattaa käyttää päivityksen yhdistämistoimintoa. Sinun on kutsuttava sitä objektissa, jota on tarkoitus laajentaa uusilla avain-arvo-pareilla. Tässä on esimerkki sanakirjan lisäämisestä Pythonin sanakirjaan:

A = (1: "yksi", 2: "kaksi", 3: "kolme") b = (4: "neljä", 5: "viisi") a.päivitys(b) tulosta(a) (1: " yksi", 2: "kaksi", 3: "kolme", ​​4: "neljä", 5: "viisi")

Tulostusmenetelmän tuloksena näytetään sanakirjan päivitetty sisältö nimeltä a.

Yhdistyksen jälkeen uudet elementit kirjoitettiin automaattisesti kokoelman loppuun.

Elementin poistaminen

Jos sanakirja sisältää tarpeettomia tietoja, voit helposti päästä eroon siitä erityisellä del-toiminnolla. Suorittaaksesi sen, sinun on määritettävä kokoelman nimi sekä avain hakasulkeissa. Seuraava esimerkki näyttää parin poistamisen.

A = (1: "yksi", 2: "kaksi", 3: "kolme") del a print(a) (1: "yksi", 2: "kaksi")

Koska operaatio sai avaimen 3, sen toiminnan seurauksena arvo kolme myös poistettiin.

Koon saaminen

Toiminto len antaa sinun määrittää sanakirjaelementtien nykyisen määrän milloin tahansa, jos annat sen argumentiksi kokoelman nimen. Alla olevassa esimerkissä tulostusmenetelmä tulostaa sanakirjan mittasuhteen a.

A = (1: "yksi", 2: "kaksi", 3: "kolme") tulosta(len(a)) 3

Se kannattaa huomioida len-funktio palauttaa tarkan määrän pareja, mutta ei objekteja. Tässä tapauksessa on sanakirja, joka sisältää täsmälleen 3 paria.

Haku sanakirjasta

Voit luetella sanakirjan elementit useilla tavoilla riippuen siitä, mitä tietoja haluat saada sen sisällöstä.

Elementtien iterointi voidaan suorittaa saadakseen myöhempää käsittelyä varten:

  • Avain-arvo-parit;
  • Kaikkien avainten luettelointi;
  • Arvojen iterointia.

Tämä esimerkki näyttää, kuinka tämän kokoelman kaikki parit näytetään muodossa avain: arvo. Tätä varten käytämme for-silmukkaa ja items-toimintoa, joka toimii sanakirjaelementtien kanssa.

A = (1: "yksi", 2: "kaksi", 3: "kolme") avaimelle, arvo a.items(): print(avain, ":", arvo) 1: yksi 2: kaksi 3: kolme

Jos haluat saada vain avaimet, käytä avaimet-menetelmää kutsumalla sitä sanakirjaan.

A = (1: "yksi", 2: "kaksi", 3: "kolme") avaimelle a.keys(): print(avain) 1 2 3

Sinun on tehtävä sama, jos haluat näyttää vain sanakirjan arvot. Tässä tapauksessa for-silmukka käyttää kuitenkin arvomenetelmää.

A = (1: "yksi", 2: "kaksi", 3: "kolme") arvolle arvo a.values(): print(val) yksi kaksi kolme

Molemmissa tapauksissa vain parin valittu osa, avain tai arvo, näytetään.

Hae

Voit tarkistaa tietyn avaimen olemassaolon käyttämällä toimintoa. Tätä varten riittää, kun näytetään sen suorituksen tulos sanakirjalle nimeltä a.

A = (1: "yksi", 2: "kaksi", 3: "kolme") tulosta(2 in a) tulosta(4 in a) Tosi Väärin

Kuten näet, avaimen 2 tarkistaminen antoi positiivisen tuloksen (tosi). Toisessa tapauksessa arvo oli False, koska avainta 4 ei löytynyt sanakirjasta.

Lajittelu

Kielityökalujen avulla Pythonissa oleva sanakirja voidaan lajitella avaimien ja arvojen mukaan tarpeen mukaan. Seuraavassa esimerkissä on tietokokoelma nimeltä a, joka sisältää tietoja missään järjestyksessä. Näppäimet ovat tässä numeroita ja arvot merkkijonoja. Lajittelu tapahtuu tuodulla operaattorimoduulilla ja sisäänrakennetulla itemgetter-menetelmällä vastaanottaa 0 tai 1.

Tuontioperaattori a = (2: "kaksi", 3: "kolme", ​​1: "yksi") b = lajiteltu(a.kohteet(), avain = operaattori.tuotemerkki(0)) print(b) b = lajiteltu( a.items(), key = operator.itemgetter(1)) print(b) [(1, "yksi"), (2, "kaksi"), (3, "kolme")] [(1, "yksi" " "), (3, "kolme"), (2, "kaksi")]

Kuten näet, argumentilla 0 voit lajitella sanakirjan avaimen mukaan, kun taas 1:n avulla voit näyttää sen sisällön aakkosjärjestyksessä.

Vertailu

Joskus sinun on varmistettava, että kahdessa sanakirjassa on täsmälleen samat tiedot, tai selvittää, kumpi kokoelma on suurempi tai pienempi. Tässä tapauksessa cmp-menetelmä tulee apuun, vastaanottaen kaksi sanakirjaa parametreina.

A = (1: "yksi", 2: "kaksi", 3: "kolme") b = (4: "neljä", 5: "viisi") c = (1: "yksi", 2: "kaksi" , 3: "kolme") tulosta(cmp(a, b)) tulosta(cmp(b, c)) tulosta(cmp(a, c)) 1 -1 0

Seuraava koodi osoitti cmp-menetelmän suorittamisen kolmella argumenttiyhdistelmällä. Kuten tulostuloksista voidaan nähdä, funktio palauttaa arvon 1, jos ensimmäinen on suurempi kuin toinen, -1, jos päinvastoin ja 0, kun tiedot ovat täysin identtisiä.

Kopio

Kopiointimenetelmää käytetään sanakirjan sisällön kopioimiseen toiseen. Tämä esimerkki osoittaa avainten ja arvojen siirtämisen kokoelmasta a kokoelmaan b.

A = (1: "yksi", 2: "kaksi", 3: "kolme") b = a.copy() print(b) (1: "yksi", 2: "kaksi", 3: "kolme" )

Kuten näette, kaikkien parien järjestys ja sisältö on säilynyt uudessa sarjassa.

Puhdistus

Päästäksesi eroon kaikista sanakirjan elementeistä, sinun tulee kutsua sanakirjasta Clear-toiminto.

A = (1: "yksi", 2: "kaksi", 3: "kolme") a.clear() print(a) ()

Tuloksena on täysin tyhjä tietojoukko.

Sanakirjan generaattori

Kuten muutkin tietojoukot, Voit täyttää sanakirjoja käyttämällä. Seuraava esimerkki havainnollistaa kokoelman numeeristen parien luomista Python-sanakirjageneraattorilla ja aluemenetelmällä 5 argumenttina.

A = (a: a * a: a alueella(5)) tulosta(a) (0: 0, 1: 1, 2: 4, 3: 9, 4: 16)

Siten tulos on sanakirja a, joka sisältää täsmälleen 5 paria. Näppäimet ovat numeroita 0-4, ja arvot ovat niiden matemaattisia neliöitä.

Muunna merkkijonoksi

Sanakirja voidaan helposti muuntaa merkkijonoksi työskentelyn helpottamiseksi, jolloin sen sisältö on yhtenäinen. Tätä varten tarvitset funktion str. Kuten suoritustuloksista näkyy menetelmän tyyppi, muunnos onnistui.

A = (1: "yksi", 2: "kaksi", 3: "kolme") b = str(a) print(b) print(type(b)) (1: "yksi", 2: "kaksi" , 3: "kolme")

Käänteinen muunnos tapahtuu samalla tavalla Python-merkkijonot sanakirjaan. On tärkeää, että sen tekstisisältö vastaa kyseisen kokoelman rakennetta.

A = "(1: "yksi", 2: "kaksi", 3: "kolme")" b = eval(a) print(b) print(type(b)) (1: "yksi", 2: " kaksi", 3: "kolme")

Kuten esimerkistä näkyy, eval-menetelmä muuntaa koko merkkijonon tekstin uudeksi sanakirjaksi.

Sisäkkäinen

Pythonissa sanakirjat voivat olla sisäkkäisiä, eli ne voivat olla osa toista suurempaa sanakirjaa.. Tuttujen aaltosulkeiden ja kaksoispisteiden avulla voit rajata tämän tietojoukon rajat ja tarjota ohjelmalle avainarvo-pareja.

A = ( "Ensimmäinen": ( 1: "yksi", 2: "kaksi", 3: "kolme" ), "Toinen": ( 4: "neljä", 5: "viisi" ) ) tulosta(a) ( "Ensimmäinen": (1: "yksi", 2: "kaksi", 3: "kolme"), "Toinen": (4: "neljä", 5: "viisi"))

Yllä kuvatussa esimerkissä luodaan sanakirja a, joka sisältää kaksi muuta sanakirjaa (ensimmäinen ja toinen). Ne puolestaan ​​sisältävät useita avaimia ja arvoja.

Yhteenveto

Seuraava taulukko osoittaa lyhyt yhteenveto kaikista käsitellyistä menetelmistä työskennellä Python 3:n sanakirjojen kanssa. Taulukossa näkyvät menetelmien nimet sekä tiedot niiden tarkoituksesta.

Python-ohjelmointikielessä sanakirjat (tyyppiset dicts) ovat toinen tietorakennetyyppi luetteloiden ja monistojen ohella. Sanakirja on vaihdettavissa(kuten lista) häiriintynyt(toisin kuin merkkijonot, luettelot ja monikot) joukko avain:arvo-elementtejä.

"Järjestämätön" tarkoittaa, että parien järjestyksellä ei ole merkitystä. Ohjelmointikieli ei ota sitä huomioon, minkä seurauksena elementteihin pääsy indekseillä on mahdotonta.

Muissa kielissä sanakirjoja vastaavia rakenteita kutsutaan eri tavalla. Esimerkiksi Javassa tämän tyyppistä dataa kutsutaan kartoitukseksi.

Jotta sanakirjan idea olisi selkeämpi, piirretään analogia tavallisen sanakirjan, esimerkiksi englanti-venäläisen, kanssa. Jokaiselle englannin sanalle tällaisessa sanakirjassa on Venäjän sanan käännös: kissa - kissa, koira - koira, pöytä - pöytä jne. Jos Englanti-venäjä sanakirja kuvaile kanssa Pythonin avulla, niin englanninkielisistä sanoista voidaan tehdä avaimia ja venäläisistä sanoista niiden merkityksiä:

("kissa": "kissa", "koira": "koira", "lintu": "lintu", "hiiri": "hiiri")

Kiinnitä huomiota kihariin aaltosulkeisiin, joita käytetään sanakirjan määrittämiseen. Pythonin sanakirjan syntaksi kuvataan seuraavalla kaaviolla:

Usein sanakirjaa tulostettaessa avain:arvo-parien järjestys ei vastaa syötettyä:

>>> a = ( "kissa" : "kissa", "koira" : "koira", "lintu" : "lintu", "hiiri" : "hiiri") >>>a("koira": "koira", "kissa": "kissa", "lintu": "lintu", "hiiri": "hiiri")

Koska parien järjestyksellä sanakirjassa ei ole merkitystä, tulkki näyttää ne sopivalla tavalla. Miten sitten päästään käsiksi tiettyyn elementtiin, jos indeksointi ei ole periaatteessa mahdollista? Sanakirjassa arvoihin päästään avaimilla, jotka sisältyvät sanakirjaan hakasulkeet(samanlainen kuin luettelohakemistot):

>>> [ "kissa"]"kissa" >>> [ "lintu"]"lintu"

Sanakirjat, kuten luettelot, ovat muuttuvia tietotyyppejä: voit muuttaa, lisätä ja poistaa elementtejä (avain:arvo-pareja). Aluksi sanakirja voidaan luoda tyhjäksi (esimerkiksi d = ()) ja sitten täyttää elementeillä. Lisäämisellä ja muuttamisella on sama syntaksi: sanakirja[avain] = arvo. Avain voi olla joko olemassa oleva (silloin arvo muuttuu) tai uusi (sanakirjaelementti lisätään silloin). Elementin poistaminen tapahtuu Pythonin sisäänrakennetulla del-operaattorilla.

>>> a[ "elephant" ] = "virtahepo" # add >>> a[ "taulukko" ] = "taulukko" # lisäys >>>a("koira": "koira", "kissa": "kissa", "hiiri": "hiiri", "lintu": "lintu", "pöytä": "pöytä", "norsu": "virtahepo") >>> a[ "elephant" ] = "norsu" # vaihdettavissa >>> del a[ "taulukko" ] # poista >>>a("koira": "koira", "kissa": "kissa", "hiiri": "hiiri", "lintu": "lintu", "elefantti": "norsu")

Sanakirjassa ei voi olla kahta elementtiä samoilla avaimilla. Eri avaimilla voi kuitenkin olla samat arvot.

Avain voi olla mikä tahansa muuttuva tyyppi tiedot. Arvo – mikä tahansa tietotyyppi. Sanakirja-arvot voivat hyvinkin olla rakenteita, kuten muita sanakirjoja tai luetteloita.

>>> numerot = ( 1 : "yksi" , 2 : "kaksi" , 3 : "kolme") >>> henkilö = ( "nimi" : "Tom" , 1 : [ 30 , 15 , 16 ] , 2 : 2.34 , ( "ab" , 100 ) : "ei" )

Kierto sanakirjaelementtien läpi for-silmukassa

Sanakirjaelementit iteroidaan for-silmukassa kuten muutkin elementit. monimutkaisia ​​esineitä. Oletuksena kuitenkin vain avaimet haetaan:

>>> numeroita>>> i numeroissa: ...tulosta(i) ... 1 2 3

Mutta voit aina saada arvoja avaimista:

>>> i numeroissa: ... tulosta (numerot[ i] ) ... yksi kaksi kolme

Toisaalta sanakirjassa luokkana on items()-metodi, joka luo erityisen monikoista koostuvan rakenteen. Jokainen monikko sisältää avaimen ja arvon:

>>> n = numerot.kohteet() >>> n dict_items([(1, "yksi"), (2, "kaksi"), (3, "kolme")])

For-silmukassa voit purkaa monikot ja siten purkaa sekä avaimen että sen arvon kerralla:

>>> avaimelle, arvo numeroissa.kohteet () : ... tulosta (avain, "on" , arvo) ... 1 on yksi 2 on kaksi 3 on kolme

Sanakirjamenetelmien keys() ja values() avulla voit hankkia erilliset avaimet ja arvot. Joten jos esimerkiksi haluat iteroida vain arvoja tai vain avaimia, on parempi käyttää jotakin seuraavista tavoista:

>>> v_nums = >>> v:lle numeroissa.arvot() : ...v_nums.append(v) ... >>> v_nums["yksi kaksi kolme"]

Sanakirjamenetelmät

Edellä käsiteltyjen kolmen menetelmän items(), keys() ja value() lisäksi sanakirjoissa on kahdeksan muuta. Nämä ovat menetelmät clear(), kopioi(), fromkeys(), get(), pop(), popitem(), setdefault(), update().

Clear()-menetelmä poistaa sanakirjan kaikki elementit, mutta ei itse sanakirjaa. Tämän seurauksena tyhjä sanakirja jää jäljelle:

>>>a("koira": "koira", "kissa": "kissa", "hiiri": "hiiri", "lintu": "lintu", "elefantti": "norsu") >>> a.clear() >>>a {}

Sanakirja on muuttuva tietotyyppi. Siksi se, kuten luettelo, välitetään funktiolle viittauksella. Siksi joskus, jotta vältetään ei-toivotut muutokset globaaliin sanakirjaan, se kopioidaan. Tämä tehdään myös muihin tarkoituksiin.

>>> numerot2 = numerot.copy() >>> numerot2[4] = "neljä" >>> numeroita(1: "yksi", 2: "kaksi", 3: "kolme") >>> numerot2(1: "yksi", 2: "kaksi", 3: "kolme", ​​4: "neljä")

Fromkeys()-menetelmällä voit luoda sanakirjan luettelosta, jonka elementeistä tulee avaimia. Voit soveltaa menetelmää sekä dict-luokkaan että sen objekteihin:

>>> a = [ 1 , 2 , 3 ] >>> c = sanele .fromkeys (a) >>>c(1: ei mitään, 2: ei mitään, 3: ei mitään) >>> d = sanelu .fromkeys (a, 10 ) >>> d {1: 10, 2: 10, 3: 10} >>>c(1: ei mitään, 2: ei mitään, 3: ei mitään)

Get()-menetelmällä voit saada elementin sen avaimella:

>>> numerot.get (1)"yksi"

Vastaa numeroita.

Pop()-menetelmä poistaa elementin sanakirjasta määritetyllä avaimella ja palauttaa poistetun parin arvon. Popitem()-menetelmä ei ota argumentteja ja poistaa ja palauttaa mielivaltaisen elementin.

>>> numerot.pop (1)"yksi" >>> numeroita(2: "kaksi", 3: "kolme") >>> numerot.popitem()(2, "kaksi") >>> numeroita(3: "kolme")

Voit lisätä elementin sanakirjaan käyttämällä setdefault()-komentoa:

>>> nums.setdefault (4 , "neljä" )"neljä" >>> numeroita(3: "kolme", ​​4: "neljä")

Vastaa numeroita = "neljä", jos elementtiä avaimella 4 ei ole sanakirjassa. Jos se on jo olemassa, nums = "neljä" korvaa vanhan arvon, setdefault() ei.

KANSSA päivityksen avulla() voit lisätä sanakirjaan toisen sanakirjan:

>>> nums.update (( 6 : "six" , 7 : "seven" ) ) >>> numeroita(3: "kolme", ​​4: "neljä", 6: "kuusi", 7: "seitsemän")

Menetelmä päivittää myös olemassa olevien avainten arvot. Sisältää useita muita ominaisuuksia.

Käytännön työ

    Luo sanakirja linkittämällä se koulumuuttujaan ja täytä se tiedoilla, jotka kuvastavat oppilaiden määrää eri luokat(1a, 1b, 2b, 6a, 7c jne.). Tee muutokset sanakirjaan seuraavasti: a) yhden luokan oppilasmäärä on muuttunut, b) kouluun on ilmaantunut uusi luokka, c) toinen luokka on purettu (poistettu) koulusta. Laske koulun oppilaiden kokonaismäärä.

    Luo sanakirja, jossa avaimet ovat numeroita ja arvot merkkijonoja. Käytä siihen items()-metodia, välitä tuloksena oleva dict_items-objekti kirjoittamaasi funktioon, joka luo ja palauttaa uuden sanakirjan, alkuperäisen "käänteisen" eli avaimet ovat merkkijonoja ja arvot numeroita.

On Stack Overflow.
Tässä artikkelissa käsitellään CPythonin version 2.7 käyttöönottoa. Kaikki esimerkit valmistettiin Pythonin 32-bittisessä versiossa 64-bittisessä käyttöjärjestelmässä, arvot ovat erilaiset.

Python-sanakirja

Pythonin sanakirja on assosiatiivinen taulukko, eli se tallentaa tiedot parien muodossa (avain, arvo). Sanakirja on muuttuva tietotyyppi. Tämä tarkoittaa, että voit lisätä siihen elementtejä, muuttaa niitä ja poistaa ne sanakirjasta. Se tarjoaa myös toiminnon elementin etsimiseksi ja palauttamiseksi avaimella.

Alustus ja elementtien lisääminen:

>>> d = () # sama kuin d = dict() >>> d["a"] = 123 >>> d["b"] = 345 >>> d["c"] = 678 >> > d ("a": 123, "c": 678, "b": 345)
Elementin hankkiminen:

>>> d["b"] 345
Elementin poistaminen:

>>> del d["c"] >>> d ("a": 123, "b": 345)
Sanakirjaavaimet voivat olla vain hajautustyyppien arvoja, eli tyyppejä, joille voidaan saada tiiviste (tätä varten niillä on oltava __hash__() -menetelmä). Siksi tyyppien arvot, kuten lista (luettelo), joukko (joukko) ja itse sanakirja (dict), eivät voi olla sanakirjan avaimia:

>>> d = 1 Traceback (viimeisin puhelu viimeisin): Tiedosto " ", rivi 1, sisään TypeError: ei-hajautustyyppi: "luettelo" >>> d = 2 Traceback (viimeisin puhelu viimeisin): Tiedosto " ", rivi 1, sisään TypeError: ei-hajautustava tyyppi: "set" >>> d = 3 Traceback (viimeisin puhelu viimeisin): Tiedosto " ", rivi 1, sisään TypeError: ei-hajautustyyppi: "dict"

Toteutus

Pythonin sanakirja on toteutettu hash-taulukona. Kuten tiedät, hash-taulukon toteutuksessa on otettava huomioon törmäysten mahdollisuus – tilanteet, joissa eri avaimilla on sama hash-arvo. On oltava tapa lisätä ja poistaa elementtejä, jotka huomioivat törmäykset. Törmäysten ratkaisemiseen on useita tapoja, kuten ketjutusmenetelmä ja avoin osoitusmenetelmä. Python käyttää avointa osoitemenetelmää. Kehittäjät suosivat avointa osoitusmenetelmää ketjutusmenetelmän sijaan, koska se voi säästää merkittävästi muistia ketjutetuissa hash-taulukoissa käytettävien osoittimien tallentamisessa.

Tarkasteltavana olevassa toteutuksessa jokainen sanakirjan hash-taulukon merkintä (PyDictEntry) koostuu kolmesta elementistä - hash, avain ja arvo.

Typedef-rakenne ( Py_ssize_t me_hash; PyObject *minä_avain; PyObject *minä_arvo; ) PyDictEntry;
PyDictObject-rakenne näyttää tältä:

#define PyDict_MINSIZE 8 typedef struct _dictobject PyDictObject; struct _dictobject ( PyObject_HEAD Py_ssize_t ma_fill; Py_size_t ma_used; Py_size_t ma_mask; PyDictEntry *ma_table; PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyDictObject *mp, PyDict hashmaltry);
Kun uusi sanakirjaobjekti luodaan, sen koko on 8. Tämä arvo määräytyy PyDict_MINSIZE-vakion avulla. Hajautustaulukon tallentamiseksi PyDictObjectiin otettiin käyttöön muuttujat ma_smalltable ja ma_table. Ennalta varattu ma_smalltable-muuttuja, jonka koko on PyDict_MINSIZE (eli 8), mahdollistaa pienten sanakirjojen (enintään 8 PyDictEntry-objektin) tallentamisen ilman lisämuistin varaamista. Kehittäjien tekemät kokeilut ovat osoittaneet, että tämä koko riittää useimpiin sanakirjoihin: pieniin sanakirjoihin ja tyypillisesti pieniin sanakirjoihin, jotka on suunniteltu välittämään nimetyt argumentit (kwargs). Muuttuja ma_table vastaa ma_smalltable pienille taulukoille (eli taulukoille, joissa on 8 solua). Suuremmille taulukoille ma_table-muisti varataan erikseen. Ma_table-muuttuja ei voi olla NULL.

Jos joku haluaa yhtäkkiä muuttaa PyDict_MINSIZE:n arvoa, se voidaan tehdä lähteissä ja rakentaa Python uudelleen. On suositeltavaa tehdä arvoksi potenssi kaksi, mutta vähintään neljä.

Hajautustaulukon solulla voi olla kolme tilaa

1) Käyttämätön (me_key == me_value == NULL)
Tämä tila tarkoittaa, että solu ei sisällä eikä ole koskaan sisältänyt (avain, arvo) paria.
Avaimen asettamisen jälkeen "käyttämätön" solu muuttuu "aktiiviseen" tilaan.
Tämä tila on ainoa tapaus, jossa me_avain = NULL, ja se on kaikkien taulukon solujen alkutila.
2) Aktiivinen (minä_avain != NULL ja minä_avain != dummy ja minä_arvo != NULL)
Osoittaa, että solu sisältää aktiivisen (avain, arvo) parin.
Avaimen poistamisen jälkeen solu siirtyy "aktiivisesta" tilasta "tyhjään" tilaan (eli me_key = dummy, ja
dummy = PyString_FromString(" ")).
Tämä on ainoa tila, jossa me_arvo != NULL.
3) Tyhjä (me_key == dummy ja me_value == NULL)
Tämä tila osoittaa, että solu sisälsi aiemmin aktiivisen (avain, arvo) parin, mutta se poistettiin ja uusi aktiivinen pariskunta ei vielä kirjoitettu soluun.
Aivan kuten "käyttämättömässä" tilassa, avaimen lisäämisen jälkeen "tyhjä" -tilasta tuleva solu siirtyy "aktiiviseen" tilaan.
"Tyhjä" solu ei voi palata "käyttämättömään" tilaan, eli palauttaa me_key = NULL, koska tässä tapauksessa koettimien sekvenssillä törmäyksen sattuessa ei ole mahdollisuutta tietää, olivatko solut "aktiivisia" .

Rakenteen jäsenmuuttujat

ma_fill – nollasta poikkeavien avainten määrä (me_key != NULL), eli "aktiivisten" ja "tyhjien" solujen summa.
ma_used – nollasta poikkeavien ja ei-tyhjien avainten määrä (me_key != NULL, me_key != dummy), eli "aktiivisten" solujen lukumäärä.
ma_mask – maski yhtä suuri kuin PyDict_MINSIZE - 1.
ma_lookup – hakutoiminto. Oletusarvoisesti lookdict_string käytetään uutta sanakirjaa luotaessa. Tämä tehtiin, koska kehittäjät katsoivat, että useimmat sanakirjat sisältävät vain merkkijonoavaimia.

Perushienot

Hajautustaulukon tehokkuus riippuu siitä, onko sillä "hyviä" hash-funktioita. "Hyvä" hash-funktio tulisi laskea nopeasti ja mahdollisimman pienellä törmäysmäärällä. Mutta valitettavasti yleisimmin käytetyt ja tärkeimmät hash-funktiot (merkkijono- ja kokonaislukutyypeille) palauttavat melko säännöllisiä arvoja, mikä johtaa törmäyksiin. Otetaan esimerkki:

>>> kartta(hash, ) >>> kartta(hash, ["abca", "abcb", "abcc", "abcd", "abce"])
Kokonaisluvuille tiivisteet ovat niiden arvoja, joten peräkkäisillä luvuilla on peräkkäiset tiivisteet, ja merkkijonojen peräkkäisillä riveillä on lähes peräkkäiset tiivisteet. Tämä ei välttämättä ole huono asia, päinvastoin, 2**i-koon taulukossa hashin vähiten merkitsevien i-bittien ottaminen taulukon aloitusindeksiksi on erittäin nopeaa, ja sanakirjoissa, jotka on indeksoitu sekvenssillä kokonaislukuja törmäyksiä ei tapahdu ollenkaan:

Sama pätee lähes täysin, jos sanakirjan avaimet ovat "peräkkäisiä" merkkijonoja (kuten yllä olevassa esimerkissä). SISÄÄN yleisiä tapauksia tämä antaa enemmän kuin satunnaisen käyttäytymisen, mitä vaaditaan.

Hashista vain vähiten merkitsevien i-bittien ottaminen indeksiksi on myös alttiina törmäyksille: ota esimerkiksi luettelo avainjoukoksi. Koska kokonaisluvut ovat omia tiivisteitä ja tämä sopii sanakirjaan, jonka koko on 2**15 (vuodesta 20000< 32768), последние 15 бит от каждого хэша все будут равны 0.

>>> kartta(lambda x: "(0:016b)". muoto(x), ) ["0000000000000000", "1000000000000000", "100000000000000000", "110000000000", "110000000000", "110000000000", 0.0 " 0", "1010000000000000000", " 11000000000000000000" , "11100000000000000000", ...]
Osoittautuu, että kaikilla avaimilla on sama indeksi. Toisin sanoen kaikki näppäimet (lukuun ottamatta ensimmäistä) törmäävät.
Esimerkit erityisesti valituista "huonoista" tapauksista eivät saa vaikuttaa normaaleihin tapauksiin, joten jätämme vain viimeiset i-bitit. Kaikki muu on jätetty törmäysratkaisumenetelmään.

Törmäyksen ratkaisumenetelmä

Proseduuria, jossa valitaan sopiva solu elementin lisäämiseksi hash-taulukkoon, kutsutaan luotaukseksi ja kyseistä ehdokassolua kutsutaan koettimeksi.

Yleensä solu löytyy ensimmäisellä yrityksellä (ja tämä on totta, koska hash-taulukon täyttökerroin pidetään alle 2/3), mikä eliminoi tarpeen tuhlata aikaa tutkimiseen ja tekee alkuindeksin laskemisesta erittäin nopeaa. Mutta katsotaanpa, mitä tapahtuu, jos törmäys tapahtuu.

Törmäysresoluutiomenetelmän ensimmäinen osa on laskea taulukkoindeksit mittausta varten kaavalla:

J = ((5*j) + 1) % 2**i
Tämän kaavan kutsuminen 2**i-kertaa varten palauttaa jokaisen alueen sisällä olevan luvun tarkalleen kerran. Esimerkiksi:

>>> j = 0 >>> i = 3 >>> _:lle xrange(0, 2**i): ... tulosta j, ... j = ((5 * j) + 1) % 2 **minä ... 0 1 6 7 4 5 2 3
Sanotte, että tämä ei ole parempaa kuin lineaarisen mittauksen käyttäminen vakioaskeleena, koska tässä tapauksessa myös hash-taulukon solut tarkastellaan tietyssä järjestyksessä, mutta tämä ei ole ainoa ero. Yleisissä tapauksissa, kun hash-arvo avaimet ovat peräkkäin, tätä menetelmää parempi kuin lineaarinen testaus. Yllä olevasta esimerkistä näet, että taulukon koko 8 (2**3) indeksointijärjestys on seuraava:

0 -> 1 -> 6 -> 7 -> 4 -> 5 -> 2 -> 3 -> 0 -> ... (siis on toistoja)
Jos törmäys tapahtuu koettimelle, jonka indeksi on 5, niin seuraava mittausindeksi on 2, ei 6, kuten lineaarisen mittauksen tapauksessa, jonka askel on +1, joten tulevaisuudessa lisättävälle avaimelle anturi jonka indeksi on 6, törmäystä ei tapahdu. Lineaarinen testaus tässä tapauksessa (kanssa peräkkäisiä arvoja avaimet) olisi huono vaihtoehto, koska törmäyksiä tulisi paljon. Todennäköisyys, että avainten hash-arvot ovat luokkaa 5 * j + 1, on paljon pienempi.

Törmäysresoluutiomenetelmän toinen osa on käyttää hashin alhaisimpien i-bittien lisäksi myös jäljellä olevia bittejä. Tämä toteutetaan perturb-muuttujan avulla seuraavasti:

J = (5 * j) + 1 + häiriöhäiriö >>= PERTURB_SHIFT, sitten j % 2**i käytetään seuraavana mittausindeksinä, jossa: häiriö = hash(avain) PERTURB_SHIFT = 5
Tämän jälkeen näytteiden järjestys riippuu jokaisesta hashin bitistä. Pseudosatunnainen variaatio on erittäin tehokas, koska se lisää nopeasti bittien eroja. Koska perturb on allekirjoittamaton, jos luotausta tehdään riittävän usein, perturbista tulee lopulta ja se pysyy yhtä kuin nolla. Tässä vaiheessa (mitä saavutetaan hyvin harvoin) tulokseksi j tulee jälleen 5 * j + 1. Haku etenee sitten täsmälleen kuten menetelmän ensimmäisessä osassa, ja lopulta löydetään vapaa solu, koska, kuten aiemmin mainittiin , jokainen alueen numero palautetaan täsmälleen kerran, ja olemme varmoja, että niitä on aina yksi vähintään yksi "käyttämätön" solu.

"Hyvän" arvon valitseminen PERTURB_SHIFT:lle on tasapainottamiskysymys. Jos teet siitä pienen, tiivisteen merkittävimmät bitit vaikuttavat näytteiden sekvenssiin iteraatioiden aikana. Jos teet siitä suuren, niin todella "huonoissa" tapauksissa tiivisteen merkittävimmät bitit vaikuttavat vain varhaisissa iteraatioissa. Yhden Python-kehittäjän Tim Petersin suorittamien kokeiden tuloksena PERTURB_SHIFT:n arvoksi valittiin 5, koska tämä arvo osoittautui "parhaaksi". Toisin sanoen se osoitti törmäysten vähimmäismäärän sekä normaaleissa että erityisesti valituissa "huonoissa" tapauksissa, vaikka arvot 4 ja 6 eivät olleet merkittävästi huonompia.

Taustaa: Yksi Python-kehittäjistä, Reimer Behrends, ehdotti ajatusta polynomipohjaisen lähestymistavan käyttämisestä taulukkoindeksien laskemiseen, jota Christian Tismer sitten paransi. Tämä lähestymistapa osoitti myös erinomaisia ​​törmäystuloksia, mutta vaati enemmän operaatioita sekä lisämuuttujan taulukkopolynomin tallentamiseksi PyDictObject-rakenteeseen. Tim Petersin kokeissa virta, jota käytettiin vuonna Python menetelmä oli nopeampi ja osoitti yhtä hyviä törmäystuloksia, mutta vaati vähemmän koodia ja käytti vähemmän muistia.

Sanakirjan alustaminen

Kun luot sanakirjan, PyDict_New-funktiota kutsutaan. Tässä toiminnossa seuraavat toiminnot suoritetaan peräkkäin: muisti varataan uudelle PyDictObject-sanakirjaobjektille. Muuttuja ma_smalltable tyhjennetään. Muuttujat ma_used ja ma_fill asetetaan arvoon 0, ma_table tulee yhtä suureksi kuin ma_smalltable. Ma_mask-arvoksi asetetaan PyDict_MINSIZE - 1. Hakutoiminnon ma_lookup on lookdict_string. Luotu sanakirjaobjekti palautetaan.

Elementin lisääminen

Kun lisäät kohteen sanakirjaan tai muutat kohteen arvoa sanakirjassa, PyDict_SetItem-funktio kutsutaan. Tämä funktio ottaa hash-arvon ja kutsuu insertdict-funktion sekä dictresize-funktion, jos taulukko on 2/3 täynnä nykyisestä koostaan.

Insertdict-funktio puolestaan ​​kutsuu lookdict_stringiä (tai lookdict-toimintoa, jos sanakirjassa on ei-merkkijonoavain), joka etsii lisättävää vapaata solua hash-taulukosta. Samaa toimintoa käytetään avaimen löytämiseen poiston aikana.

Alkuperäinen koetinindeksi tässä funktiossa lasketaan avaimen hajautusmoduuli jaettuna taulukon koolla (joten otetaan tiivisteen vähiten merkitsevät bitit). Tuo on:

>>> PyDict_MINSIZE = 8 >>> avain = 123 >>> hash(avain) % PyDict_MINSIZE >>> 3
Pythonissa tämä toteutetaan loogisen AND-operaation ja maskin avulla. Maski on yhtä suuri kuin seuraava arvo: mask = PyDict_MINSIZE - 1.

>>> PyDict_MINSIZE = 8 >>> mask = PyDict_MINSIZE - 1 >>> avain = 123 >>> hash(avain) & maski >>> 3
Näin saadaan hashin vähiten merkitsevät bitit:
2**i = PyDict_MINSIZE, joten i = 3, eli kolme vähiten merkitsevää bittiä riittää.
hash(123) = 123 = 1111011 2
maski = PyDict_MINSIZE - 1 = 8 - 1 = 7 = 111 2
indeksi = hash(123) & maski = 1111011 2 & 111 2 = 011 2 = 3

Kun indeksi on laskettu, solua verrataan indeksiin, ja jos se on "käyttämätön", siihen lisätään merkintä (hash, key, value). Mutta jos tämä solu on varattu, koska toisessa tietueessa on sama hash (eli on törmäys), tehdään vertailu lisätyn tietueen ja solun tietueen välillä. Jos merkintöjen hash ja avain täsmäävät, merkinnän oletetaan olevan jo sanakirjassa ja se päivitetään.

Tämä selittää hankalan osan avainten lisäämisessä, jotka ovat samanarvoisia mutta tyypiltään erilaisia ​​(esimerkiksi float, int ja kompleksi):

>>> 7.0 == 7 == (7+0j) Tosi >>> d = () >>> d="kellu" >>> d (7.0: "kellu") >>> d="int" > >> d (7.0: "int") >>> d="kompleksi" >>> d (7.0: "kompleksi") >>> type(d.keys())
Toisin sanoen sanakirjaan ensin lisätty tyyppi on avaintyyppi päivityksestä huolimatta. Tämä johtuu siitä, että kelluvien arvojen hash-toteutus palauttaa int:n hajautusarvon, jos murto-osa on 0,0. Esimerkki hajautuslaskelmasta floatille, kirjoitettu uudelleen Pythonissa:

Def float_hash(float_value): ... murtoosa, intpart = matematiikka.modf(float_value) if fracpart == 0.0: return int_hash(int(float_value)) # use int hash ...
Ja kompleksista saatu hash palauttaa kelluvasta tiivisteen. Tässä tapauksessa vain reaaliosan hash palautetaan, koska imaginaariosa on nolla:

Def kompleksi_hash(kompleksin_arvo): hashreal = float_hash(kompleksi_arvo.todellinen) if hashreal == -1: return -1 hashimag = float_hash(complex_value.imag) if hashimag == -1: return -1 res = hashreal + 1000003 * hashimag res = hand_overflow(res) if res == -1: res = -2 return res
Esimerkki:

>>> hash(7) 7 >>> hash(7.0) 7 >>> hash(7+0j) 7
Johtuen siitä, että sekä tiivisteet että arvot ovat kaikille kolme tyyppiä ovat yhtä suuret, löydetyn tietueen arvon yksinkertainen päivitys suoritetaan.

Huomautus elementtien lisäämisestä: Python estää elementtien lisäämisen sanakirjaan iteroinnin aikana, joten jos yrität lisätä uuden elementin, tapahtuu virhe:

>>> d = ("a": 1) >>> i:lle d:ssä: ... d["uusi kohde"] = 123 ... Traceback (viimeisin puhelu viimeisin): Tiedosto " ", rivi 1, sisään RuntimeError: sanakirjan kokoa muutettiin iteroinnin aikana
Palataan elementin lisäämiseen sanakirjaan. Kun merkintä on lisätty tai päivitetty hash-taulukkoon, seuraavaa lisättävää merkintää verrataan. Jos tietueiden tiiviste tai avain ei täsmää, luotaus alkaa. Haetaan "käyttämätön" solu lisättäväksi. Tässä Python-toteutukset satunnaista (ja jos häiriömuuttuja on yhtä suuri kuin nolla - neliöllinen) näytteenottoa käytetään. Kuten edellä on kuvattu, satunnaisnäytteenotossa seuraavan solun indeksi valitaan näennäissatunnaisesti. Merkintä lisätään ensimmäiseen löydettyyn "käyttämättömään" soluun. Toisin sanoen kaksi avainta a ja b, joissa hash(a) == hash(b), mutta a != b voivat helposti olla samassa sanakirjassa. Jos alkuperäisen näyteindeksin solu on "tyhjä", testaus suoritetaan. Ja jos ensimmäinen löydetty solu on "nolla", "tyhjää" solua käytetään uudelleen. Tämän avulla voit korvata poistetut solut säilyttäen samalla käyttämättömät solut.

Osoittautuu, että sanakirjaan lisättyjen elementtien indeksit riippuvat siinä jo olevista elementeistä, ja kahden samasta parijoukosta (avain, arvo) koostuvan sanakirjan avainten järjestys voi olla erilainen ja määräytyy järjestyksen mukaan. johon elementit lisätään:

>>> d1 = ("yksi": 1, "kaksi": 2, "kolme": 3, "neljä": 4, "viisi": 5) >>> d2 = ("kolme": 3, "kaksi" ": 2, "viisi": 5, "neljä": 4, "yksi": 1) >>> d1 == d2 Tosi >>> d1.keys() ["neljä", "kolme", ​​"viisi" , "kaksi", "yksi"] >>> d2.keys() ["neljä", "yksi", "viisi", "kolme", ​​"kaksi"]
Tämä selittää, miksi Pythonin sanakirjat näyttävät tallennetut (avain, arvo) parinsa epäjärjestyksessä siinä järjestyksessä, jossa ne lisättiin sanakirjaan. Sanakirjat näyttävät ne siinä järjestyksessä, jossa ne näkyvät hash-taulukossa (eli indeksijärjestyksessä).

Katsotaanpa esimerkkiä:

>>> d = () >>> d["habr"] = 1

Indeksissä 5 tapahtui lisäys. Muuttujat ma_fill ja ma_used ovat yhtä kuin 1.

>>> d["python"] = 2

Indeksissä 0 tapahtui lisäys. Muuttujat ma_fill ja ma_used ovat yhtä suuret kuin 2.

>>> d["dikt"] = 3

Indeksissä 4 tapahtui lisäys. Muuttujat ma_fill ja ma_used ovat yhtä suuret kuin 3.
>>> d["artikkeli"] = 4

Indeksissä 1 tapahtui lisäys. Muuttujat ma_fill ja ma_used ovat yhtä suuret kuin 4.

>>> d["!!!"] = 5

Tapahtui seuraavaa:
hash("!!!") = -1297030748
i = -1297030748 & 7 = 4
Mutta kuten taulukosta näet, indeksi 4 on jo varattu merkinnällä "dict"-näppäimellä. Eli törmäys tapahtui. Testaus alkaa:
häiriö = -1297030748
i = (i * 5) + 1 + häiriö
i = (4 * 5) + 1 + (-1297030748) = -1297030727
indeksi = -1297030727 & 7 = 1
Uusi mittausindeksi on 1, mutta tämä indeksi on myös varattu (tietue "artikkeli"-näppäimellä). Toinen törmäys on tapahtunut, jatkamme testausta:
häiritä = häiriö >> PERTURB_SHIFT
häiriö = -1297030748 >> 5 = -40532211
i = (i * 5) + 1 + häiriö
i = (-1297030727 * 5) + 1 + (-40532211) = -6525685845
indeksi = -6525685845 & 7 = 3
Uusi mittausindeksi on 3, ja koska se ei ole varattu, lisätään tietue avaimella "!!!". soluun, jossa on kolmas indeksi. Tässä tapauksessa merkintä lisättiin kahden kokeilun jälkeen tapahtuneiden törmäysten vuoksi. Muuttujat ma_fill ja ma_used ovat nyt yhtä suuria kuin 5.

>>> d ("python": 2, "artikkeli": 4, "!!!": 5, "dict": 3, "habr": 1)
Yritetään lisätä sanakirjaan kuudes elementti.

>>> d[";)"] = 6

Kuudennen elementin lisäämisen jälkeen taulukko on 2/3 täynnä, ja vastaavasti sen koko muuttuu. Kun koko muuttuu (tässä tapauksessa kasvaa 4 kertaa), hash-taulukko rakennetaan kokonaan uudelleen ottaen huomioon uusi koko - kaikki "aktiiviset" solut jaetaan uudelleen ja "tyhjät" ja "käyttämättömät" solut jätetään huomiotta. .

Hajautustaulukon koko on nyt 32 ja muuttujat ma_fill ja ma_used ovat nyt 6. Kuten näet, elementtien järjestys on muuttunut täysin:

>>> d ("!!!": 5, "python": 2, "habr": 1, "dict": 3, "artikkeli": 4, ";)": 6)

Etsi elementti

Merkinnän etsiminen sanakirjan hash-taulukosta etenee samalla tavalla, alkaen solusta i, jossa i riippuu avaimen hajautusarvosta. Jos tiiviste ja avain eivät täsmää löydetyn tietueen tiivistettä ja avainta (eli tapahtui törmäys ja sopiva tietue on löydettävä), testaus alkaa ja jatkuu, kunnes löydetään tietue, jonka hash ja avain vastaavat. Jos kaikista soluista on haettu, mutta tietuetta ei löydy, palautetaan virhe.

>>> d = ("a": 123, "b": 345, "c": 678) >>> d["x"] Traceback (viimeisin puhelu viimeisin): Tiedosto " ", rivi 1, sisään KeyError: "x"

Hash-taulukon täyttökerroin

Pöydän koko muuttuu, kun se on 2/3 täynnä. Eli jos sanakirjan hash-taulukon alkuperäinen koko on 8, muutos tapahtuu kuudennen elementin lisäämisen jälkeen.

>>> 8 * 2.0 / 3 5.333333333333333
Tässä tapauksessa hash-taulukko rakennetaan uudelleen ottaen huomioon sen uusi koko, ja vastaavasti kaikkien merkintöjen indeksit muuttuvat.

2/3 koosta valittiin optimaaliseksi, jotta testaus ei kestänyt liikaa aikaa, eli lisäys uusi merkintä tapahtui nopeasti. Tämän arvon lisääminen saa sanakirjan täytettyä tiheämmin merkintöjä, mikä puolestaan ​​lisää törmäysten määrää. Niiden pienentäminen lisää tietueiden harvaa, mikä haittaa niiden käyttämien prosessorin välimuistirivien lisääntymistä ja kokonaismuistin koon kasvamisen kustannuksella. Tarkistetaan, onko taulukko täynnä, koodin aikaherkässä osassa. Tarkistuksesta yritetään tehdä monimutkaisempi (esimerkiksi muuttamalla käyttöjaksoa eri kokoja hash-taulukot) heikentynyt suorituskyky.

Voit tarkistaa, että sanakirjan koko todella muuttuu näin:

>>> d = sanele.näppäimistä(alue(5)) >>> d.__sizeof__() 248 >>> d = sanele avaimista(alue(6)) >>> d.__sizeof__() 1016
Esimerkki palauttaa koko PyDictObjectin koon 64-bittiselle käyttöjärjestelmäversiolle.
Taulukon alkukoko on 8. Siten kun täytettyjen solujen määrä on 6 (eli yli 2/3 koosta), taulukon koko kasvaa 32:een. Sitten kun luku on 22, se Nousee 128:aan. 86:ssa se nousee 512:een, 342:een - 2048:aan ja niin edelleen.

Taulukon koon lisäyskerroin

Pöydän koon kasvukerroin saavuttaessa enimmäistaso täyte on 4, jos pöydän koko on alle 50 000 elementtiä, ja 2 suuremmille pöydille. Tämä lähestymistapa voi olla hyödyllinen muistirajoitteisissa sovelluksissa.

Taulukon koon suurentaminen parantaa keskimääräistä harvalukuisuutta eli merkintöjen leviämistä sanakirjataulukossa (vähentää törmäyksiä) lisäämällä muistin kulutusta ja iterointinopeutta ja vähentää myös kalliiden koon muuttavien muistivarausten määrää kasvavalle sanakirjalle.

Tyypillisesti kohteen lisääminen sanakirjaan voi kasvattaa sen kokoa kertoimella 4 tai 2 riippuen sanakirjan koosta, mutta on myös mahdollista, että sanakirjan koko pienenee. Tämä tilanne voi syntyä, jos ma_fill (nollasta poikkeavien avainten määrä, "aktiivisten" ja "tyhjien" solujen summa) on paljon suurempi kuin ma_used ("aktiivisten" solujen lukumäärä), eli monet avaimet on poistettu .

Elementin poistaminen

Kun alkio poistetaan sanakirjasta, PyDict_DelItem-funktiota kutsutaan.
Sanakirjasta poisto tapahtuu avaimella, vaikka todellisuudessa muistia ei vapauteta. Tämä funktio laskee avaimen hash-arvon ja etsii sitten merkintää hash-taulukosta käyttämällä samaa lookdict_string- tai lookdict-funktiota. Jos löydetään tietue, jossa on tällainen avain ja hash, tämän tietueen avain asetetaan arvoon "tyhjä" (eli me_key = dummy) ja tietueen arvoksi asetetaan NULL (me_value = NULL). Tämän jälkeen ma_used muuttuja pienenee yhdellä ja ma_fill pysyy ennallaan. Jos merkintää ei löydy, palautetaan virheilmoitus.

>>> del d["!!!"]

Poiston jälkeen ma_used muuttujaksi tuli 4, mutta ma_fill pysyi 5:nä, koska solua ei poistettu, vaan se yksinkertaisesti merkittiin "tyhjäksi" ja jatkaa solun miehittämistä hash-taulukossa.

Hash-satunnaistaminen

Kun käynnistät pythonin, voit käyttää -R-kytkintä pseudosatunnaisen suolan käyttämiseen. Tässä tapauksessa tyyppien, kuten merkkijonojen, puskurien, tavujen ja datetime-objektien (päivämäärä, kellonaika ja päivämääräaika) hash-arvot ovat arvaamattomia tulkin kutsujen välillä. Tämä menetelmä ehdotetaan suojaksi DoS-hyökkäyksiä vastaan.

ja se on JÄRJESTÄMÄTÖN arvojen kokoelma.


Sanakirjojen ominaisuudet:
1. Avaimen käyttö. Silmukan elementteihin pääsee käsiksi näppäimillä.
2. Sanakirjan arvot tallennetaan LAITTELEMAMATTOMASSA järjestyksessä, eikä avaimia välttämättä tallenneta siinä järjestyksessä, jossa ne on lisätty.
3. Sanakirja voi tallentaa sisäkkäisiä sanakirjoja. Sanakirjan arvot voivat olla mitä tahansa tyyppiä. Sanakirjan avain on muuttumaton tyyppi, se voi olla merkkijono, kokonaisluku, float tai määritetyistä tyypeistä koostuva monikko.
4. Sanakirjat toteutetaan hash-taulukoina nopea pääsy.
5. Sanakirjat tallentavat viittaukset objekteihin, eivät itse objektiin.

Sanakirjan päätoiminnot ovat: tallentaminen tietyllä avaimella ja arvon hakeminen siitä. Voit myös poistaa parin avain: arvo ohjeiden avulla del.

Sanakirjamenetelmät (funktiot):

  • sanella ()- sanakirjan luominen;
  • len()- palauttaa parien lukumäärän;
  • asia selvä()- poistaa kaikki arvot sanakirjasta;
  • kopio()- luo pseudokopion sanakirjasta;
  • syväkopio ()- luo täysi kopio sanakirja;
  • fromkeys()- sanakirjan luominen;
  • saada()- saada arvo avaimella;
  • has_key()- arvon tarkistaminen avaimella;
  • tuotteet()
  • iteriems()- palauttaa iteraattorin;
  • avaimet ()- palauttaa avainluettelon;
  • iterkeys ()- palauttaa avainten iteraattorin;
  • pop()- hakee arvon avaimella;
  • popitem()- poimii mielivaltaisen arvon;
  • päivittää()- muuttaa sanakirjaa;
  • arvot()- palauttaa arvoluettelon;
  • itervalues()- palauttaa iteraattorin arvoluetteloon.
  • sisään- operaattori tarkistaa arvon olemassaolon avaimella;
  • del- operaattori, poistaa parin avaimella;
  • sanella ()- rakentaa sanakirjan sekvenssin avulla.

Käytämme pientä ohjelmaa - esimerkkinä puhelinluetteloa - näytämme joitain toimintoja sanakirjoilla:

Koodi: telbook = ("sasha": "32-11-4", "vanya": "44-65-99") # Ilmoita sanakirja telbook["fedya"] = "22-47-32" # add uusi kohde sanakirjaan tulosta telbook # Tulosta sanakirjan kaikki arvot tulosta telbook["vanya"] # Tulosta arvon "vanya" numero del telbook["sasha"] # poista arvo "sasha" tulosta telbook # katso mitä tapahtui tulosta telbook.keys() # Tulosta arvot print telbook.has_key("vanya") # tarkista sisältääkö sanakirja arvon "vanya" Tulos: ("vanya": "44-65-99", " fedya": "22-47-32", "sasha": "32-11-4") 44-65-99 ("vanya": "44-65-99", "fedya": "22-47- 32") ["vanya", "fedya"] Totta

Tapoja luoda sanakirja:
1. Analogisesti listojen ja monikoiden kanssa. Vain kiharat hakasulkeet ():

>>> s = ("nimi": "Vitalia", "ikä": 25) >>> s ("ikä": 25, "nimi": "Vitali")

2.Dynaamisesti. Tarvittaessa:

>>> s["nimi"] = "Vitalia" >>> s["ikä"] = 26 >>> s ("ikä": 26, "nimi": "Vitali")

3. Menetelmän käyttö sanella (). Tässä tapauksessa avainten on oltava merkkijonoja. Tällä menetelmällä voit kirjoittaa avaimen ilman lainausmerkkejä. Alla on neljä vaihtoehtoa sanakirjan täyttämiseen:

>>> s1 = dict(id = 1, nimi = "Vitalia", ikä = 26) >>> s1 ("ikä": 26, "nimi": "Vitali", "id": 1) >>> s2 = dict(("id": 1, "nimi": "Vitalia", "ikä": 26)) >>> s2 ("ikä": 26, "id": 1, "name": "Vitali") >>> s3 = dict([("id",1),("nimi","Vitalia"),("ikä",26)]) >>> s3 ("ikä": 26, "id": 1, "nimi": "Vitalia") >>> s4 = dict(zip(("id","nimi","ikä"),(1"Vitalia",26))) >>> s4 (" ikä": 26, "id": 1, "nimi": "Vitalia")

4. käyttämällä menetelmää fromkeys()— luo sanakirjan määritetystä avainluettelosta tyhjillä arvoilla.

>>> d = ().fromkeys(["nimi","ikä"],123) >>> d ("ikä": 123, "nimi": 123) tai >>> d = ().fromkeys( ["nimi","ikä"]) >>> d ("ikä": ei mitään, "nimi": ei mitään)

5. Konstruktorin käyttäminen:

>>> s = dict((x,x**2) x:lle x-alueella(5)) >>> s (0: 0, 1: 1, 2: 4, 3: 9, 4: 16)

No, nyt harjoittelua varten, yritetään luoda sanakirja monikkoluettelon avulla:

>>> lista = [("nimi","ikä"),("Tanya",25)] >>> s = dict(lista) >>> s ("Tanya": 25, "nimi": "ikä ") >>> len(s) # Sanakirjan pituus (arvojen määrä) 2

Luomme pienen tietokannan ja etsimme poneja:

Koodi: ludi = ("Ivan": ("puhelin": "23-44-6", "ikä": "20"), "Igor": ("puhelin": "45-2-67", "ikä" " : "40")) name = raw_input("Vvedite imya, chtobi uznat nomer telefona: ") key = "puhelin" jos nimi ludissa: tulosta "%s puhelin on %s" % (nimi, ludi) Tulos: Vvedite imya, chtobi uznat nomer telefona: Igor Igor puhelin on 45-2-67

Sanakirjan kopioiminen menetelmällä kopio() :

>>> x = ("nimi":"järjestelmänvalvoja","huone":) >>> y = x.copy() >>> y ("nimi": "järjestelmänvalvoja", "huone": )

Mutta copy()-menetelmä näyttää vain lähteen sisällön. Jos esimerkiksi poistamme arvon "huone"-luettelosta x:ssä, huomaamme, että se puuttuu myös y:stä:

>>> x["huone"].remove(1) >>> y ("nimi": "järjestelmänvalvoja", "huone": )

Tämän estämiseksi käytämme syväkopio ():

>>> kopiosta tuonti syväkopio >>> y = x.deepcopy()

Menetelmä saada()- Näyttää arvon avaimella, ja jos sitä ei ole, antaa Ei mitään:

>>> a = ("nimi":"Vitalia") >>> print a.get("nimi") Vitaliy

Menetelmä tuotteet()- palauttaa koko luettelon sanakirjan arvoista:

Koodi: d = ("nimi":"käyttäjä","huone":"125") avaimelle, arvo d.items(): print(avain, arvo) Tulos: ("nimi", "käyttäjä") ( "huone", "125")

Menetelmä iteriems()- palauttaa iteraattorin - tuottaa saman tuloksen:

Koodi: k, v in d.iteritems(): tulosta k, v Tulos: nimi käyttäjähuone 125

Menetelmä avaimet ()- palauttaa avainluettelon.

>>> tulosta d.keys() ["nimi", "huone"]

Menetelmä pop()- hakee arvon avaimella ja poistaa sen sitten:

>>> d.pop("nimi") "käyttäjä" >>> d ("huone": "125")

Menetelmä popitem()- poimii mielivaltaisen arvon ja poistaa sen sitten.

Menetelmä päivittää()- muuttaa sanakirjan arvoa avaimella:

>>> d1 = ("huone":"12") >>> d.update(d1) >>> d ("huone": "12")

Menetelmä arvot()- palauttaa arvoluettelon:

>>> d.values() ["12"]

Operaattori del- poistaa avaimen: arvo pari kerrallaan:

>>> del d["huone"] >>> d()

Katselukerrat: 3 890

Sisältösarja:

Listojen jälkeen sanakirja on joustavin sisäänrakennettu tyyppi. Jos luettelo on järjestetty kokoelma, niin sanakirja on järjestämätön kokoelma. Sanakirjojen tärkeimmät ominaisuudet:

  1. Pääsy tapahtuu avaimella, ei indeksillä. Kuten luettelossa, sanakirjassa voit käyttää elementtejä silmukassa avaimien perusteella.
  2. Sanakirjan arvot tallennetaan lajittelemattomassa järjestyksessä, eikä avaimia voida tallentaa siinä järjestyksessä, jossa ne on lisätty.
  3. Kuten luettelot, sanakirja voi tallentaa sisäkkäisiä sanakirjoja. Sanakirja voi tallentaa arvoina minkä tahansa tyyppisiä (heterogeenisiä) objekteja. Sanakirjan avain on muuttumaton tyyppi, joka voi olla merkkijono, kokonaisluku, float tai määritetyistä tyypeistä koostuva monikko.
  4. Sanakirjat toteutetaan hajautustaulukoina, joihin pääsee nopeasti.
  5. Sanakirjat, kuten luettelot, tallentavat viittauksia objekteihin, eivät itse objekteihin.

Tänään käsittelemme seuraavia aiheita.

  1. Mikä on sanakirja?
  2. Sanakirjan funktiot/menetelmät.
  3. Toiminnot sanakirjan kanssa.
  4. Esimerkkejä.

1. Mikä on sanakirja

Sanakirja on assosiatiivinen taulukko tai hash. Se on järjestämätön joukko avain:arvo-pareja, joiden vaatimus on, että avaimet ovat yksilöllisiä. Aaltosulkeiden pari () luo tyhjän sanakirjan. Toisin kuin sekvenssit, sanakirjaelementtejä käytetään avaimella, ei hakemistolla, avainta ei voi muuttaa.

Sanakirjan päätoiminnot ovat tallentaminen tietyllä avaimella ja arvon hakeminen siitä. Voit myös poistaa avain: arvo -parin del-käskyn avulla.

Sanakirjan keys()-metodi palauttaa luettelon kaikista käytetyistä avaimista ei tietyssä järjestyksessä; Listan lajittelemiseksi sinun on käytettävä sort()-menetelmää. Tietyn avaimen olemassaolon määrittämiseksi on olemassa has_key()-metodi, joka vanhenee versiossa 3.0 - sen sijaan on in-operaattori. Uuden objektin lisääminen sanakirjaan ei vaadi ennakkotarkastuksia: jos avaimella oli jo vastaava arvo, se korvataan.

Esimerkki - sanakirja puhelinluettelona:

>>> dic = ("vanya" : 23323223, "smith" : 32232332) >>> dic["fedya"] = 33332222 >>> dic ("vanya": 23323223, "fedya": 33332222": "smith", "smith" 32232332) >>> dic["smith"] 32232332 >>> del dic["vanya"] >>> dic ("fedya": 33332222, "smith": 32232332) >>> dic.keys() ["fedya" ", "smith"] >>> dic.has_key("fedya") Totta

Voit luoda sanakirjan useilla tavoilla:

  1. Yleinen ilmaus - on kätevää, jos sanakirja on staattinen: D = ("nimi": "mel", "ikä": 45)
  2. Dynaaminen vaihtoehto lennossa luomiseen: D = () D["nimi"] = "mel" D["ikä"] = 45
  3. Dict()-funktiota käytettäessä näppäinten on oltava merkkijonoja. Tämän toiminnon avulla voit poistaa vaatimuksen laittaa avainta lainausmerkkeihin. Esimerkki näyttää neljä vaihtoehtoa saman sanakirjan luomiseen: d1 = dict(id=1948, name="Pesukone", koko=3) d2 = dict(("id": 1948, "nimi": "Pesukone", "koko": 3)) d3 = dict([( "id", 1948), ("nimi", "pesukone"), ("koko", 3)]) d4 = dict(zip(("id", "nimi", "koko"), (1948, " Pesukone", 3)))
  4. Käyttämällä fromkeys() - luo sanakirjan käyttämällä tyhjien arvojen avainluetteloa: D = ().fromkeys(["nimi", "ikä"],123)
  5. Konstruktoria käyttämällä: d = dict((x, x**2) x:lle x-alueella(5))

2. Sanakirjan toiminnot/menetelmät

dict() - sanakirjan luominen;

len() - palauttaa parien määrän;

clear() - poistaa kaikki arvot sanakirjasta;

copy() - luo pseudokopion sanakirjasta;

deepcopy() - luo täydellisen kopion sanakirjasta;

fromkeys() - sanakirjan luominen;

get() - saada arvo avaimella;

has_key() - arvon tarkistaminen avaimella;

items() - palauttaa arvoluettelon;

iteriyems() - palauttaa iteraattorin;

iterkeys() - palauttaa avainiteraattorin;

pop() - hakee arvon avaimella;

popitem() - hakee mielivaltaisen arvon;

update() - muuttaa sanakirjan;

arvot() - palauttaa arvoluettelon;

itervalues() - palauttaa iteraattorin arvoluetteloon.

in - operaattori, tarkistaa arvon olemassaolon avaimella;

del - operaattori, poistaa parin avaimella;

dict() - rakentaa sanakirjan sekvenssin avulla.

Luo esimerkiksi sanakirja käyttämällä monikkoluetteloa:

>>> kohteita = [("nimi","sveta"),("ikä",20)] >>> d = dict(items) >>> d ("ikä": 20, "nimi": "sveta ") >>> len(d) 2

in() - tapahtumatarkistusoperaattori.

Esimerkki: tietokanta voidaan täyttää sanakirjana.

Tarkista puhelinnumero tietokannasta nimellä:

people = ("Liisa": ("puhelin": "2341", "addr": "Foo drive 23"), "Beth": ("puhelin": "9102", "addr": "Baarikatu 42") ) name = "Alice" key = "puhelin" jos nimi ihmisissä: tulosta "%s puhelin on %s" % (nimi, ihmiset) >>> Liisa puhelin on 2341 copy()

Esimerkki sanakirjan kopion luomisesta:

>>> x = ("käyttäjä":"admin","attr":) >>> y = x.copy() >>> y ("käyttäjä": "admin", "attr": )

Copy()-menetelmä ei toimi täysi kopio: jos esimerkiksi suoritamme toiminnon:

>>> x["attr"].remove(1)

Sitten yllätämme, että määrite poistetaan myös kopiosta.

Jotta näin ei tapahdu, sinun on käytettävä deepcopy()-menetelmää.

>>> kopiosta tuonti syväkopio >>> y = x.deepcopy()

fromkeys() - luo sanakirjan annetuille avaimille tyhjillä arvoilla:

>>> ().fromkeys(["nimi", "ikä"]) ("ikä": ei mitään, "nimi": ei mitään)

Voit täyttää kaikki arvot oletuksena:

>>> ().fromkeys(["nimi", "ikä"],123) ("ikä": 123, "nimi": 123)

get() - saa arvon avaimella, jos puuttuu, antaa Ei mitään:

>>> d = () >>> tulosta d.get("nimi") Ei mitään

has_key() - tarkistaa, onko sanakirjassa arvo by tämä avain:

>>> d = () >>> d.has_key("nimi") Väärä

items() - palauttaa arvoluettelon:

avaimelle arvo d.items(): print(avain, arvo)

iteriyems() - palauttaa iteraattorin - tuottaa saman tuloksen:

>>> k, v in d.iteritems(): ... tulosta k, v

keys() - palauttaa avainluettelon;

iterkeys() - palauttaa avainiteraattorin:

>>> d.keys() ["url", "title"] >>> d.iterkeys()

pop() - hakee arvon avaimella ja poistaa sen sitten:

>>> d.pop("nimi") >>> d ("url": "http://www.python.org")

popitem() - hakee mielivaltaisen arvon ja poistaa sen sitten:

>>> d = ("title": "Python-verkkosivusto", "url": "http://www.python.org", "www": "python") >>> d.popitem() >> > d ("www": "python", "title": "Python-verkkosivusto")

update() - muuttaa arvoa avaimella:

>>> d2 = ("www":"python.org") >>> d.update(d2) >>> d ("www": "python.org", "title": "Python-verkkosivusto")

arvot() - palauttaa arvoluettelon:

>>> d=() >>> d=1 >>> d=2 >>> d=3 >>> d (1:1, 2:2, 3:3) >>> d.values()

del - operaattori poistaa avaimen: arvo pari kerrallaan:

>>> del d >>> d (1: 1, 3: 3)

3. Toiminnot

Koska sanakirjat ovat karttoja eivätkä sekvenssejä, niitä ei voi ketjuttaa tai viipaloida.

Voit käyttää tavallisia vertailuoperaattoreita sanakirjoihin:

<, <=, ==, !=, >=, >

Sanakirjan avaimien läpikäymiseksi käytämme:

>>> table = ("Python": "Guido van Rossum", ... "Perl": "Larry Wall", ... "Tcl": "John Ousterhout" ) >>> lang in table: .. print (lang, table) .. >>> Tcl John Ousterhout >>> Python Guido van Rossum >>> Perl Larry Wall

Sanakirjoja on hyvä säilyttää moniulotteisia taulukoita tai matriiseja:

>>> Matriisi = () >>> Matriisi[(2, 3, 4)] = 88 >>> Matriisi[(7, 8, 9)] = 99 >>> >>> X = 2; Y = 3; Z = 4 >>> Matriisi[(X, Y, Z)] 88 >>> Matriisi ((2, 3, 4): 88, (7, 8, 9): 99)

Sanakirjojen avulla voit tallentaa jäsenneltyä tietoa tietueiden muodossa:

>>> man = ("nimi": "Serg", ... "työpaikat": ["ohjelmoija", "kirjoittaja"], ... "web": "www.iakovlev.org", ... " koti": ("kaupunki": "Moskova", "zip":129000)) >>> mies["nimi"] Serg >>> mies["työpaikat"] "kirjoittaja"

4. Esimerkkejä

Esimerkki 1. Lasketaan kuinka monta kertaa kukin merkki esiintyy rivillä:

def histogrammi(t): d = dict() c:lle s:ssä: jos c ei d:ssä[c] = 1 else:d[c] += 1 return d hist = histogrammi("kuinka monta kertaa") > >> ("a": 1,"e": 1"i": 1"h": 1"m": 2"o": 1"n": 1"s": 1 "t": 1"w": 1"y": 1)

Jos meidän on käännettävä tämä sanakirja ja asetettava taajuus avaimeksi:

def invert_dict(d): inv = dict() avaimelle d:ssä: val = d jos val ei inv:inv = else:inv.append(key) return inv print invert_dict(hist) >>> (1: [" a", "e", "i", "h", "o", "n", "s", "t", "w", "y"], 2: [" ", "m"] )tuonti merkkijono tuonti sys sanat = () strip = string.whitespace + merkkijono.välimerkit + merkkijono.numerot + "\""" filename = "tiedosto" riville avoimessa(tiedostonimi): sanalle rivissä.lower().split (): sana = sana.kaistale(kaistale) if len(sana) > 2: sanat = sanat.get(sana, 0) + 1 sanalle lajiteltuna(sanat): print(""(0)" esiintyy (1 ) kertaa".muoto(sana, sanat))

Esimerkki 3. Sanakirjan lajittelu avainten mukaan:

author = ("php":"Rasmus Lerdorf",\ "perl":"Larry Wall",\ "tcl":"John Ousterhout",\ "awk":"Brian Kernighan",\ "java":"James Gosling ",\ "parrot":"Simon Cozens",\ "python":"Guido van Rossum") #Tai näin: langs = author.keys() langs.sort() kielelle langs: tulostuskieli," - " ,author #or this: for key in sorted(author.iterkeys()): tulosta "%s: %s" % (avain, tekijä) >>> awk - Brian Kernighan >>> java - James Gosling >>> papukaija - Simon Cozens >>> perl - Larry Wall >>> php - Rasmus Lerdorf >>> python - Guido van Rossum >>> tcl - John Ousterhout

Esimerkki 4. Kuinka kääntää sanakirja, esim. muuta avaimia arvoilla:

def invert_dict_nonunique(d): newdict = () for k, v in d.iteritems(): newdict.setdefault(v, ).append(k) return newdict d = ("lapsi1": "parent1","lapsi2": "parent1","child3": "parent2","child4": "parent2") tulosta invert_dict_nonunique(d) >>> ("parent2": ["lapsi3", "lapsi4"], "parent1": ["lapsi1" ", "lapsi2"])

Johtopäätös

Voimme tiivistää: sanakirjat ja luettelot ovat yksinkertaisimpia, joustavimpia ja tehokkaimpia kokoelmatyyppejä. Sanakirja, kuten luettelo, on muuttuva tietotyyppi, vaikka se sisältääkin muuttumattomia avaimia ja voi kasvaa loputtomasti. Jos tarvitset kokoelmaa avaimella, sanakirja sopii tähän parhaiten. Jos tarvitset kokoelman mielivaltaisten sisäkkäisten objektien tallentamiseen, sanakirja auttaa sinua tässä.

Esimerkkikoodia testattiin Python-versiolla 2.6.

Ladattavat resurssit

static.content.url=http://www.site/developerworks/js/artrating/

Zone=Avoin lähdekoodi, Linux

Artikkelin tunnus=505647

ArticleTitle=Python-ohjelmointi: Osa 4: Sanakirjat