Ohjelman tulkinta ja ajonaikainen suorituskyky perustuvat siihen, miten ohjelma puretaan ja käsitellään sen rakenneosista. Esimerkiksi, kun ohjelman jollakin rivillä tapahtuu operaatio, kuten GOTO tai GOSUB, tulkintaohjelma täytyy ohjata oikeaan kohtaan ohjelmakoodissa. Tämä vuorovaikutus ohjelman syntaksin, tokenien ja lauseiden kanssa vaatii erityistä huomiota ja tarkkaa rakenteen käsittelyä. Yksi keskeisistä osista tulkinnan rakentamisessa on ajonaikaisen ympäristön luominen, jossa ohjelma tulee suoritetuksi ja jossa muuttujat ja rivinumerot saavat merkityksensä.
Tulkintaa varten tarvitsemme tietoa ohjelman rakenteesta ja sen muuttujista. Yksi tehokas tapa luoda ympäristö ja suorittaa ohjelma on käyttää AST:ta (Abstrakti Syntaksipuu). Tämä puu ei vain edusta ohjelman rakennetta, vaan myös sitä, miten ohjelman eri osat ovat yhteydessä toisiinsa. Kun AST on rakennettu, seuraava vaihe on se, että tulkki (Interpreter) käy sen läpi ja suorittaa koodin askel askeleelta.
Kun lähdemme käsittelemään esimerkkejä, kuten aritmeettisia lauseita, on tärkeää huomioida, miten operaattorit eri syvyystasoilla saavat etusijan. Tulkinnassa se, miten eri operaattorit arvioidaan (esimerkiksi kertolasku ennen yhteenlaskua), voi vaikuttaa suuresti ohjelman toimintaan ja siihen, miten aritmeettiset lauseet käsitellään. Yksi tapa lähestyä tätä on käyttää silmukoita ja tulostuksia apuna, jolloin voidaan seurata, miten eri lauseet ja operaattorit käsitellään vaiheittain.
Paremman ymmärryksen saamiseksi kannattaa kirjoittaa omia esimerkkejä ja käyttää esimerkiksi print()-komentoja metodien välillä havainnollistamaan purkamisprosessia. Tämä auttaa visualisoimaan, kuinka ohjelman koodi etenee ja miten eri vaiheissa esiintyvät osat kytkeytyvät toisiinsa. Jos ohjelma on vielä keskeneräinen ja et ole päässyt kirjoittamaan koko ohjelmaa, on järkevää poistaa ohjelman ajaminen AST:n läpi, sillä tämä voi johtaa virheellisiin tuloksiin.
Vaikka nykyisin käytetään tehokkaampia menetelmiä lauseiden purkamiseen, kuten Dijkstran kehittämä Shunting Yard -algoritmi, on mahdollista yhdistää se myös rekursiiviseen laskevaan purkajaan. Tällaisessa hybridimallissa voidaan hyödyntää molempien menetelmien vahvuuksia, jolloin saadaan sekä tehokkuutta että selkeyttä ohjelman purkamiseen ja suorittamiseen.
Tulkinnassa keskeistä on luoda ajonaikainen ympäristö, joka ei vain suorita ohjelmaa, vaan myös pitää kirjaa muuttujien tilasta ja käsittelee lauseet oikein. Tämä ympäristö on rakennettu AST:n perusteella, ja sen luominen edellyttää tarkkaa muuttujien ja lauseiden käsittelyä. Näin syntyy tulkki, joka pystyy suoritukseen, mutta myös palauttamaan tuloksia ja muokkaamaan ympäristöä sen mukaan, miten ohjelma etenee.
Es
Mikä on MacPaintin tiedostomuoto ja miten se toimi?
MacPaint oli yksi ensimmäisistä graafisista piirustusohjelmista, joka tuli markkinoille Macintosh-tietokoneen mukana vuonna 1984. Se mullisti digitaalisen taiteen tekemisen tarjoamalla yksinkertaisen ja tehokkaan tavan luoda kuvia, mutta sen toiminta oli rajoitettu aikansa laitteistolla. Vaikka MacPaint oli aikanaan vallankumouksellinen, sen tiedostomuoto ja kompressio olivat rajoittuneita käytettävissä olevan tallennustilan vuoksi. Tämä oli erityisesti ongelma alkuperäisessä Macintoshissa, joka ei sisältänyt kovalevyä, vaan toimi ainoastaan levykkeiltä. Näin ollen MacPaintin tiedostot olivat pakko rajoittaa mahdollisimman pieniksi.
MacPaint
Miten NES-pelin emulointi toimii ja mitä on tärkeää tietää ROM-tiedostojen käsittelystä?
NES-pelien emulointi on monivaiheinen prosessi, jossa keskeinen osio on pelin grafiikan päivittäminen ja oikeiden syötteiden käsittely. Jokaisella NES-pelillä on oma NMI (Non-Maskable Interrupt) -käsittelijä, joka päivittää pelin grafiikan videomäärityksessä eli vblankissa. Muut osat pelin suorituksessa keskittyvät tapahtumien käsittelyyn, kuten näppäinkomentoihin ja ohjauslaitteen syötteisiin.
Pygame-kirjaston avulla voidaan käsitellä NES-ohjaimen painalluksia. Näppäimistön painalluksia tunnistetaan, ja painettavat näppäimet muunnetaan NES-ohjaimen painikkeiksi, jotka vastaavat vasenta, oikeaa, ylös- ja alas-nuolta sekä muita peruskomentoja kuten A, B, Start ja Select. Tämä tapahtuu käsittelemällä käyttäjän syötteitä ja päivittämällä vastaavat tilat NES:n virtuaalisessa ohjaimessa.
NES-pelien pelitiedostot, eli ROM-tiedostot, muodostavat pelin koodin ja graafiset elementit. Alun perin NES-pelikonsolien kartuskit koostuivat pääasiassa ROM-siruista, jotka sisälsivät pelin ohjelmakoodin ja grafiikat. Koodit sijaitsevat PRG-ROM-sirulla, ja grafiikat CHR-ROM-sirulla. ROM-siruilla oli kuitenkin myös muita osia, kuten logiikkasiruja, jotka mahdollistivat niin sanotun pankkivaihdon (bank switching). Tämä tekniikka antoi peleille mahdollisuuden käyttää enemmän muistia kuin mitä NES-konsoli pystyi suoraan käsittelemään.
Yksi keskeisistä tekniikoista, jota käytettiin ROM-sirujen käsittelyssä, oli pankkivaihto, jossa peli saattoi vaihtaa muistipankkeja. Tämä mahdollisti suurempien pelitiedostojen käytön, vaikka NES:n suora muisti oli rajallinen. Tällöin peli saattoi siirtyä seuraavaan muistiosaan, mikä mahdollisti laajemman ja monimutkaisemman pelin luomisen. Tämä tekniikka on keskeinen myös NES-emulaattoreissa, joissa on tuettava monenlaisia ROM-siruja, sillä eri pelit saattavat käyttää eri tyyppisiä mapper-siruja.
Mapperit ovat erityisiä siruja, jotka mahdollistavat muistipankkien vaihtamisen pelissä. NES-emulaattoreiden on tuettava näitä mappereita, jotta ne voivat pyörittää kaikkia NES-pelejä, mutta käytännössä suurin osa peleistä käyttää muutamia yleisiä mapper-malleja. Yksi vanhimmista NES-emulaattoreista, Marat Fayzullinin iNES, määritteli mapperien numeroimiskäytännön. Tämä sama järjestelmä on käytössä nykyäänkin useimmissa NES-emulaattoreissa.
iNES-formaatissa ROM-tiedostot sisältävät myös otsikon, jonka avulla voidaan lukea tiedoston sisällön rakennetta. Tämä otsikko määrittää muun muassa sen, millaisia siruja pelissä käytetään ja minkälaisia laajennuksia tiedostoon on liitetty. Esimerkiksi iNES-formaatissa ROM-tiedoston otsikossa näkyy pelin käyttämä mapper-numero, joka määrittää sen, kuinka muistipankkien vaihto tapahtuu. Nykyään iNES-formaatin lisäksi käytetään myös NES 2.0 -formaattia, jossa on lisätietoja otsikosta, mutta perusmuoto on samanlainen.
NES-emulaattori rakentuu siten, että ROM-tiedosto luetaan ja sen otsikko tarkistetaan. Tiedoston otsikosta saadaan tärkeää tietoa, kuten pelin käyttämä mapper, PRG-ROM:n koko ja CHR-ROM:n koko. Itse ROM-luokka käsittelee tiedoston lukemisen ja datan purkamisen, kuten otsikon tietojen erottelun. Tämä mahdollistaa sen, että emulaattori pystyy tunnistamaan pelin ja toimimaan oikein.
Tämän kaiken emuloinnin taustalla on kuitenkin se, että NES-konsolin pelit olivat alun perin suunniteltu laitteistolle, joka ei ollut kovin tehokas. Emulaattorit yrittävät jäljitellä tätä alkuperäistä laitteistoa mahdollisimman tarkasti, mutta usein ne joutuvat tekemään kompromisseja sen suhteen, miten laitteiston rajoituksia simuloidaan. Esimerkiksi muistinhallinta ja näytön päivitys voivat poiketa alkuperäisestä käytöstä emulaattorissa, koska emulaattorin täytyy sovittaa tämä prosessi nykyaikaisiin tietokoneisiin.
Emulaattorin kehittämisessä on tärkeää ymmärtää myös, että NES-pelit voivat sisältää pelimekaniikkoja, jotka vaativat erityistä huomiota. Näitä voivat olla esimerkiksi tallennuspaikat (kuten PRG RAM), jotka voivat pitää pelin tilan tallessa myös virran katketessa. Tämä mahdollistaa pelin jatkamisen myöhemmin ilman, että edistys häviää. Jotkut pelit käyttävät jopa paristoja näiden tallennusten säilyttämiseksi, mikä lisää pelin elinikää ja pelaajan sitoutumista.
NES-pelien emulointi on monivaiheinen prosessi, joka edellyttää syvällistä ymmärrystä alkuperäisestä laitteistosta, mutta myös kykyä mukauttaa tätä tietoa nykyaikaisiin ohjelmointiympäristöihin. Erilaiset mapper-sirut ja laajennukset, kuten paristolla varustetut RAM-muistit, tekevät emuloinnista monivivahteisen ja haasteellisen, mutta myös erittäin palkitsevan.
Kuinka piirtää spritejä NES:llä ja hallita niiden attribuutteja
Spritejen piirtäminen NES:llä muistuttaa monella tavalla taustalaattojen piirtämistä, mutta sen sijaan, että luettaisiin nimitaulusta (nametable), luetaan OAM-muistista (Object Attribute Memory, it.spr). Kukin sprite-muistissa oleva merkintä on 4 tavua, jotka kuvaavat spriten y- ja x-koordinaatit, kuvamallin taulukon indeksin ja sen attribuutit (ks. Taulukko 6-5). OAM-muistissa voi olla enintään 64 spriten merkintää. Jos y-koordinaatti on 0xFF, merkintä ei ole käytössä. Spritejen piirtämisprosessi alkaa luetteloimalla kaikki kelvolliset merkinnät OAM:sta neljä tavua kerrallaan.
Ensimmäiseksi haetaan spriten y- ja x-koordinaatit. Samalla tarkistetaan, onko sprite taustasprite, joka piirretään vain, jos tausta on läpinäkyvä. Spritejen piirtäminen tapahtuu päinvastaiseen suuntaan OAM:sta, koska ensimmäinen sprite (nollasprite) on erityisen tärkeä.
Seuraavaksi sprite piirretään pikseli kerrallaan, aivan kuten taustalaatat:
Tässä x ja y ovat analogeja fine_x:lle ja fine_y:lle taustan piirtämisessä. Oleellista on varmistaa, ettei piirretyt pikselit mene ruudun ulkopuolelle.
Yksi spriten tärkeimmistä attribuuteista on pystysuuntainen kääntö (flip_y), joka määräytyy spriten attribuuttibytin seitsemännen bitin mukaan:
Jos sprite on käännetty pystysuunnassa, sen pikselit luetaan päinvastaisessa järjestyksessä. Koska sprite on aina 8x8 pikseliä, käytetään taikakonstina lukua 7. Kuvan lukeminen spriten mallitaulukosta on hyvin samanlaista kuin taustan piirtämisessä:
Tässä käytetään nimeämiskäytäntöä bit0s_address ja bit1s_address muistialueiden osoitteiden laskemiseen. Spriten värien määrittämiseen käytetään sen attribuuttibytin 0 ja 1 bittiä, jotka yhdistetään lopulliseen väriin.
Sprite voi myös olla vaakasuunnassa käännetty (flip_x) sen attribuuttibytin kuudennen bitin perusteella:
Spritejen piirtämisessä on myös tarpeen yhdistää kaksi bittitasoa ja ohittaa läpinäkyvät pikselit:
PPU (Picture Processing Unit) seuraa nollaspriten (OAM:n ensimmäinen merkintä) törmäämistä ei-läpinäkyviin taustapikseleihin. Tämä kutsutaan "sprite-zero hitiksi". Tähän yksinkertaiseen törmäystunnistukseen käytetään seuraavaa koodia:
Kun sprite-zero hit tapahtuu, se merkitään tilarekisteriin. Samalla varmistetaan, että taustaspritejä ei piirretä, jos tausta ei ole läpinäkyvä. Tässä tarkastellaan myös PPU:n lippuja, jotka rajoittavat taustan tai spriten vasemman 8 pikselin piirtämistä.
Lopuksi haetaan spriten yksittäisen pikselin väri yhdistämällä bitti 3 ja 2 bitiin 1 ja 0:
Väri haetaan paletista käyttäen read_memory()-metodia, joka ottaa huomioon muistialueiden peilaamisen.
PPU:lla on useita muistiin kartoittuja rekistereitä, joilla on omat erityispiirteensä ja tekniset rajoituksensa lukemisessa ja kirjoittamisessa. Esimerkiksi rekisterissä 0x2002 sijaitsee tilarekisteri, jonka lukeminen tyhjentää vblank-tilan:
Rekisterissä 0x2004 on mahdollisuus lukea OAM:in self.spr_address-osoitteesta:
Tässä vaiheessa puhutaan rekistereistä 0x2000 ja 0x2001, jotka ovat hallintarekistereitä ja niillä on tärkeä rooli PPU:n sisäisten asetusten määrittämisessä.
Lopuksi, kaikki rekistereiden kirjoittaminen ja lukeminen tapahtuu muistinäkymän kautta, ottaen huomioon erityisesti muistivälimuistien (bufferien) roolin.
Miten KNN-algoritmi toimii kalojen ja käsinkirjoitettujen numeroiden luokittelussa?
KNN (K-Nearest Neighbors) on yksi yksinkertaisimmista ja tehokkaimmista luokittelualgoritmeista, joka perustuu lähimpien naapureiden etäisyyksien vertailuun. Sen perusidea on yksinkertainen: annetaan syöte (kuten kalojen tai numeroiden ominaisuudet), ja algoritmi palauttaa sen luokan, johon syöte parhaiten sopii sen mukaan, mitä lähimmät tiedot (naapurit) luokittelevat. Tämä luku käsittelee KNN-algoritmin soveltamista sekä kalojen että käsinkirjoitettujen numeroiden luokittelussa.
Kuvitellaan, että meillä on kalojen aineisto, jossa kullakin kalalla on useita mitattuja ominaisuuksia: pituus, paino, ympärysmitta ja muita mittoja. Oletetaan myös, että tavoitteena on ennustaa kalojen paino käyttäen kalojen muita mittauksia. Tällöin kalojen paino on meille tuntematon, mutta voimme käyttää KNN-algoritmia vertaillessamme muiden kalojen mittoja ja löytääksemme ne, joiden ominaisuudet ovat lähimpänä tutkimassamme kalassa. Näin pystymme luokittelemaan ja ennustamaan kalojen painoa tietyllä tarkkuudella.
Kalojen luokittelussa voimme hyödyntää yksinkertaista yksikkötestausta (unit tests) varmistaaksemme, että kalat, jotka ovat lähimpänä testikalaa, löytyvät oikein. Esimerkiksi testaamme, onko lähimmät kalat oikeita, ja varmistamme, että KNN-algoritmi tunnistaa ne tarkasti. Tällaiset testit voidaan kirjoittaa Pythonissa käyttämällä unittest-kirjastoa, joka testaa, että algoritmin palauttamat tulokset vastaavat odotuksia. Näin voimme varmistaa, että ohjelmamme toimii oikein, ennen kuin käytämme sitä käytännön luokittelutehtävissä.
Toinen esimerkki KNN:n käytöstä on käsinkirjoitettujen numeroiden luokittelu optisen merkin tunnistuksen (OCR) avulla. KNN:ää voidaan käyttää tunnistamaan käsinkirjoitettuja numeroita, kuten postitse lähetettyjä osoitteita, joissa käytetään automaattisia lajittelukoneita. Tässä käytettävä datasetti sisältää 5620 käsinkirjoitettua numeroa, jotka on luokiteltu 0–9, ja ne on luotu 43 eri henkilön toimesta. Kuvaesitykset on pienennetty 8×8 pikselin kokoiseksi, mikä tarkoittaa, että jokainen numero on esitetty 64 pikselillä, joista jokainen arvostellaan harmaasävyarvon mukaan.
Digit-luokka, joka edustaa käsinkirjoitettuja numeroita, on luotu Pythonissa käyttämällä NumPy-kirjastoa pikselitietojen tallentamiseksi. Tällöin voimme hyödyntää NumPyn tehokkuutta numeeristen laskelmien tekemisessä, kuten etäisyyksien laskemisessa eri numeroiden välillä. Etäisyyksien laskeminen NumPyn avulla mahdollistaa KNN-algoritmin sujuvan toiminnan. Kun syötämme testikuvat tähän luokittelijaan, ohjelma arvioi, mikä numero se on, ja vertaa sitä oikeaan luokkaan. Tämä prosessi toistetaan suurissa testeissä, joissa ohjelman tarkkuus pyritään maksimoimaan.
Käytettäessä KNN-algoritmia numeroiden tunnistamiseen, voimme määritellä tarkkuuden vertaamalla algoritmin antamia luokituksia todellisiin luokkiin testikokoelmassa. Kuten esimerkissä testissä, joka käyttää käsinkirjoitettujen numeroiden datasettiä, saamme erinomaisia tuloksia, kun tarkkuus ylittää 97 %. Tämä tarkoittaa, että algoritmi osaa luokitella numerot erittäin tarkasti, ja tämä saavutetaan käyttämällä k=1, eli vain yhden lähimmän naapurit arvioidaan. KNN:n tehokkuus perustuu osittain siihen, että se käyttää yksinkertaisia etäisyyksien laskelmia ja ei vaadi monimutkaista mallin kouluttamista.
Lopuksi, vaikka KNN on yksinkertainen ja tehokas, on tärkeää muistaa sen rajat. Yksi haasteista on se, että algoritmi voi olla hidas suurilla tietomäärillä, koska se vertaa jokaista uutta tietoa kaikkiin olemassa oleviin tietoihin. Tämä voi tulla ongelmaksi, jos tietokannan koko kasvaa erittäin suureksi. On myös tärkeää valita oikea k-arvo (lähimpien naapureiden määrä), sillä liian suuri k voi johtaa liian yleisiin ennusteisiin, kun taas liian pieni k voi tehdä algoritmista herkemmän kohinalle.
Pedagoginen vierashuone: Opettajien yhteisön "Opimme yhdessä" piirit
Matkatuotteen myyntisopimus matkatoimiston ja matkustajan tai muun tilaajan välillä
Luokan vanhempainkomitean sääntö
Ilmoitus muutoksista vuoden 2021 ensimmäisen neljänneksen tilinpäätösraportissa

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский