diff --git a/data/osa-10/1-luokkahierarkiat.md b/data/osa-10/1-luokkahierarkiat.md index 0501b0c9b..856db4936 100644 --- a/data/osa-10/1-luokkahierarkiat.md +++ b/data/osa-10/1-luokkahierarkiat.md @@ -1,22 +1,24 @@ --- path: '/osa-10/1-luokkahierarkiat' -title: 'Luokkahierarkiat' +title: 'Klasshierarkier' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät mitä tarkoitetaan perinnällä -- Osaat kirjoittaa luokkia jotka perivät jonkin toisen luokan -- Tiedät miten eri piirteet periytyvät +- Vet du vad arv betyder i programmeringssammanhang +- Kommer du att kunna skriva klasser som ärver andra klasser +- Vet du hur arv påverkar egenskaperna i klasser -## Luokkien erikoistaminen +## Specialklasser för speciella ändamål -Joskus tulee vastaan tilanne, jossa luokan toimintaa olisi hyvä pyrkiä erikoistamaan, mutta vain osalle olioista. Tarkastellaan esimerkkinä tilannetta, jossa meillä on kaksi luokkaa - Opiskelija ja Opettaja. Yksinkertaistuksen vuoksi luokista on jätetty pois kaikki asetus- ja havainnointimetodit. +Ibland stöter man på en situation där man redan har definierat en klass, men sedan inser att man behöver speciella egenskaper i vissa, men inte alla, instanser av klassen. Och ibland inser man att man har definierat två stycken mycket liknande klasser med bara små skillnader. Som programmerare strävar vi efter att alltid upprepa oss så lite som möjligt, medan vi behåller tydlighet och läsbarhet. Så hur kan vi ta hänsyn till olika implementeringar av liknande objekt? + +Låt oss ta en titt på två klassdefinitioner: `Student` och `Larare`. Getter- och sättar-metoder har utelämnats tills vidare för att hålla exemplet kort. ```python @@ -38,7 +40,9 @@ class Opettaja: ``` -Yksinkertaistetustakin esimerkistä huomataan, että luokilla on yhteisiä piirteitä - tässä tapauksessa nimi ja sähköpostiosoite. Monessa tilanteessa olisi hyvä, jos yhteisiä piirteitä voitaisin käsitellä yhdellä operaatiolla: oletetaan tilanne, jossa koulun sähköpostitunnus muuttuu. Toki voitaisiin kirjoittaa kaksi käsittelyfunktiota... +Även i ett avskalat exempel som ovan har vi redan en hel del upprepningar: båda klasserna innehåller attributen `namn` och `epost`. Det vore en bra idé att ha en enda attributdefinition, så att det räcker med en enda funktion för att redigera båda attributen. + +Tänk dig till exempel att skolans e-postadress ändras. Alla adresser skulle behöva uppdateras. Vi skulle kunna skriva två separata versioner av i stort sett samma funktion: ```python @@ -50,15 +54,15 @@ def korjaa_email2(o: Opettaja): ``` -...mutta saman koodin toistaminen kahteen kertaan tuntuu turhalta työltä, ja lisää virheiden mahdollisuutta. Olisi siis hyvä, jos molempien luokkien mukaisia olioita voitaisiin käsitellä samalla metodilla. +Att skriva i stort sett samma sak två gånger är onödig upprepning, för att inte tala om att det fördubblar möjligheterna till fel. Det skulle vara en klar förbättring om vi kunde använda en enda funktion för att arbeta med instanser av båda klasserna. -Luokat kuitenkin sisältävät myös piirteitä, joita toisella luokalla ei ole. Sen takia luokkien yhdistäminen ei tunnu järkevältä. +Båda klasserna har också attribut som är unika för dem. Att bara kombinera alla attribut i en enda klass skulle innebära att alla instanser av klassen då skulle ha onödiga attribut, bara olika för olika instanser. Det verkar inte heller vara en idealisk situation. - ## Perintä +## Arv - Ratkaisu löytyy olio-ohjelmoinnin tekniikasta nimeltä _perintä_. Perinnällä tarkoitetaan sitä, että luokka _perii_ piirteet joltain toiselta luokalta. Näiden perittyjen piirteiden rinnalle luokka voi sitten toteuttaa uusia piirteitä. +Objektorienterade programmeringsspråk innehåller vanligtvis en teknik som kallas arv (eng. inheritance). En klass kan ärva egenskaper från en annan klass. Förutom dessa ärvda egenskaper kan en klass också innehålla egenskaper som är unika för den. - Opettaja- ja Opiskelija-luokilla voisi olla yhteinen _yliluokka_ `Henkilo`: +Med detta i åtanke är det rimligt att klasserna `Larare` och `Student` har en gemensam bas eller föräldraklass `Person`: ```python @@ -70,9 +74,9 @@ class Henkilo: ``` - Luokassa on toteutettu siis henkilöön liittyvät piirteet. Nyt luokat Opiskelija ja Opettaja voivat _periä_ luokan ja lisätä perittyjen ominaisuuksien rinnalle uusia piirteitä: +Den nya klassen innehåller de egenskaper som delas av de andra två klasserna. Nu kan `Student` och `Larare` ärva dessa egenskaper och dessutom lägga till sina egna. : - Perintä tapahtuu kirjoittamalla luokan nimen perään perittävän luokan nimi sulkuihin: +Syntaxen för arv innebär helt enkelt att basklassens namn läggs till inom parentes på rubrikraden: ```python @@ -114,9 +118,9 @@ if __name__ == "__main__": ``` - Koska sekä Opiskelija että Opettaja perivät luokan Henkilo, molemmilla on käytössään Henkilo-luokassa määritellyt piirteet, mukaanlukien metodi `vaihda_spostitunniste`. +Både `Student` och `Larare` ärver klassen `Person`, så båda har de egenskaper som definieras i klassen Person, inklusive metoden `uppdatera_epost_doman`. Samma metod fungerar för instanser av båda de härledda klasserna. - Tarkastellaan vielä toista esimerkkiä, jossa luokka Kirjahylly perii luokan Laatikko: + Låt oss titta på ett annat exempel. Vi har en `Bokhylla` som ärver klassen `BokLada`: ```python class Kirja: @@ -151,11 +155,11 @@ class Kirjahylly(Kirjalaatikko): ``` - Luokassa Kirjahylly on määritelty metodi `lisaa_kirja`. Samanniminen metodi on määritelty myös yliluokassa `Kirjalaatikko`. Tällaisessa tapauksessa puhutaan metodin _uudelleenmäärittelystä_ tai ylikirjoituksesta (overwriting): aliluokan samanniminen metodi korvaa yliluokan vastaavan metodin. + Klassen `Bokhylla` innehåller metoden `tillägg_bok`. En metod med samma namn finns definierad i basklassen `BokLada`. Detta kallas överstyrning (eng. override): om en härledd klass har en metod med samma namn som basklassen, överstyr den härledda versionen originalet i instanser av den härledda klassen. - Esimerkissämme idea on, että kirjalaatikossa kirja asetetaan aina laatikossa päällimmäiseksi, mutta kirjahyllyssä voidaan määritellä asetuspaikka. Sen sijaan metodin `listaa_kirjat` uudelleenmäärittelyä ei ole nähty tarpeelliseksi - sama kirjojen listaus toimii niin laatikossa kuin hyllyssäkin (ainakin esimerkissämme). + Tanken i exemplet ovan är att en ny bok som läggs till i en BokLada alltid hamnar högst upp, men med en Bokhylla kan du själv ange platsen. Metoden `lista_böcker` fungerar likadant för båda klasserna, eftersom det inte finns någon överordnad metod i den härledda klassen. - Tarkastellaan esimerkkiä luokkien käyttämisestä: + Låt oss prova dessa klasser: ```python @@ -203,13 +207,13 @@ Sinuhe (Mika Waltari) - Myös Kirjahylly-luokasta muodostettujen olioiden kautta voidaan käyttää metodia `listaa_kirjat`, koska perinnän ansiosta se on olemassa myös luokan `Kirjahylly` aliluokissa. +Klassen Bokhylla har alltså också tillgång till metoden `lista_böcker`. Genom ärvning är metoden medlem i alla klasser som kommer från klassen `BokLada`. - ## Piirteiden periytyminen +## Arv och räckvidd av egenskaper - Aliluokka perii yliluokalta kaikki piirteet. Aliluokasta voidaan viitata suoraan yliluokan piirteisiin, paitsi jos yliluokassa on määritelty piirteet yksityisiksi (käyttämällä kahta alaviivaa muuttujan nimen edessä). + En härledd klass ärver alla egenskaper från sin basklass. Dessa egenskaper är direkt åtkomliga i den härledda klassen, såvida de inte har definierats som privata i basklassen (med två understreck före egenskapens namn). - Niinpä esimerkiksi Kirjahylly-luokasta voitaisiin viitata yliluokan konstruktoriin sen sijaan että kirjoitettaisiin toiminnallisuus uudestaan: + Eftersom attributen för en Bokhylla är identiska med en BokLada, fanns det ingen anledning att skriva om konstruktorn för Bokhylla. Vi anropade helt enkelt basklassens konstruktor: ```python @@ -220,9 +224,9 @@ Sinuhe (Mika Waltari) ``` -Yliluokan konstuktoriin (tai yliluokkaan muutenkin) viitataan funktion `super()` avulla. Huomaa, että tässäkin tapauksessa parametri `self` lisätään automaattisesti. +Alla egenskaper i basklassen kan nås från den härledda klassen med funktionen `super()`. Argumentet `self` utelämnas från metodanropet, eftersom Python lägger till det automatiskt. -Tarkastellaan toisena esimerkkinä luokkaa Gradu, joka perii luokan Kirja. Aliluokasta kutsutaan yliluokan konstruktoria: +Men vad händer om attributen inte är identiska; kan vi fortfarande använda basklassens konstruktor på något sätt? Låt oss titta på en klass som heter `Avhandling` och som ärver klassen `Bok`. Den härledda klassen kan fortfarande anropa konstruktören från basklassen: ```python @@ -243,9 +247,9 @@ class Gradu(Kirja): ``` -Nyt Gradu-luokan konstruktorista kutsutaan yliluokan (eli luokan Kirja) konstruktoria, jossa asetetaan attribuuttien `nimi` ja `kirjailija` arvot. Sen jälkeen aliluokan konstruktorissa asetetaan attribuutin `arvosana` arvo - tätä luonnollisesti ei voida tehdä yliluokan konstruktorissa, koska yliluokalla ei tällaista attribuuttia ole. +Konstruktorn i `Avhandling`-klassen anropar konstruktorn i basklassen `Bok` med argumenten för `namn` och `forfattare`. Dessutom anger konstruktören i den härledda klassen värdet för attributet `vitsord`. Detta kan naturligtvis inte vara en del av basklassens konstruktor, eftersom basklassen inte har något sådant attribut. -Luokkaa voidaan käyttää esimerkiksi näin: +Ovanstående klass kan användas på följande sätt: ```python @@ -269,9 +273,7 @@ Pekka Python -Koska aliluokka `Gradu` perii kaikki yliluokan piirteet, se perii myös attribuutit `nimi` ja `kirjailija`. Arvot osalle attribuuteista annetaan yliluokan sisältä löytyvässä konstruktorissa. - -Aliluokka voi myös viitata yliluokan metodiin, vaikka metodi olisikin määritelty uudestaan aliluokassa. Seuraavassa esimerkissä luokasta `Platinakortti` kutsutaan uudelleenmääritellyssä metodissa `bonuspisteet` yliluokan vastaavaa metodia. +Även om en härledd klass åsidosätter en metod i sin basklass kan den härledda klassen fortfarande anropa den åsidosatta metoden i basklassen. I följande exempel har vi ett grundläggande `BonusKort` och ett särskilt `PlatinumKort` för särskilt lojala kunder. Metoden `rakna_bonus` är åsidosatt i den härledda klassen, men den åsidosatta metoden anropar basmetoden: ```python @@ -312,7 +314,7 @@ class Platinakortti(Bonuskortti): ``` -Nyt platinakortin bonus lasketaan hyödyntämällä aluksi yliluokan vastaavaa metodia ja lisäämällä sitten ylimääräiset 5 prosenttia tähän bonukseen. Esimerkki luokkien käytöstä: +Bonusen för ett PlatinumKort beräknas alltså genom att anropa den överstyrda metoden i basklassen och sedan lägga till 5 procent extra till basresultatet. Ett exempel på hur dessa klasser används: ```python if __name__ == "__main__": diff --git a/data/osa-10/2-nakyvyysmaareet.md b/data/osa-10/2-nakyvyysmaareet.md index 7b5afb7a0..f76ca9d5d 100644 --- a/data/osa-10/2-nakyvyysmaareet.md +++ b/data/osa-10/2-nakyvyysmaareet.md @@ -1,19 +1,19 @@ --- path: '/osa-10/2-nakyvyysmaareet' -title: 'Näkyvyysmääreet' +title: 'Åtkomstmodifierare' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät mitä eroa on näkyvyysmääreillä yksityinen ja suojattu -- Tiedät, miten piirteiden näkyvyys määritetään Pythonissa +- Kommer du att förstå åtkomstmodifierarna privat och skyddad +- Vet du hur synligheten för egenskaper bestäms i Python -Aikaisemmin mainittiin, että yliluokassa yksityiseksi määritettyihin piirteisiin ei pääse käsiksi aliluokassa. Tarkastellaan esimerkkinä luokkaa `Muistikirja`, jossa muistiinpanojen säilyttämiseen käytettävä lista-attribuutti on piilotettu asiakkailta: +Om en egenskap definieras som privat i basklassen är den inte direkt åtkomlig i några härledda klasser, liksom kort nämndes i föregående avsnitt. Låt oss ta en titt på ett exempel. I klassen `Anteckningsbok` nedan lagras anteckningarna i en lista, och listattributet är privat: ```python @@ -35,7 +35,7 @@ class Muistikirja: ``` -Luokan sisäisen eheyden kannalta tietorakenteena toimivan listan piilottaminen asiakkaalta on sinänsä järkevää, koska luokka tarjoaa itse sopivat operaatiot muistiinpanojen lisäämiseksi ja selaamiseksi. Ongelmalliseksi tilanne muodostuu, jos yritetään kirjoittaa `Muistikirja`-luokan perivä luokka `ProMuistikirja`, johon halutaan lisätä muistiinpanojen etsiminen ja järjestäminen. Piilotettu attribuutti ei ole käytettävissä myöskään aliluokissa; metodi `etsi_muistiinpanot` antaa kutsuttaessa virheen: +Om klassens integritet är viktig är det vettigt att göra listattributen `anteckningar` privat. Klassen förser trots allt klienten med lämpliga metoder för att lägga till och bläddra i anteckningar. Detta tillvägagångssätt blir problematiskt om vi definierar en ny klass `AnteckningsbokPro`, som ärver `Anteckningsbok`-klassen. Det privata listattributet är inte tillgängligt för klienten, men det är inte heller tillgängligt för de härledda klasserna. Om vi försöker komma åt det, som i metoden `hitta_anteckningar` nedan, får vi ett felmeddelande: ```python class MuistikirjaPro(Muistikirja): @@ -66,11 +66,11 @@ AttributeError: 'MuistikirjaPro' object has no attribute '_MuistikirjaPro__muist -## Suojatut piirteet +## Skyddade egenskaper -Toisin kuin joistain muista ohjelmointikielistä, Pythonista ei suoraan löydy ominaisuutta joka piilottaa piirteet asiakkailta mutta samaan aikaan avaa ne mahdollisille aliluokille. Ratkaisuksi Python-yhteisö onkin päätynyt _konventioon_ eli yleisesti ymmärrettyyn merkintätapaan _suojatuille_ (eli _protected_) piirteille. +Många objektorienterade programmeringsspråk har en funktion, oftast ett speciellt nyckelord, för att skydda egenskaper. Detta innebär att en egenskap ska vara dold för klassens klienter, men hållas tillgänglig för dess underklasser. Python avskyr i allmänhet nyckelord, så ingen sådan funktion är direkt tillgänglig i Python. Istället finns det en konvention för att markera skyddade egenskaper på ett visst sätt. -Koska piirre voidaan piilottaa kirjoittamalla sen tunnisteen (eli nimen) eteen kaksi alaviivaa +Kom ihåg att en egenskap kan döljas genom att prefixera dess namn med två understreck: ```python @@ -79,7 +79,7 @@ def __init__(self): ``` -on yleisesti sovittu että yhdellä alaviivalla alkavat piirteet ovat tarkoitettu ainoastaan luokan ja sen aliluokkien käyttöön, eikä niitä tulisi käyttää suoraan sen ulkopuolelta. +Den överenskomna konventionen för att skydda en egenskap är att prefixera namnet med endast ett understreck. Nu är detta bara en konvention. Ingenting hindrar en programmerare från att bryta mot konventionen, men det anses vara en dålig programmeringspraxis. ```python @@ -88,7 +88,7 @@ def __init__(self): ``` -Alla on esitetty koko muistikirjaesimerkki uudestaan niin, että muistiinpanot on merkitty suojatuiksi yliluokassa yksityisen sijasta: +Nedan har vi hela Anteckningsbok-exemplet, med skyddade `_anteckningar` istället för privata `__anteckningar`: ```python @@ -127,7 +127,7 @@ class MuistikirjaPro(Muistikirja): ``` -Seuraavassa taulukossa on vielä esitetty piirteiden näkyvyys kaikkien eri suojausmääreiden tapauksessa: +Nedan har vi en praktisk tabell för synligheten av attribut med olika åtkomstmodifierare: Näkyvyysmääre | Esimerkki | Näkyy asiakkaalle | Näkyy aliluokalle :--:|:----:|:----:|:----: @@ -135,7 +135,7 @@ Julkinen | `self.nimi` | kyllä | kyllä Suojattu | `self._nimi` | ei | kyllä Yksityinen | `self.__nimi` | ei | ei -Näkyvyysmääreet toimivat vastaavasti kaikkien piirteiden kanssa. Luokassa Henkilo oleva metodi `isot_alkukirjaimet` on suojattu, joten sitä voi käyttää myös aliluokassa Jalkapalloilija: +Åtkomstmodifierare fungerar på samma sätt med alla egenskaper. I klassen `Person` nedan har vi till exempel den skyddade metoden `kapitalisera_initialer` Den kan användas från den härledda klassen `Fotbollsspelare`: ```python diff --git a/data/osa-10/3-olio-ohjelmoinnin-tekniikoita.md b/data/osa-10/3-olio-ohjelmoinnin-tekniikoita.md index b9029e8e9..bd6213a1d 100644 --- a/data/osa-10/3-olio-ohjelmoinnin-tekniikoita.md +++ b/data/osa-10/3-olio-ohjelmoinnin-tekniikoita.md @@ -1,6 +1,6 @@ --- path: '/osa-10/3-olio-ohjelmoinnin-tekniikoita' -title: 'Olio-ohjelmoinnin tekniikoita' +title: 'Objektorienterade programmeringstekniker' hidden: false --- @@ -8,13 +8,13 @@ hidden: false Tämän osion jälkeen -- Tunnet muuttujan self eri käyttötarkoituksia -- Osaat ylikuormittaa operaattoreita omissa luokissa -- Tiedät miten muodostaa iteroitavan luokan +- Känner du till några av de olika användningsområdena för variabelnamnet `self` +- Vet du hur du överbelastar operatorer i dina egna klasser +- Kommer du att kunna skapa en iterabel klass -Luokka voi palauttaa metodista myös sen itsensä tyyppisen olion. Luokan `Tuote` metodi `alennustuote` palauttaa uuden tuotteen, jolla on sama nimi kuin nykyisellä tuotteella, mutta 25% halvempi hinta: +En klass kan innehålla en metod som returnerar ett objekt av samma klass. Nedan har vi t.ex. klassen `Produkt`, vars metod `produkt_på_rea` returnerar ett nytt Produkt-objekt med samma namn som det ursprungliga men med ett pris som är 25 % lägre: ```python class Tuote: @@ -44,7 +44,7 @@ Omena (hinta 2.2425) -Kerrataan vielä muuttujan `self` merkitys: luokan sisällä se viittaa nykyiseen olioon. Tyypillinen tapa käyttää muuttujaa onkin viitata olion omiin piirteisiin, esimerkiksi attribuuttien arvoihin. Muuttujaa voidaan käyttää myös palauttamaan koko olio (vaikka tälle onkin selvästi harvemmin tarvetta). Esimerkkiluokan `Tuote` metodi `halvempi` osaa palauttaa halvemman tuotteen, kun sille annetaan parametriksi toinen `Tuote`-luokan olio: +Låt oss gå igenom syftet med variabeln `self`: inom en klassdefinition hänvisar den till själva objektet. Vanligtvis används den för att hänvisa till objektets egna egenskaper, dess attribut och metoder. Variabeln kan också användas för att hänvisa till hela objektet, till exempel om själva objektet måste returneras till klientkoden. I exemplet nedan har vi lagt till metoden `billigare` i klassdefinitionen. Den tar en annan Produkt som sitt argument och returnerar det billigare av de två: ```python class Tuote: @@ -82,13 +82,13 @@ Appelsiini (3.95) -Esimerkin vertailun toteutus vaikuttaa kuitenkin melko kömpelöltä - paljon parempi olisi, jos voisimme vertailla `Tuote`-olioita suoraan Pythonin vertailuoperaattoreilla. +Även om det här fungerar bra är det ett mycket specialiserat fall av att jämföra två objekt. Det skulle vara bättre om vi kunde använda Pythons jämförelseoperatorer direkt på dessa `Produkt`-objekt. -## Operaattorien ylikuormitus +## Överbelastande av operatorer -Pythonin lasku- ja vertailuoperaattorien käyttö omien olioiden kanssa on onneksi mahdollista. Tähän käytetään tekniikkaa, jonka nimi on _operaattorien ylikuormitus_. Kun halutaan, että tietty operaattori toimii myös omasta luokasta muodostettujen olioiden kanssa, luokkaan kirjoitetaan vastaava metodi joka palauttaa oikean lopputuloksen. Periaate on vastaava kuin metodin `__str__` kanssa: Python osaa käyttää tietyllä tapaa nimettyjä metodeja tietyissä operaatioissa. +Python innehåller några speciellt namngivna inbyggda metoder för att arbeta med standardoperatorerna för aritmetik och jämförelse. Tekniken kallas operatörsöverbelastning. Om du vill kunna använda en viss operator på instanser av självdefinierade klasser kan du skriva en speciell metod som returnerar det korrekta resultatet av operatorn. Vi har redan använt den här tekniken med `__str__` metoden: Python vet att leta efter en metod som heter så här när en strängrepresentation av ett objekt efterfrågas. -Tarkastellaan ensin esimerkkiä, jossa `Tuote`-luokkaan on toteutettu metodi `__gt__` (lyhenne sanoista *g*reater *t*han) joka toteuttaa suurempi kuin -operaattorin. Tarkemmin sanottuna metodi palauttaa arvon `True`, jos nykyinen olio on suurempi kuin parametrina annettu olio. +Låt oss börja med operatorn > som talar om för oss om den första operanden är större än den andra. Klassdefinitionen `Produkt` nedan innehåller metoden `__gt__`, som är en förkortning av greater than. Denna speciellt namngivna metod ska returnera det korrekta resultatet av jämförelsen. Specifikt ska den returnera `True` om och endast om det aktuella objektet är större än det objekt som skickas som ett argument. De kriterier som används kan bestämmas av programmeraren. Med aktuellt objekt menar vi det objekt som metoden anropas på med punkt `.` notationen ```python class Tuote: @@ -107,9 +107,9 @@ class Tuote: return self.hinta > toinen_tuote.hinta ``` -Metodi `__gt__` palauttaa arvon `True`, jos nykyisen tuotteen hinta on suurempi kuin parametrina annetun tuotteen, ja muuten arvon `False`. +I implementationen ovan returnerar metoden `__gt__` `True` om priset på den aktuella produkten är högre än priset på den produkt som skickas som argument. I annat fall returnerar metoden `False`. -Nyt luokan olioita voidaan vertailla käyttäen `>`-operaattoria samalla tavalla kuin vaikkapa kokonaislukuja: +Nu finns jämförelseoperatorn `>` tillgänglig för användning med objekt av typen Produkt: ```python appelsiini = Tuote("Appelsiini", 4.90) @@ -127,7 +127,7 @@ Appelsiini on suurempi -Olioiden suuruusluokan vertailua toteuttaessa täytyy päättää, millä perusteella suuruusjärjestys määritetään. Voisimme myös haluta, että tuotteet järjestetään hinnan sijasta nimen mukaiseen aakkosjärjestykseen. Tällöin omena olisikin appelsiinia "suurempi": +Som nämnts ovan är det upp till programmeraren att bestämma vilka kriterier som ska gälla för att avgöra vad som är störst och vad som är minst. Vi kan t.ex. bestämma att ordningen inte ska baseras på pris, utan i stället ska vara alfabetisk enligt namn. Detta skulle innebära att `banan` nu skulle vara "större än" `apelsin`, eftersom "banan" kommer senare alfabetiskt. ```python class Tuote: @@ -166,9 +166,9 @@ Omena on suurempi -## Lisää operaattoreita +## Fler operatorer -Tavalliset vertailuoperaattorit ja näitä vastaavat metodit on esitetty seuraavassa taulukossa: +Här har vi en tabell som innehåller de vanliga jämförelseoperatorerna, tillsammans med de metoder som måste implementeras om vi vill göra dem tillgängliga för användning på våra objekt: Operaattori | Merkitys perinteisesti | Metodin nimi :--:|:--:|:--: @@ -179,7 +179,7 @@ Operaattori | Merkitys perinteisesti | Metodin nimi `<=` | Pienempi tai yhtäsuuri kuin | `__le__(self, toinen)` `>=` | Suurempi tai yhtäsuuri kuin | `__ge__(self, toinen)` -Lisäksi luokissa voidaan toteuttaa tiettyjä muita operaattoreita, esimerkiksi: +Du kan också implementera några andra operatorer, inklusive följande aritmetiska operatorer: Operaattori | Merkitys perinteisesti | Metodin nimi :--:|:--:|:--: @@ -189,11 +189,11 @@ Operaattori | Merkitys perinteisesti | Metodin nimi `/` | Jakaminen | `__truediv__(self, toinen)` `//` | Kokonaisjakaminen | `__floordiv__(self, toinen)` -Lisää operaattoreita ja metodien nimien vastineita löydät helposti Googlella. +Fler operatorer och metodnamn finns lätt att hitta på nätet. Kom också ihåg `dir`-instruktionen för att lista de metoder som är tillgängliga för användning på ett visst objekt. -Huomaa, että vain hyvin harvoin on tarvetta toteuttaa kaikkia operaatioita omassa luokassa. Esimerkiksi jakaminen on operaatio, jolle on hankalaa keksiä luontevaa käyttöä useimmissa luokissa (mitä tulee, kun jaetaan opiskelija kolmella saati toisella opiskelijalla?). Tiettyjen operaattoreiden toteuttamisesta voi kuitenkin olla hyötyä, mikäli vastaavat operaatiot ovat loogisia luokalle. +Det är mycket sällan nödvändigt att implementera alla aritmetiska operatorer och jämförelseoperatorer i dina egna klasser. Division är t.ex. en operation som sällan är meningsfull utanför numeriska objekt. Vad skulle resultatet av att dividera ett Student-objekt med tre, eller med ett annat Student-objekt bli? Trots detta är vissa av dessa operatorer ofta mycket användbara även i egna klasser. Valet av metoder att implementera beror på vad som är vettigt, med tanke på egenskaperna hos dina objekt. -Tarkastellaan esimerkkinä luokkaa joka mallintaa yhtä muistiinpanoa. Kahden muistiinpanon yhdistäminen `+`-operaattorilla tuottaa uuden, yhdistetyn muistiinpanon, kun on toteutettu metodi `__add__`: +Låt oss ta en titt på en klass som modellerar en enda anteckning. Om vi implementerar metoden `__add__` i vår klassdefinition blir additionsoperatorn + tillgänglig för våra Anteckning-objekt: ```python from datetime import datetime @@ -212,7 +212,7 @@ class Muistiinpano: uusi_muistiinpano.merkinta = self.merkinta + " ja " + toinen.merkinta return uusi_muistiinpano ``` - + ```python merkinta1 = Muistiinpano(datetime(2016, 12, 17), "Muista ostaa lahjoja") merkinta2 = Muistiinpano(datetime(2016, 12, 23), "Muista hakea kuusi") @@ -228,18 +228,18 @@ print(molemmat) -## Olion esitys merkkijonona +## En strängrepresentation av ett objekt -Olemme toteuttaneet luokkiin usein metodin `__str__`, joka antaa merkkijonoesityksen olion sisällöstä. Toinen melko samanlainen metodi on `__repr__`, joka antaa _teknisen_ esityksen olion sisällöstä. Usein metodi `__repr__` toteutetaan niin, että se antaa koodin, joka muodostaa olion. +Du har redan implementerat en hel del `__str__`-metoder i dina klasser. Som du vet returnerar metoden en strängrepresentation av objektet. En annan ganska liknande metod är `__repr__` som returnerar en teknisk representation av objektet. Metoden `__repr__` är ofta implementerad så att den returnerar den programkod som kan exekveras för att returnera ett objekt med identiskt innehåll som det aktuella objektet. -Funktio `repr` antaa olion teknisen merkkijonoesityksen, ja lisäksi tätä esitystä käytetään, jos oliossa ei ole määritelty `__str__`-metodia. Seuraava luokka esittelee asiaa: +Funktionen `repr` returnerar denna tekniska strängrepresentation av objektet. Den tekniska representationen används även när metoden `__str__` inte har definierats för objektet. Exemplet nedan kommer att göra detta tydligare: ```python class Henkilo: def __init__(self, nimi: str, ika: int): self.nimi = nimi self.ika = ika - + def __repr__(self): return f"Henkilo({repr(self.nimi)}, {self.ika})" ``` @@ -258,16 +258,16 @@ Henkilo('Pekka', 99) -Huomaa, että metodissa `__repr__` haetaan nimen tekninen esitys metodilla `repr`, jolloin tässä tapauksessa nimen ympärille tulee `'`-merkit. +Lägg märke till hur metoden `__repr__` själv använder `repr`-funktionen för att hämta den tekniska representationen av strängen. Detta är nödvändigt för att inkludera tecknen `'` i resultatet. -Seuraavassa luokassa on toteutettu sekä metodi `__repr__` että `__str__`: +Följande klass har definitioner för både `__repr__` och `__str__`: ```python class Henkilo: def __init__(self, nimi: str, ika: int): self.nimi = nimi self.ika = ika - + def __repr__(self): return f"Henkilo({repr(self.nimi)}, {self.ika})" @@ -288,7 +288,7 @@ Henkilo('Anna', 25) -Kun tietorakenteessa (kuten listassa) on olioita, Python käyttää vähän epäloogisesti metodia `__repr__` olioiden merkkijonoesityksen muodostamiseen, kun lista tulostetaan: +Det är värt att nämna att med datastrukturer, såsom listor, använder Python alltid `__repr__`-metoden för strängrepresentationen av innehållet. Detta kan ibland se lite förvirrande ut: ```python3 henkilot = [] @@ -524,9 +524,9 @@ print(p1-p3) -## Iteraattorit +## Iteratorer -Olemme aikaisemmin käyttäneet for-lausetta erilaisten tietorakenteiden ja tiedostojen _iterointiin_ eli läpikäyntiin. Tyypillinen tapaus olisi vaikkapa seuraavanlainen funktio: +Vi vet att `for`-satsen kan användas för att iterera genom många olika datastrukturer, filer och samlingar av objekt. Ett typiskt användningsfall skulle kunna vara följande funktion: ```python @@ -539,11 +539,11 @@ def laske_positiiviset(lista: list): ``` -Funktio käy läpi listan alkio kerrallaan ja laskee positiivisten alkioiden määärän. +Funktionen går igenom objekten i listan ett efter ett och håller reda på hur många av objekten som var positiva. -Iterointi on mahdollista toteuttaa myös omiin luokkiin. Hyödyllistä tämä on silloin, kun luokasta muodostetut oliot tallentavat kokoelman alkioita. Esimerkiksi aikaisemmin kirjoitettiin luokka, joka mallintaa kirjahyllyä – olisi näppärä, jos kaikki kirjahyllyn kirjat voisi käydä läpi yhdessä silmukassa. Samalla tavalla opiskelijarekisterin kaikkien opiskelijoiden läpikäynti for-lauseella olisi kätevää. +Det är också möjligt att göra sina egna klasser itererbara. Detta är användbart när klassens huvudsyfte handlar om att lagra en samling objekt. Klassen Bokhylla från ett tidigare exempel skulle vara en bra kandidat, eftersom det skulle vara vettigt att använda en `for`-loop för att gå igenom böckerna på hyllan. Detsamma gäller för, exempelvis, ett studentregister. Att kunna iterera genom samlingen av studenter kan vara användbart. -Iterointi mahdollistuu toteuttamalla luokkaan iteraattorimetodit `__iter__` ja `__next__`. Käsitellään metodien toimintaa tarkemmin, kun on ensin tarkasteltu esimerkkinä kirjahyllyluokkaa, joka mahdollistaa kirjojen läpikäynnin: +För att göra en klass itererbar måste du implementera iteratormetoderna `__iter__` och `__next__`. Vi återkommer till detaljerna i dessa metoder efter följande exempel: ```python @@ -585,9 +585,11 @@ class Kirjahylly: ``` -Metodissa `__iter__` siis alustetaan iteroinnissa tarvittava muuttuja tai muuttujat - tässä tapauksessa riittää, että meillä on laskuri joka osoittaa listan nykyiseen alkioon. Lisäksi tarvitaan metodi `__next__`, joka palauttaa seuraavan alkion. Esimerkkitapauksessa palautetaan listasta alkio muuttujan `n` kohdalta ja kasvatetaan muuttujan arvoa yhdellä. Jos listassa ei ole enempää alkiota, "nostetaan" poikkeus `StopIteration`, joka kertoo iteroijalle (esim. for-silmukalle), että kaikki alkiot on käyty läpi. +Metoden `__iter__` initialiserar iterationsvariabeln eller variablerna. I det här fallet räcker det med att ha en enkel räknare som innehåller index för det aktuella objektet i listan. Vi behöver också metoden `__next__`, som returnerar nästa objekt i iteratorn. I exemplet ovan returnerar metoden objektet med index n från listan i Bokhylla-objektet, och iteratorvariabeln inkrementeras också. + +När alla objekt har genomgåtts utlöser metoden `__next__` undantaget `StopIteration`. Processen skiljer sig inte från andra undantag, men det här undantaget hanteras automatiskt av Python och dess syfte är att signalera till koden som anropar iteratorn (t.ex. en `for`-loop) att iterationen nu är över. -Nyt voidaan käydä kirjahyllyn kirjat läpi esimerkiksi for-silmukassa näppärästi: +Vår bokhylla är nu redo för iteration, till exempel med en `for`-loop: ```python diff --git a/data/osa-10/4-laajempi-sovellus.md b/data/osa-10/4-laajempi-sovellus.md index 16d0da294..e764d8033 100644 --- a/data/osa-10/4-laajempi-sovellus.md +++ b/data/osa-10/4-laajempi-sovellus.md @@ -1,67 +1,68 @@ --- path: '/osa-10/4-lisaa-esimerkkeja' -title: 'Laajemman sovelluksen kehittäminen' +title: 'Att utveckla en större applikation ' hidden: false --- - + -Tässä osiossa +Efter den här delen -- Käydään läpi hieman laajemman sovelluksen tekemiseen liittyviä seikkoja -- Erityinen fokus on sovelluksen eri osa-alueiden (käyttöliittymä, sovelluslogiikka ja tiedostojen käsittely) eriyttämisessä -- Harjoitellaan laajemman sovelluksen toteuttamista itse +- Känner du till några grundläggande principer för applikationsutveckling +- Kommer du att känna dig bekväm med att skilja mellan de olika delarna av en applikation (användargränssnitt, programlogik och filhantering) +- Har du övat på att skriva din egna lite större applikation -Ohjelmoinnin perusteiden ja jatkokurssin aikana on esitelty suuri määrä Pythonin tarjoamia ominaisuuksia. +Hittills i detta kursmaterial har vi gått igenom ett stort antal Python-funktioner. -Ohjelmoinnin perusteissa tutustuttiin kielen kontrollirakenteisiin (while ja for), funktioihin sekä perustietorakenteisiin eli listaan ja sanakirjaan. Näytti jo hetken siltä että muuta ei tarvitakaan. Periaatteessa näin onkin: ohjelmoinnin perusteiden kalustolla pystyy ilmaisemaan kaiken mikä Pythonilla on ylipäätään ilmaistavissa. +I Introduktion till programmering-kursen introducerades kontrollstrukturer som while och for, funktioner samt grundläggande datastrukturer som listor, tupler och ordlistor. I princip är dessa verktyg allt som behövs för att uttrycka vad som helst som en programmerare kan tänka sig vilja uttrycka med Python. -Jatkokurssin alussa, eli kurssin osassa 8 pakkaa ruvettiin kuitenkin hämmentämään tuomalla mukaan luokat ja oliot. Milloin ja ylipäätään _miksi_ olioita tulisi käyttää jos kurssin osien 1-7 kalusto on jo ilmaisuvoimaltaan riittävä? +På denna avancerade kurs i programmering, med början i del 8 av materialet, har du blivit bekant med klasser och objekt. Låt oss ta en stund att fundera på när och varför de är nödvändiga, ifall de grundläggande verktygen från del 1 till 7 borde räcka. -## Monimutkaisuuden hallintaa +## Att hantera komplexitet -Monissa tilanteissa voi ja varmasti kannattaakin olla käyttämättä oliota. Esimerkiksi jos koodataan pieni "kertakäyttöinen" apuohjelma, ei ehkä ole mitään tarvetta olioille. Tilanne alkaa muuttua, kun siirrytään hieman suuremman kokoluokan ohjelmiin. +Objekt och klasser är långt ifrån nödvändiga i alla programmeringssammanhang. Om du t.ex. programmerar ett litet skript för engångsbruk är objekt oftast överflödiga. Men när du ska programmera något större och mer komplicerat blir objekten mycket användbara. -Kun ohjelma laajenee, alkaa sen sisältämien yksityiskohtien määrä nousta hallitsemattomaksi, ellei ohjelmaa jäsennellä jollain järkevällä tavalla. Itse asiassa jo ohjelmoinnin perusteiden tehtävissä oli havaittavissa varsin monimutkaisia ratkaisuja, joiden ymmärtämisessä jopa alan ammattilaisilla on vaikeuksia. +När programmen blir allt mer komplexa blir mängden detaljer snabbt ohanterlig, såvida inte programmet är organiserat på något systematiskt sätt. Även några av de mer komplicerade övningarna på den här kursen hittills skulle ha haft nytta av de exempel som ges i den här delen av materialet. -Käsite [Separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) on ollut jo vuosikymmeniä eräs ohjelmoinnin ja koko tietojenkäsittelyn keskeisiä teemoja. Wikipedian mukaan käsitteellä tarkoitetaan seuraavaa +I flera decennier har begreppet [Separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) varit en av de centrala principerna inom programmering och inom datavetenskapen i stort. Citat från Wikipedia: _Separation of concerns is a design principle for separating a computer program into distinct sections such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program_ -Kyse on ohjelman suunnittelua ohjaavasta periaatteesta, jonka mukaan ohjelmakoodi jäsennellään pienempiin osiin, joista kukin huolehtii vain omasta "tontistaan". Kuhunkin osaan tehdyt muutokset vaikuttavat - periaatteen mukaisesti - vain rajattuun alueeseen ohjelmassa, joten ohjelmien väistämätöntä monimutkaisuutta on helpompi hallita. +Genom att dela upp programmet i olika delar, så att varje del har sina egna problem att hantera, blir det lättare att hantera den oundvikliga komplexiteten i ett datorprogram. -Funktiot ovat yksi mekanismi tämän tavoitteen saavuttamiseen. Sen sijaan että ohjelma kirjoitetaan yhtenä isona kokonaisuutena, koostetaan se pienistä funktioista, joista kukin ratkaisee pienen osan ongelmasta. +Funktioner är ett sätt att organisera ett program i distinkta, hanterbara helheter. I stället för att skriva ett enda skript är tanken att formulera små, separat verifierbara funktioner som var och en löser en del av det större problemet. -Olio-ohjelmointi tarjoaa funktioita jossain määrin ilmaisuvoimaisemman ja joidenkin mielestä "paremman" tavan saavuttaa sama tavoite. Kuten olemme nähneet, olioiden avulla on mahdollista koota samaan asiaan liittyvä data ja sitä käsittelevä koodi, eli olion metodit, samaan paikkaan. Oliot tarjoavat myös mekanismin käsittelemänsä datan kapselointiin, joka taas tavallaan on keino piilottaa "turhia" yksityiskohtia olion ulkopuoliselta osalta ohjelmaa. +En annan vanlig metod för att hantera större program är objekt, genom objektorienterade programmeringsprinciper. Det finns fördelar och nackdelar med båda metoderna, och varje programmerare har sin egen favorit. Som vi har sett hittills kan vi med hjälp av objekt och klasser samla alla data och den kod som bearbetar dessa data i en enda enhet, i ett objekts attribut och metoder. Dessutom ger objekten ett sätt att kapsla in de data de kontrollerar, så att andra delar av programmet inte behöver oroa sig för de interna detaljerna i ett objekt. -## Esimerkki: puhelinluettelo +## Ett fungerande exempel: telefonkatalog -Miten ohjelma sitten tulisi jakaa luokkiin ja olioihin? Kysymys ei ole helppo, ja asiaa on helpompi pohdiskella konkreettisen esimerkin kautta. Toteutetaan esimerkkinä olio-ohjelmointia hyödyntäen hieman samantyylinen puhelinluettelo, joka oli aiheena ohjelmoinnin perusteiden viidennen osan [tehtävässä](/osa-5/3-dictionary#programming-exercise-puhelinluettelo-versio-2). +Hur ska ett program delas in i klasser och objekt? Det här är inte alls en enkel fråga med ett enda godtagbart svar, så vi fortsätter med ett exempel. I del fem genomförde du en [telefonkatalogsapplikation](/osa-5/3-dictionary#programming-exercise-puhelinluettelo-versio-2), och nu ska vi genomföra något liknande med hjälp av objektorienterade programmeringsprinciper. -Separation of concerns -periaatetta noudatellen koodi tulee jakaa osiin, joista kukin käsittelee omaa asiaansa. Olio-ohjelmoinnin piirissä tätä periaatetta ilmentää niin sanottu [yhden vastuun (single responsibility)](https://en.wikipedia.org/wiki/Single-responsibility_principle) -periaate. Ei mennä sen tarkemmin periaatteen yksityiskohtiin, mutta maalaisjärjellä ajatellen periaatteen nimi jo kertoo mistä on kyse: _yksittäisen luokan olioiden tulisi olla vastuussa yhdestä asiasta_. +Enligt principen om separation of concerns bör ett program delas upp i sektioner som var och en har sin egen sak att ta hand om. I objektorienterad programmering översätts detta till [principen om ett ansvar](https://en.wikipedia.org/wiki/Single-responsibility_principle). Utan att gå in på detaljerna framgår det grundläggande syftet redan av namnet: en enda klass och de objekt som skapas utifrån den ska ha ett enda ansvar i programmet. -Olioita käytettäessä ohjelmointiongelman "reaalimaailman asioita" vastaa yleensä oma luokkansa. Puhelinluettelon tapauksessa tälläisiä reaalimaailman asioita olisivat esimerkiksi: -- henkilö -- nimi -- puhelinnumero -Näistä nimi ja puhelinnumero ovat kenties liian vähäpätöisiä ollakseen omia luokkiaan, mutta _henkilö_ voisi hyvinkin olla oma luokkansa, jonka vastuulla on sitoa yhteen tietty nimi ja siihen liittyvät puhelinnumerot. +Objektorienterad programmering används ofta som ett sätt att modellera objekt och fenomen i den verkliga världen. Ett enskilt objekt i den verkliga världen modelleras med en enda klass i programkoden. I fallet med en telefonkatalog kan sådana objekt vara +- en person +- ett namn +- ett telefonnummer -Myös _puhelinluettelo_ itsessään on potentiaalinen luokka, sen vastuulla on hallinnoida eri henkilöiden tietoja. +Ett namn och ett telefonnummer kan uppfattas som data som inte förtjänar egna klasser, men en person är en distinkt fysisk enhet i den verkliga världen, och i programmeringsvärlden skulle den kunna fungera som en klass. Ett Person-objekt skulle vara ansvarigt för att knyta ihop ett namn och de telefonnummer som är kopplade till det. -Nämä kaksi luokkaa eli _puhelinluettelo_ ja _henkilö_ muodostavat sovelluksen ytimen, eli niin sanotun _sovelluslogiikan_. Näiden lisäksi ohjelma tarvitsee muutaman muunkin luokan. +En telefonkatalog i sig skulle kunna vara en bra kandidat för en klass. Dess ansvar skulle vara att hantera olika personobjekt och de data de innehåller. -Käyttäjän kanssa tapahtuvasta interaktiosta huolehtivaa luokkaa ei kannata sotkea sovelluslogiikan kanssa samaan luokkaan - sehän on kokonaan oma vastuunsa. Eli sovelluslogiikan luokkien lisäksi ohjelmalle tulee myös luokka, joka huolehtii ohjelman käyttöliittymästä. +Nu har vi skissat kärnan i vår applikation: telefonkatalog och person utgör programmeringslogiken i vår applikation, eller applikationslogiken i korthet. Vår applikation skulle behöva några andra klasser också. -Talletamme puhelinluettelon tiedot tiedostoon. Myös tiedoston käsittely on selkeästi oma vastuunsa, joten tulemme sisällyttämään siihen käytettävän koodin omaan luokkaansa. +Det är oftast en bra idé att hålla all interaktion med en användare skild från applikationslogiken. Det är ju trots allt ett helt eget ansvar. Förutom den centrala applikationslogiken bör vårt program därför innehålla en klass som hanterar användargränssnittet. -Kun ohjelman luokkarakenne alkaa pikkuhiljaa hahmottua, nousee kysymykseksi se, mistä ohjelmointi kannattaa aloittaa. Usein paras tapa aloittaa on pienellä palalla sovelluslogiikka. +Dessutom bör vår telefonkatalog ha någon form av beständig lagring mellan exekveringar. Filhanteringen är återigen ett tydligt separat ansvar, så den förtjänar en egen klass. -## Vaihe 1: sovelluslogiikan runko +Nu när vi har en översikt över de grundläggande komponenterna i vårt program uppstår frågan: var ska vi börja programmera? Inte heller här finns det något rätt eller fel svar, men det är ofta en bra idé att börja med någon del av programlogiken. -Aloitetaan luokasta _Puhelinluettelo_. Runko voisi näyttää seuraavalta: +## Steg 1: en skiss för applikationslogiken + +Låt oss börja med klassen Telefonkatalog. En skelettimplementering skulle kunna se ut så här: ```python class Puhelinluettelo: @@ -76,11 +77,11 @@ class Puhelinluettelo: ``` -Luokka pitää siis sisällään listan henkilöitä ja tarjoaa metodit tietojen lisäämiseen ja hakemiseen. +Denna klass består av en lista med personer samt metoder för att både lägga till och hämta data. -Jokaiseen henkilöön voi liittyä useita numeroita, joten toteutetaan luettelon sisäinen tila sanakirjan avulla, koska sanakirjasta on helppo hakea nimen perusteella. Sanakirjaan on helppo tallettaa suoraan myös nimeen liittyvät numerot, joten ainakaan tässä vaiheessa ei tarvita erillistä luokkaa yksittäisen henkilön tietojen tallettamiseen. +Varje person kan vara kopplad till flera nummer, så låt oss implementera den interna strukturen för `personer` med en ordlista. En ordlista ger oss möjlighet att söka efter nycklar enligt namn, och värdet som är kopplat till en ordlistas nyckel kan vara en lista. Hittills ser det ut som att vi inte behöver en separat klass för att representera en person – ett inlägg i en ordlista räcker. -Luokka laajenee seuraavasti. Mukana on myös pieni toiminnan varmistava koodinpätkä: +Låt oss implementera de metoder som listas ovan och testa vår telefonkatalog: ```python class Puhelinluettelo: @@ -107,7 +108,7 @@ print(luettelo.hae_numerot("Erkki")) print(luettelo.hae_numerot("Emilia")) ``` -Testikoodin tulostus on seuraava: +Detta borde utskriva följande: @@ -116,16 +117,15 @@ None -Metodi `hae_numerot` siis palauttaa arvon `None`, jos henkilö ei löydy luettelosta, jos henkilö löytyy, palautetaan lista joka sisältää henkilön puhelinnumerot. +Metoden `hamta_nummer` returnerar `None` om ett namn inte finns med i telefonkatalogen. Om namnet finns returneras listan med de nummer som är kopplade till namnet. -Ohjelmoidessa mitä tahansa ohjelmaa kannattaa _aina_ kokeilla, että koodi toimii kuten sen olettaa toimivan, ennen kun etenee muuhun koodiin. -Usein tämä testikoodi on poisheitettävää koodia, ja sikäli voisi ajatella testaamisesta olevan ylimääräistä vaivaa. Lähes 100% tapauksissa ei näin kuitenkaan ole. +När man gör ändringar i ett program är det alltid värt att testa att koden fungerar som förväntat innan man går vidare med andra ändringar. Den kod som används för testning är vanligtvis något som raderas strax efteråt, och därför kanske du tycker att det inte är värt besväret att skriva några tester i första hand. I de flesta fall är detta inte sant. Testning är en förutsättning för bra programmeringsresultat. -Koodiin tullut bugi kannattaa saada kiinni ja korjata niin pian kuin mahdollista. Jos koodin toimivuuden varmistaa lähes jokaisen uuden koodirivin jälkeen, on debuggaus ja korjaaminen yleensä vaivatonta ja nopeaa, koska tällöin voi olla melko varma siitä, että ongelma johtuu hetki sitten lisätyistä koodiriveistä. Jos taas koodia testataan vasta sen jälkeen kun siihen on lisätty kymmeniä koodirivejä, on virhelähteitä moninkertaisesti. +En bugg i programmet bör fångas upp och åtgärdas så snart som möjligt. Om du tar för vana att kontrollera funktionaliteten i praktiskt taget varje ny kodrad kommer du att upptäcka att buggarna oftast är lätta att hitta och åtgärda, eftersom du kan vara helt säker på att buggen orsakades av den senaste ändringen. Om du bara testar programmet efter att ha lagt till dussintals rader kod ökar de potentiella källorna till buggar också med dussintals gånger. -## Vaihe 2: käyttöliittymän runko +## Steg 2: en skiss för användargränssnittet -Kun sovelluslogiikan ydintoiminnallisuus on kunnossa, voidaan edetä sovelluksen tekstikäyttöliittymään. Tehdään sitä varten oma luokka `PuhelinluetteloSovellus`, jonka runko on seuraava: +Med den grundläggande applikationslogiken ur vägen är det dags att implementera ett textbaserat användargränssnitt. Vi kommer att behöva en ny klass, `TelefonkatalogApplikation`, med följande inledande funktionalitet: ```python class PuhelinluetteloSovellus: @@ -148,9 +148,9 @@ sovellus = PuhelinluetteloSovellus() sovellus.suorita() ``` -Luokka saattaa vaikuttaa vielä hämmentävältä, mutta tässä luodaan tosiaan vasta runko toiminnalle. Konstruktori luo puhelinluettelon, jonka olio pitää sisällään. Metodi `suorita(self)` käynnistää sovelluksen tekstikäyttöliittymän, jonka ytimen muodostaa `while`-silmukka, joka kyselee käyttäjältä mikä komento halutaan suorittaa. Ennen toistolauseeseen menemistä ohjelma tulostaa käyttöohjeet, kutsumalla metodia `ohje(self)`. Varsinaiset toiminnot toteutetaan seuraavaksi. +Det här programmet gör inte så mycket ännu, men låt oss gå igenom innehållet. Konstruktormetoden skapar en ny Telefonkatalog, som lagras i ett privat attribut. Metoden `exekvera(self)` startar programmets textbaserade användargränssnitt, vars kärna är `while`-loopen, som fortsätter att be användaren om instruktioner tills de skriver in instruktionen för att avsluta. Det finns också en metod för instruktioner, `hjalp(self)`, som anropas innan man går in i loopen, så att instruktionerna skrivs ut. -Laajennetaan käyttöliittymää siten, että luetteloon voidaan lisätä uusia tietoja: +Låt oss nu lägga till lite faktisk funktionalitet. Först implementerar vi att kunna lägga till nya data i telefonkatalogen: ```python class PuhelinluetteloSovellus: @@ -178,11 +178,11 @@ sovellus = PuhelinluetteloSovellus() sovellus.suorita() ``` -Jos valittu komento on tietojen lisäys (eli komento on _1_), kysyy käyttöliittymä nimen ja numeron käyttäjältä, ja lisää tiedot puhelinluetteloon kutsumalla sopivaa luettelon metodia. +Om användaren skriver in 1 för att lägga till ett nytt nummer, frågar användargränssnittet efter ett namn och ett nummer och lägger till dessa i telefonkatalogen med hjälp av den lämpliga metod som definieras i klassen. -Käyttöliittymä on siis vastuussa ainoastaan siitä, että se kommunikoi käyttäjän kanssa. Puhelinnumeron säilöminen nimen yhteyteen on jätetty kokonaisuudessan _Puhelinluettelo_-olion vastuulle. +Användargränssnittets enda ansvar är att kommunicera med användaren. All annan funktionalitet, t.ex. att lagra ett nytt namn- och nummerpar, är Telefonkatalog-objektets ansvar. -Käyttöliittymän rakennetta on mahdollista vielä parannella siten, että tietojen lisäys eriytetään omaan metodiinsa _lisays(self)_: +Det finns utrymme för förbättringar i strukturen i vår användargränssnittsklass. Låt oss skapa en metod `tillägg_inlägg(self)` som hanterar instruktionen för att lägga till ett nytt inlägg: ```python class PuhelinluetteloSovellus: @@ -214,9 +214,9 @@ sovellus = PuhelinluetteloSovellus() sovellus.suorita() ``` -Erillisen metodin käyttämisen taustallakin on sama _separation of concerns_ -periaate. Sen sijaan että koko käyttöliittymän toiminnallisuus sijoitettaisiin ison `while`-silmukan sisälle, tehdään jokaisesta yksittäisestä toiminnosta oma metodinsa. Tämä helpottaa kokonaisuuden hallintaa. Jos halutaan muokata tietojen lisäämisen toiminnallisuutta, tiedetään heti missä päin relevantti koodi sijaitsee. +Separation of concerns-principen sträcker sig även till metodnivå. Vi skulle kunna ha hela användargränssnittets funktionalitet i en enda komplicerad `while`-loop, men det är bättre att separera varje funktionalitet i en egen metod. Ansvaret för `exekvera()`-metoden är bara att delegera de instruktioner som användaren skriver in till relevanta metoder. Detta hjälper till att hantera den växande komplexiteten i vårt program. Om vi till exempel senare vill ändra hur det fungerar att lägga till inlägg, är det omedelbart klart att vi då måste fokusera våra ansträngningar på `tillägg_inlägg()`-metoden. -Lisätään käyttöliittymään toiminnallisuus numeroiden hakemista varten. Sijoitetaan sen hoitava koodi heti omaan metodiinsa: +Låt oss inkludera funktionalitet för att söka efter inlägg i vårt användargränssnitt. Detta bör också ha sin egen metod: ```python @@ -262,7 +262,7 @@ sovellus = PuhelinluetteloSovellus() sovellus.suorita() ``` -Sovelluksen perusversio toimii nyt. Seuraavassa esimerkki sovelluksen käytöstä: +Vi har nu en enkel fungerande telefonkatalogsapplikation som är redo för testning. Följande är ett exempel på en körning: @@ -292,18 +292,18 @@ komento: **0** -Koodia on aika paljon, todennäköisesti enemmän kuin jos kaikki olisi ohjelmoitu yhteen pötköön. Koodin rakenne on kuitenkin siistihkö, ja koodin laajentamisenkaan ei pitäisi olla kovin hankalaa. +För en så enkel applikation har vi skrivit ganska mycket kod. Om vi hade skrivit allt i den enda `while`-loopen hade vi förmodligen kunnat komma undan med mycket mindre kod. Det är dock ganska lätt att läsa koden, strukturen är tydlig och vi borde inte ha några problem med att lägga till nya funktioner. -## Vaihe 3: tietojen haku tiedostosta +## Steg 3: Importera data från en fil -Laajennetaan ohjelmaa siten, että se lataa käynnistäessään puhelinluettelon tiedostosta, joka on seuraavaa muotoa: +Låt oss anta att vi redan har några telefonnummer lagrade i en fil, och att vi vill läsa detta när programmet startar. Datafilen är i följande CSV-format: ```csv Erkki;02-1234567;045-4356713 Emilia;040-324344 ``` -Tiedoston käsittely on selkeästi oma vastuualueensa, eli toteutetaan sitä varten oma luokka: +Hantering av filer är helt klart ett eget ansvarsområde, så det förtjänar en klass för sig: ```python class Tiedostonkasittelija(): @@ -321,9 +321,9 @@ class Tiedostonkasittelija(): return nimet ``` -Konstruktorin parametrina annetaan tiedoston nimi. Metodi `lataa(self)` lukee tiedoston, ja pilkkoo sen rivit sanakirjaksi, missä avain on nimi ja arvona ovat nimeen liittyvät numerot. +Konstruktörsmetoden tar namnet på filen som sitt argument. Metoden `ladda_fil(self)` läser innehållet i filen. Varje rad delas upp i två delar: ett namn och en lista med siffror. Sedan läggs dessa till i en ordbok, med namnet som nyckel och listan som värde. -Metodi käyttää erästä Pythonin kätevää ominaisuutta: listasta on mahdollista ottaa ensin yksittäisiä alkioita erikseen nimettyinä muuttujina, sekä loput alkiot uutena listana, kuten seuraavasta esimerkistä käy ilmi. [Luvusta 6](osa-6/1-tiedostojen-lukeminen#csv-tiedoston-lukeminen) muistamme että merkkijonojen metodi `split` tuottaa nimenomaan listan. +Metoden använder en smidig Python-funktion: det är möjligt att först välja några objekt från en lista separat och sedan ta resten av objekten i en ny lista. Du kan se ett exempel på detta nedan. Du kanske minns från [del 6](osa-6/1-tiedostojen-lukeminen#csv-tiedoston-lukeminen) att strängmetoden `split` returnerar en lista. ```python lista = [1, 2, 3, 4, 5] @@ -341,9 +341,9 @@ print(loput) -Sijoituslauseen viimeisen muuttujan nimen edessä on *, ja se tarkoittaa, että viimeiseen muuttujaan kerätään taulukosta loput, eli kolmas ja sitä seuraavat alkiot. +Tecknet `*` framför variabelnamnet `resten` i ovanstående exempel betyder att den sista variabeln ska innehålla alla återstående poster i listan, från den tredje och framåt. -Tiedostonkäsittelijääkin kannattaa ehdottomasti testata, ennen kuin se pultataan muuhun koodiin: +Vi bör absolut testa filhanteraren separat innan vi inkluderar den i vår applikation: ```python t = Tiedostonkasittelija("luettelo.txt") @@ -356,7 +356,7 @@ print(t.lataa()) -Kun tiedostosta lukemisen todetaan toimivan, liitetään koodi muuhun ohjelmaan. Looginen paikka tiedoston lukemiseen on se hetki kun sovellus käynnistyy, eli luokan _PuhelinluetteloSovellus_ konstruktori: +Eftersom filhanteraren verkar fungera bra kan vi lägga till den i vår applikation. Låt oss anta att vi vill läsa filen som det första varje gång programmet körs. Den logiska platsen för att läsa filen skulle vara konstruktören för klassen `TelefonkatalogApplikation`: ```python class PuhelinluetteloSovellus: @@ -372,13 +372,13 @@ class PuhelinluetteloSovellus: # muu koodi ``` -Tiedoston lukua osana PuhelinLuetteloSovellusta kannattaa myös testata. Kun on varmistettu, että tiedoston sisältö saadaan ladattua luetteloon, voidaan edetä viimeiseen vaiheeseen. +Denna funktionalitet bör också testas. När vi har försäkrat oss om att filens innehåll är tillgängligt via användargränssnittet i vår applikation kan vi gå vidare till nästa steg. -## Vaihe 4: tietojen talletus tiedostoon +## Steg 4: exportera data till en fil -Viimeistellään ohjelman alustava versio vielä siten, että se tallentaa lopetettaessa puhelinluettelon takaisin tiedostoon. +Den sista funktionen i vår grundversion av applikationen är att spara innehållet i telefonkatalogen tillbaka i samma fil som data lästes från. -Tätä varten luokkaa _Puhelinluettelo_ tulee laajentaa siten, että sieltä saadaan tallennusta varten kaikki tiedot ulos: +Detta innebär en förändring av `Telefonkatalog`-klassen. Vi måste kunna exportera innehållet i telefonkatalogen: ```python class Puhelinluettelo: @@ -392,7 +392,7 @@ class Puhelinluettelo: return self.__henkilot ``` -Tallennus on luonnollisesti luokan _Tiedostonkasittelija_ vastuulla, eli laajennetaan sitä metodilla _talleta_, joka saa parametriksi puhelinluetteloa edustavan sanakirjan: +Själva sparandet till filen bör hanteras av `FilHanterare`-klassen. Låt oss lägga till metoden `spara_fil` som tar en ordlistsrepresentation av telefonkatalogen som argument: ```python class Tiedostonkasittelija(): @@ -409,7 +409,7 @@ class Tiedostonkasittelija(): f.write(";".join(rivi) + "\n") ``` -Tallennus tapahtuu samalla kun sovelluksen käyttö lopetetaan. Tehdään tätäkin tarkoitusta varten oma metodinsa ja kutsutaan sitä sopivassa kohdassa: +Sparandet bör ske när programmet avslutas. Låt oss lägga till en metod för detta ändamål i användargränssnittet och anropa den innan vi bryter ut ur `while`-loopen: ```python @@ -473,13 +473,13 @@ Tee laajennus sitten, että kunnioitat ohjelman rakennetta. Eli lisää luokkaan -## Olioita sanakirjassa +## Objekt i en ordlista -Seuraavassa tehtävässä on tarkoitus muuttaa puhelinluetteloa siten, että sanakirjan arvoksi talletetaan tavallisten listojen sijaan _olioita_. +I nästa övning ombeds du att ändra din telefonkatalog så att värdena i ordlistan är objekt, inte listor. -Periaatteessa asiassa ei ole mitään ihmeellistä, mutta kurssilla ei vielä ole näin tehty, joten tutkitaan ennen tehtävää hieman samantapaista, mutta yksinkertaisempaa esimerkkiä. +Det är inget konstigt med det i sig, men det är första gången på den här kursen som något sådant föreslås, så låt oss gå igenom ett enklare exempel innan vi dyker ner i övningen. -Tehdään sovellus, jonka avulla voidaan pitää kirjaa siitä, kuinka monta tehtävää opiskelijat ovat tehneet kurssin aikana. Kunkin opiskelijan tehtävämäärä lasketaan yksinkertaisen olion avulla: +Här har vi en applikation som håller reda på hur många övningar som studenterna har gjort på en kurs. Varje elevs antal övningar lagras i ett enkelt objekt: ```python class Tehtavalaskuri: @@ -493,7 +493,7 @@ class Tehtavalaskuri: return self.__tehtavia ``` -Luokkaa käyttävä pääohjelma on seuraavassa: +Följande huvudfunktion använder klassen ovan: ```python opiskelijat = {} @@ -518,7 +518,7 @@ for opiskelija, tehtavat in opiskelijat.items(): print(f"{opiskelija} tehtäviä {tehtavat.tehtyja()} kpl") ``` -Käyttöesimerkki +Att exekvera koden ovan kunde se ut enligt följande: @@ -541,29 +541,29 @@ juuso tehtäviä 2 kpl -Esimerkissä on parikin huomionarvoista seikkaa. Kun opiskelijan nimi syötetään, tarkastetaan aina ensin onko opiskelijaa vastaava olio jo sanakirjassa. Jos olioa ei ole, luodaan se: +Det finns ett par saker att tänka på i exemplet ovan. När användaren matar in ett namn kontrollerar programmet först om namnet redan är en nyckel i ordlistan. Om namnet inte finns skapas ett nytt objekt som läggs till som en post i ordlistan: ```python if not nimi in opiskelijat: opiskelijat[nimi] = Tehtavalaskuri() ``` -Tämän jälkeen _tiedetään_ että olio on olemassa. Se on joko luotu juuri äsken tai jo aiemmalla silmukan kierroksella. Haetaan olio sanakirjasta, ja kutsutaan sen metodia `merkkaa`: +Efter detta kan vi vara säkra på att objektet existerar, kopplat till namnet på den student som används som nyckel. Antingen skapades det precis, eller så fanns det redan från en tidigare iteration av loopen. I vilket fall som helst kan vi nu hämta objektet med nyckeln och kalla metoden `klar`: ```python opiskelijat[nimi].merkkaa() ``` -Rivillä tapahtuu oikeastaan kaksi asiaa, ja sama voitaisiin kirjoittaa siten, että sanakirjasta haettu olio sijoitettaisiin apumuuttujaan: +Raden ovan innehåller egentligen två separata händelser. Vi kan lika gärna använda en hjälpvariabel och skriva den på två separata kodrader: ```python opiskelijan_laskuri = opiskelijat[nimi] opiskelijan_laskuri.merkkaa() ``` -Huomaa, että vaikka olio sijoitettaisiin apumuuttujaan, se _ei tarkoita_ että olio poistuisi sanakirjasta tai oliosta syntyisi kopio. Apumuuttuja on ainoastaan _viite_ sanakirjassa olevaan olioon. +OBS: Även om objektet här tilldelas en hjälpvariabel, så finns objektet kvar i ordlistan precis som tidigare. Hjälparvariabeln innehåller en referens till objektet i ordlistan. -Esimerkin koodia kannattaa **ehdottomasti** kokeilla [visualisaattorissa](http://www.pythontutor.com/visualize.html#mode=edit) jos ei ole aivan 100% varma siitä, miten koodi toimii. +Om du inte är helt säker på vad som egentligen händer i koden ovan kan du prova med [visualiseringsverktyget](http://www.pythontutor.com/visualize.html#mode=edit). @@ -666,21 +666,21 @@ Varmista ensin että voit lisätä osoitteita luokkaan `Puhelinluettelo` ja kun -## Erinäisiä huomioita +## Några avslutande anmärkningar -Puhelinluetteloesimerkki noudattaa rakenteeltaan melko klassisia hyvän olio-ohjelmoinnin periaatteita. Kantavana ideana on siis ohjelman eri vastuualueiden jaottelu erillisiin luokkiin ja metodeihin. Eräs suurimmista motiiveista tällaiselle jaottelulle on monimutkaisuuden hallinta. Toinen tärkeä syy on se, että oikein tehty koodin jaottelu - tai _modularisointi_ kuten ammattijargon asian ilmaisee - tekee koodista potentiaalisesti helpomman ylläpitää ja laajentaa. +Strukturen i Telefonkatalog-exemplet ovan följer de grundläggande principerna för objektorienterad programmering ganska väl. Den centrala principen är att identifiera de olika ansvarsområdena i programmet och fördela dessa logiskt mellan de olika klasserna och metoderna. Ett av de viktigaste motiven för denna uppdelning är att hantera komplexiteten. Ett annat viktigt motiv är att en logisk uppdelning av ansvarsområden - modularitet i facktermer - ofta gör koden lättare att underhålla och bygga vidare på. -Oikeissa ohjelmistoissa ylivoimaisesti suurimman kustannuserän aiheuttaa juuri ylläpito (eli bugien korjailu) sekä ohjelman laajentaminen, joten tällä seikalla on taloudellisesti erittäin suuri merkitys. +I de programvarupaket som utvecklas och används ute i världen är underhåll och utbyggnad, dvs felsökning av befintlig programvara och implementering av nya funktioner, den absolut dyraste delen av utvecklingen. Korrekt implementerad modularitet är ekonomiskt sett en mycket viktig funktion i programvaruutvecklingen. -Nostetaan esimerkistä esiin vielä pari tärkeää seikkaa. Koodi ilmentää hyvin sitä, miten sovelluslogiikan varsinainen ydin on eriytetty sekä käyttöliittymästä, että datan tallettamisesta. Tämä on tärkeää muutamastakin syystä. Ensinnäkin se mahdollistaa koodin testailun pienemmissä yksiköissä, luokka ja metodi kerrallaan. Toisaalta koska sovelluslogiikka ei nyt riipu käyttöliittymästä tai tiedon talletustavasta, on esim. käyttöliittymää mahdollista muuttaa (ainakin johonkin pisteeseen asti) rikkomatta muuta sovellusta. +Det finns några fler objektorienterade programmeringsprinciper som är värda att lyfta fram här. Telefonkatalog är ett bra exempel på hur den centrala programlogiken kan (och bör) separeras från både användargränssnitt och datalagring. Detta är viktigt på grund av ett par olika skäl. För det första gör denna separation det möjligt att testa koden i mindre enheter, en klass och metod i taget. För det andra, eftersom kärnlogiken nu är oberoende av gränssnitten som kommer till omvärlden, är det möjligt att i viss utsträckning ändra implementeringen av antingen kärnlogiken eller gränssnitten utan att hela applikationen går sönder. -Tiedostojen käsittelyn suhteen kannattaa myös huomata se, että ohjelma lukee tiedostoa ainoastaan kerran, käynnistysvaiheessa. Tämän jälkeen kaikki tieto säilytetään ohjelman muuttujissa. Ohjelma tallettaa tiedot kokonaisuudessaan, eli käytännössä uudelleenkirjoittaa tiedoston joka kerta kokonaan uudestaan. Tiedostojen käsittely kannattaa lähes kaikissa tapauksissa tehdä näin. +Filhanteringen i Telefonkatalog-programmet går till på följande sätt: Programmet läser filen en enda gång, när det startar. Efter detta lagras all data i variabler i programmet. När programmet avslutas lagras alla data igen, vilket i praktiken innebär att filen skrivs över. I de flesta fall är detta det rekommenderade sättet att hantera externa filer, eftersom det ofta är mycket mer komplicerat att redigera data på plats. -Hyvän koodin kirjoittamisesta kerrotaan lisää esimerkiksi Robert Martinin mainiossa kirjassa [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882). Kirjan koodiesimerkit on kuitenkin toteutettu Javalla, eli esimerkkien lukeminen saattaa tässä vaiheessa olla vielä varsin työlästä. Paneudumme ylläpidettävyydeltään ja laajennettavuudeltaan laadukkaan koodin toteuttamiseen tarkemmin kursseilla [Ohjelmistotekniikka](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024742-2020-08-01) ja [Ohjelmistotuotanto](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024909-2020-08-01). +Det finns många bra guideböcker för att lära sig om god programmeringspraxis. En sådan är [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) av Robert Martin. Kodexemplen i boken är dock implementerade i Java, så att arbeta igenom exemplen kan vara ganska besvärligt vid denna tidpunkt i din programmeringskarriär, även om boken i sig rekommenderas mycket av kurspersonalen. Teman som lätt underhållen, utbyggbar kod av god kvalitet kommer att utforskas ytterligare på kurserna [Software Development Methods](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024742-2020-08-01) och [Software Engineering](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024909-2020-08-01). -Hyvän olio-ohjelmoinnin periaatteiden mukaisen koodin kirjoittamisella on myös hintansa. Koodia tulee todennäköisesti enemmän kuin jos sama ongelma ratkaistaisiin yhteen pötköön kirjoitetulla spagettikoodilla. Ohjelmoijan onkin aina ratkaistava se, minkälainen lähestymistapa on paras kuhunkin tilanteeseen. Joskus voi olla vain parasta häkkeröidä kasaan nopeasti jotain joka toimii nyt. Jos taas on odotettavissa, että samaa koodia tullaan jatkossa laajentamaan. joko koodarin itsensä tai jonkun muun toimesta, on todennäköisesti kannattavaa panostaa koodin luettavuuteen ja jäsentämiseen jossain määrin jo alkuvaiheissa. +Att skriva kod enligt etablerade principer för objektorienterad programmering har ett pris. Du kommer sannolikt att få skriva mer kod än om du skulle skriva din implementation i en enda kontinuerlig stöt av spaghettikod. En av de viktigaste färdigheterna hos en programmerare är att bestämma det bästa tillvägagångssättet för varje situation. Ibland är det nödvändigt att bara hacka ihop något snabbt för omedelbar användning. Å andra sidan, om det inom överskådlig framtid kan förväntas att koden kommer att återanvändas, underhållas eller vidareutvecklas, antingen av dig eller, mer kritiskt, av någon helt annan, blir programkodens läsbarhet och logiska modularitet väsentlig. Oftast är det så att om det är värt att göra, så är det värt att göra det bra, även i de allra tidigaste utvecklingsstadierna. -Harjoitellaan vielä isomman ohjelmakokonaisuuden toteuttamista yhden ohjelmointitehtävän verran. +För att avsluta denna del av materialet kommer du att implementera ytterligare en större applikation. @@ -766,9 +766,9 @@ Tehtävästä on tarjolla kaksi tehtäväpistettä. Ensimmäisen pisteen saa jos -## Epilogi +## Epilog -Palataan vielä hetkeksi tarkastelemaan puhelinluetteloesimerkkiä, ja sen käyttöliittymän toteuttavaa luokkaa: +För att avsluta den här delen av materialet återvänder vi till användargränssnittet i telefonkatalogsexemplet för en stund. ```python class PuhelinluetteloSovellus: @@ -782,9 +782,9 @@ sovellus = PuhelinluetteloSovellus() sovellus.suorita() ``` -`PuhelinluetteloSovellus`-olio pitää siis sisällään sekä `Puhelinluettelo`-olion että `Tiedostonkasittelija`-olion. Jos olisimme ammattikoodareita, tekisimme sovellukseen pienen muutoksen. Nyt nimittäin se, että sovellus käyttää nimenomaan tiedostoa _luettelo.txt_ tallentamaan luettelon tiedot, on sovelluksen _käyttöliittymän_ kannalta täysin turha deltaji. Jos tiedosto haluttaisiin vaihtaa, edellyttäisi se muutosta luokan `PuhelinluetteloSovellus` koodiin. Tämä taas ei ole hyvä _separation of concerns_ -periaatetta ajatellen, sillä puhelinluettelon tallentaminen ei kuulu ollenkaan käyttöliittymästä huolehtivan luokan vastuisiin. +Ett `TelefonkatalogApplikation`-objekt innehåller både ett `Telefonkatalog`-objekt och ett `FilHanterare`-objekt. Namnet på den fil som skickas till FilHanterare är för närvarande hårdkodat i `TelefonkatalogApplikation`-klassen. Detta är en helt irrelevant detalj när det gäller applikationens användargränssnitt. Faktum är att den bryter mot principen om separation of concerns: var ett `Telefonkatalog`-objekt sparar sitt innehåll ska inte vara en angelägenhet för en `TelefonkatalogApplikation`, men om vi vill ändra platsen måste vi ändra koden för `TelefonkatalogApplikation`. -Parempi vaihtoehto olisikin luoda tiedostokäsittelijä muualla ja antaa se `PuhelinluetteloSovellus`-oliolle, esimerkiksi konstruktorin parametrina: +Det skulle vara en bättre idé att skapa ett FilHanterare-objekt någonstans utanför `TelefonkatalogApplikation`-klassen och skicka det som ett argument till applikationen: ```python class PuhelinluetteloSovellus: @@ -801,7 +801,7 @@ sovellus = PuhelinluetteloSovellus(tallennuspalvelu) sovellus.suorita() ``` -Näin on saatu poistettua luokalta `PuhelinluetteloSovellus` _turha riippuvuus_ käsiteltävän tiedoston nimeen. Jos tiedoston nimi muuttuu, ei luokan koodiin tarvitse koskea ollenkaan. Riittää ainoastaan, että oliolle annetaan hieman erilainen konstruktoriparametri: +Detta tar bort ett onödigt beroende från `TelefonkatalogApplikation`-klassen. Om namnet på filen ändras behöver användargränssnittet inte längre ändras. Vi behöver bara skicka ett annat argument till konstruktören: ```python @@ -818,7 +818,9 @@ sovellus = PuhelinluetteloSovellus(tallennuspalvelu) sovellus.suorita() ``` -Tämä sama tekniikka mahdollistaa sen, että siirrytäänkin tallentamaan puhelinluettelo tiedoston sijaan esimerkiksi internetissä olevaan pilvipalveluun. On vain kirjoitettava pilvipalvelua käyttävä luokka, joka tarjoaa puhelinluettelosovellukselle samanlaiset metodit kuin `Tiedostonkasittelija`. Tämän luokan olio voidaan antaa sovellukselle, ilman että sovelluksen koodista tulee muuttaa riviäkään: +Denna förändring gör det också möjligt för oss att överväga mer exotiska lagringsplatser, till exempel en molntjänst på internet. Vi behöver bara implementera en klass som använder molntjänsten och erbjuder `TelefonkatalogApplikation` exakt samma metoder som `FilHanterare`. + +En instans av denna nya "moln hanterar"-klass kan skickas som ett argument till konstruktören och inte en enda rad kod behöver ändras i användargränssnittet: ```python class InternetTallennin: @@ -829,12 +831,11 @@ sovellus = PuhelinluetteloSovellus(tallennuspalvelu) sovellus.suorita() ``` -Kuten aiemmin todettiin, on tämän kaltaisten tekniikoiden käytöllä oma hintansa: koodia tulee enemmän, ja ohjelmoijan tulee harkita milloin se hinta kannattaa maksaa. +Som du har sett tidigare har den här typen av tekniker ett pris, eftersom det blir mer kod att skriva, så en programmerare måste överväga om det är en acceptabel avvägning. -Tässä esitelty tekniikka (joka kulkee ammattijargonissa nimellä _dependency injection_), missä oliolle annetaan ulkopuolelta käsin sen tarvitsema _riippuvuus_ (eli käytännössä jokin muu olio) on erittäin tyypillinen kikka ammattimaisessa koodauksessa, muun muassa siksi, että se helpottaa ohjelmistojen laajentamista sekä niiden automatisoitua testaamista. Jatkamme teeman käsittelyä kursseilla [Ohjelmistotekniikka](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024742-2020-08-01) ja [Ohjelmistotuotanto](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024909-2020-08-01). +Den teknik som beskrivs ovan kallas beroendeinjektion. Som namnet antyder är tanken att alla beroenden som krävs av ett objekt ska tillhandahållas utanför objektet. Det är ett mycket användbart verktyg i en programmerares verktygslåda, eftersom det gör det lättare att implementera nya funktioner i program och underlättar automatisk testning. Detta tema kommer att utforskas ytterligare på de tidigare nämnda kurserna [Software Development](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024742-2020-08-01) och [Software Engineering](https://studies.helsinki.fi/opintotarjonta/cu/hy-CU-118024909-2020-08-01). -Vastaa lopuksi osion loppukyselyyn: +Svara vänligen på en snabb enkät om denna del av kursen. - diff --git a/data/osa-11/1-koosteet.md b/data/osa-11/1-koosteet.md index 4b79fe994..259ee0898 100644 --- a/data/osa-11/1-koosteet.md +++ b/data/osa-11/1-koosteet.md @@ -1,23 +1,21 @@ --- path: '/osa-11/1-koosteet' -title: 'Koosteet' +title: 'List comprehension' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät mitä tarkoitetaan koosteella (comprehension) -- Osaat hyödyntää koosteita uusien listojen muodostamiseen +- Vet du vad list comprehension är +- Kommer du att kunna använda list comprehensions för att skapa nya listor -Ohjelmointiin liittyy usein erilaisten sekvenssien (eli sarjojen) käsittely. Aikaisemmissa osissa on käytetty runsaasti aikaa merkkijonojen, listojen ja sanakirjojen käsittelyyn eri tavoilla. +En av de situationer där programmering är som mest kraftfull är vid bearbetning av sekvenser av objekt och händelser. Datorer är bra på att upprepa saker. I de tidigare delarna av det här materialet har vi till exempel itererat strängar, listor och ordlistor på olika sätt. -Oletetaan tilanne, jossa meillä on lista kokonaislukuja. Tämän listan perusteella meidän pitäisi luoda vastaava merkkijonolista. - -Perinteinen tapa toteuttaa operaatio näyttäisi esimerkiksi tältä: +Låt oss anta att vi har en lista med heltal och att vi skulle behöva samma lista med objekt i strängformat. Ett traditionellt sätt att utföra uppgiften skulle kunna se ut så här: ```python luvut = [1, 2, 3, 6, 5, 4, 7] @@ -27,28 +25,30 @@ for luku in luvut: merkkijonot.append(str(luku)) ``` -## Listakooste +## List comprehension -Python tarjoaa kuitenin "pythonmaisemman" tavan uuden listan muodostamiseksi vanhan perusteella. Menetelmää voidaan kutsua "listakoosteeksi", mutta huomattavasti yleisempää on käyttää englanninkielistä nimeä _list comprehension_. +Det finns också ett mer "pythoniskt" sätt att generera listor från befintliga listor. Dessa kallas list comprehensions. -Menetelmässä ideana on kuvata yhden rivin lausekkeella, mikä operaatio listan kaikille alkioille tehdään, ennen kuin ne tallennetaan uuteen listaan. +Tanken är att på en enda rad få plats med både beskrivningen av vad som ska göras med varje objekt i listan och tilldelningen av resultatet till en ny lista. -Esimerkiksi yllä esitetty ohjelma, joka luo merkkijonolistan kokonaislukulistan perusteella, näyttäisi listakoostetta hyödyntäen tältä: +I exemplet ovan var operationen som utfördes på varje objekt i listan mycket enkel: varje heltal omvandlades till en sträng. Låt oss se hur detta skulle se ut implementerat med en list comprehension: ```python luvut = [1, 2, 3, 6, 5, 4, 7] merkkijonot = [str(luku) for luku in luvut] ``` -Koosteessa näyttää siis olevan jotakuinkin samat elementit kuin perinteisessäkin toteutuksessa, mutta syntaksi on uudenlainen. Yleisemmin listakoosteen syntaksi voitaisiin esittää esimerkiksi näin: +Den andra raden ovan innehåller många av samma element som den mer traditionella iterativa metoden, men syntaxen är annorlunda. Ett sätt att generalisera en list comprehension skulle kunna vara + +`[ for in ]` -`[ for in ]` +Hakparenteserna runt list comprehensionsatsen signalerar till Python att resultatet ska vara en ny lista. En efter en bearbetas varje objekt i den ursprungliga listan och resultatet lagras i den nya listan, precis som i det iterativa tillvägagångssättet ovan. Som resultat har vi en ny lista med exakt lika många objekt som i originalet, och alla objekt har behandlats på ett identiskt sätt. -Koosteen ympärillä olevat hakasulkeet kertovat, että lopputuloksena on uusi lista. Koosteessa poimitaan yksi kerrallaan alkio alkuperäisestä sarjasta (esimerkkimme tapauksessa listasta) ja tallennetaan siihen liittyvän lausekkeen arvo uuteen listaan. Lopputuloksena on lista, jossa on yhtä paljon alkioita kuin alkuperäisessä listassa ja kaikki alkiot on käsitelty samalla tavalla. +(OBS: originalen till bilderna i denna del saknas tillfälligt, vilket är anledningen till att det finns en del finskt vokabulär i illustrationerna i denna del. Vi arbetar på att åtgärda detta). -Toisessa esimerkissä jokainen alkuperäisen listan alkio kerrotaan kymmenellä ja tallennetaan uuteen listaan: +List comprehensions kan också hantera mycket mer komplicerade operationer. Vi kan utföra beräkningar, till exempel multiplicera de ursprungliga objekten med tio: ```python luvut = list(range(1,10)) @@ -65,7 +65,7 @@ print(luvut_kerrottuna) -Lauseke voi olla mikä tahansa Pythonin lauseke. Esimerkiksi koosteessa voidaan kutsua itse määriteltyä funktiota: +Faktum är att uttrycket i list comprehension-satsen kan vara vilket Python-uttryck som helst. Du kan till och med anropa funktioner som du själv har definierat: ```python def kertoma(n: int): @@ -88,7 +88,7 @@ if __name__ == "__main__": -Sama ohjelma esitettynä perinteisellä silmukalla näyttäisi tältä: +Med den mer välbekanta `for`-loopen skulle samma process kunna uttryckas så här: ```python @@ -109,9 +109,9 @@ if __name__ == "__main__": ``` -Koosteen avulla on siis mahdollista ilmaista sama toiminnallisuus tiiviimmin ja silti yhä helposti luettavassa muodossa. +List Comprehensions gör att vi kan uttrycka samma funktionalitet på ett mer konsekvent sätt, vanligtvis utan att förlora något av läsbarheten. -Palauttamalla funktiosta suoraan kooste saadaan aikaiseksi hyvin tiivistä koodia: +Vi kan också returnera en list comprehension-sats direkt från en funktion. Om vi behövde en funktion för att producera faktorialtal för listor med tal, skulle vi kunna göra det på ett mycket kortfattat sätt: ```python def kertomat(luvut: list): @@ -237,15 +237,17 @@ print(pituudet(listat)) -## Alkioiden suodatus +## Att filtrera föremål + +I exemplen ovan var alla våra listor lika långa före och efter en list comprehension-operation. I varje fall användes alla föremål i den ursprungliga listan som grund för den nya listan. Men ibland behöver vi bara några av de ursprungliga föremålen. Hur kan detta åstadkommas? -Edellisissä esimerkeissä uusi lista muodostettiin kaikista alkuperäisen listan alkioista. Joskus on kuitenkin näppärää, jos voitaisiin valita alkuperäiseltä listalta vain tietyt alkiot. Koosteessa tämä onnistuu yhdistämällä siihen ehto-osa. Yleinen syntaksi on seuraava: +En list comprehension-sats kan också innehålla ett villkor, så att vi kan kontrollera objekten mot villkoret och bara välja ut dem som matchar. Den allmänna syntaxen är enligt följande: -`[ for in if ]` +`[ for in if ]` -Erotuksena aiempaan koosteen loppuun kirjoitetaan siis ehtolause. Ainoastaan ne alkiot poimitaan mukaan tuloslistaan, joiden kohdalla ehtolauseke on tosi. +Satsen ovan är i övrigt identisk med den allmänna form som introducerades i början av detta avsnitt, men nu finns det en if-sats i slutet. Endast de objekt från den ursprungliga listan för vilka det booleska uttrycket är sant används som grund för den nya listan. -Esimerkissä poimitaan kaikki parilliset alkiot uuteen listaan. Huomaa, että lausekkeena on esimerkissä ainoastaan listan alkio eli poimittavia alkioita ei käsitellä minkään operaation avulla ennen sijoittamista uuteen listaan: +I exemplet nedan väljer vi alla jämna objekt från den ursprungliga listan som bas för den nya listan. I själva verket bearbetas inte dessa objekt ytterligare på något sätt, utan de tilldelas den nya listan som de är: ```python lista = [1, 1, 2, 3, 4, 6, 4, 5, 7, 10, 12, 3] @@ -260,7 +262,7 @@ print(parilliset) -Jos lausekkeeksi on määritelty jotain muuta kuin pelkkä alkio, mukaan otetuille alkioille toteutetaan tämä operaatio kuten ennenkin. Muokataan edellistä esimerkkiä niin, että uudessa listassa on kaikki alkuperäisen listan parilliset alkiot kerrottuna kymmenellä: +Uttrycket i list comprehension-satsen ovan är bara ett enkelt `foremal`, vilket innebär att inga operationer ska utföras på föremålen i listan. Uttrycket kan vara vilket Python-uttryck som helst, precis som i de tidigare exemplen. Följande list comprehension-sats tar till exempel alla jämna föremål i en lista, multiplicerar varje föremål med tio och lagrar resultatet i en ny lista: ```python lista = [1, 1, 2, 3, 4, 6, 4, 5, 7, 10, 12, 3] @@ -275,7 +277,9 @@ print(parilliset) -Seuraavassa esimerkissä lasketaan ainoastaan positiivisten alkioiden kertoma: +När du stöter på mer och mer komplicerade list comprehensions kan det vara bra att försöka läsa villkoret först. Föremålen bearbetas ändå bara om de klarar testet, så det är ofta vettigt att först ta reda på vilka objekt som klarar filtreringssteget. Ibland skulle uttrycket i en list comprehension-sats inte ens vara möjligt för alla föremål i den ursprungliga listan. + +Till exempel är faktorialtal bara definierat för icke-negativa heltal. Om vi inte kan vara säkra på att en lista bara innehåller värden på noll eller högre, måste innehållet filtreras innan det skickas vidare till den faktorialfunktion som vi skapade tidigare: ```python def kertoma(n: int): @@ -298,7 +302,7 @@ if __name__ == "__main__": -Tarkastellaan vielä edellisestä jatkettua esimerkkiä, jossa kertoma lasketaan vain parillisista positiivista luvuista. Lisäksi listaan tallennetaan tuplessa sekä alkuperäinen alkio että kertoma: +Som vi såg i vårt allra första exempel på list comprehension, där heltal omvandlades till strängar, behöver föremålen i den nya listan inte vara av samma typ som föremålen i den ursprungliga listan. Om vi fortsätter från faktorialexemplet ovan kan vi skapa en tupel från varje originella föremål och dess bearbetade motsvarighet och lagra dessa i en lista, vilket kombinerar allt vi har lärt oss hittills i en enda list comprehension-sats: ```python @@ -323,7 +327,9 @@ if __name__ == "__main__": -Esimerkissä lauseke on siis `(luku, kertoma(luku))`, joka muodostaa tuplen, jossa ensimmäinen alkio on alkio alkuperäisestä listasta ja toinen alkio kertoma-funktion palauttama arvo. Ehtolauseke on `luku > 0 and luku % 2 == 0`, jossa valikoidaan mukaan vain alkiot, jotka ovat sekä positiivisia että jaollisia kahdella. +Om vi plockar isär exemplet ovan har vi det booleska uttrycket `n > 0 and n % 2 == 0`. Detta innebär att endast föremål som är både positiva och delbara med två accepteras för vidare bearbetning från den ursprungliga listan. + +Dessa positiva, jämna tal bearbetas sedan i tur och ordning till formatet `(n, faktorial(n))`. Detta är en tupel, där det första objektet är själva talet och det andra objektet är resultatet som returneras av faktorialfunktionen. @@ -378,15 +384,15 @@ appelsiini -## Vaihtoehtoinen haara suodatuksessa +## Alternativ exekvering med list comprehension -Koosteessa voi käyttää ehtolauseen ohella myös vaihtoehtoista haaraa. Tämä onnistuu käyttämällä jo aiemmin mainittua _ehtolauseketta_: +Ofta när vi har en villkorlig sats inkluderar vi också en else-gren. Eftersom vi kan använda villkor i list comprehensions är else-grenen också tillgänglig med list comprehension. Den allmänna syntaxen för villkoret som används med list comprehension ser ut så här: -` if else ` +` if else ` -...joka saa arvokseen joko lausekkeen 1 tai 2 arvon riippuen siitä, onko ehto tosi vai epätosi. +Vi stötte på dessa enradiga villkor, eller ternära operatorer, redan i [del 7](https://programming-24.mooc.fi/part-7/6-more-features). Uttrycket ovan utvärderas till antingen `uttryck 1` eller `uttryck 2`, beroende på om villkoret är sant eller falskt. -Niinpä esim. ohjelma, joka tulostaa kahdesta luvusta suuremman yhdellä print-lauseella voisi näyttää tältä: +Som en uppfräschning av ämnet kan vi säga att om vi behöver skriva ut det större av två tal och vi bara vill använda en enda utskriftssats, kan vi få plats med allt på en enda rad: ```python luku1 = int(input("Anna luku 1:")) @@ -394,13 +400,15 @@ luku2 = int(input("Anna luku 2:")) print (luku1 if luku1 > luku2 else luku2) ``` -Kun yhdistetään syntaksi listakoosteeseen, saadaan seuraavankaltainen rakenne: +Genom att kombinera den ternära operatorssyntaxen med en list comprehension-sats får man följande allmänna struktur: + +`[ if else for in ]` -`[ if else for in ]` +Det här kan se lite förvirrande ut, eftersom den villkorliga strukturen nu kommer före den faktiska list comprehensionen. Det är bara så här syntaxen har definierats, åtminstone för tillfället. Om det också finns en `else`-gren kommer villkoret först. Om det bara finns ett `if`, kommer det sist. Du kan prova att byta ut dem och se vad som händer. -Lopputuloksena syntyvässä listassa on yksi alkio jokaista alkuperäisen sarjan alkiota kohti. Jokaiselle alkiolle suoritetaan joko lauseke 1 tai lauseke 2 riippuen siitä onko ehtolauseke tosi vai ei. +Att inkludera en else-operator innebär att vi återigen kommer att bearbeta varje objekt från den ursprungliga listan. Beroende på om villkoret är sant eller falskt utförs antingen `uttryck 1` eller `uttryck 2` på varje objekt i listan. -Seuraava esimerkki muodostaa uuden listan, jossa alkuperäisen listan negatiiviset alkiot on käännetty vastaluvuikseen - positiiviset alkiot kelpuutetaan sellaisenaan. Käytännössä koostelause siis muodostaa listan alkuperäisen listan itseisarvoista. +I följande exempel kontrolleras om föremåleni en lista är noll eller högre. Alla sådana föremål accepteras som de är, men alla negativa föremål negeras, så att tecknet ändras från negativt till positivt. Resultatet är en lista som innehåller de absoluta värdena för föremålen i den ursprungliga listan. ```python @@ -416,9 +424,9 @@ print(itseisarvot) -Suoritettava lauseke on siis `luku` (eli alkio sellaisenaan), jos ehto `luku >= 0` on tosi, muuten suoritetaan lauseke `-luku`. +Vi upprepar vad som händer ovan: om villkoret `nummer >= 0` är sant, genomgår föremålet uttrycket `nummer`, och resultatet är själva föremålet. Om villkoret är falskt genomgår föremålet uttrycket `–nummer`, så att det får ett positivt värde. -Seuraavassa esimerkissä funktio `merkkijonojen_pituudet` saa parametrikseen sekalaisia alkioita sisältävän listan. Funktio laskee merkkijonoista tuloslistaan pituuden, muun tyyppisten alkioiden kohdalle asetetaan -1. +I följande exempel har vi funktionen `strang_langder` som tar en lista som sitt argument och returnerar en annan lista med längderna på alla strängar i den ursprungliga listan. Den här funktionen är dock okej med listföremål av alla typer. Om föremålet är en sträng beräknar den dess längd. Om objektet är något annat infogar den -1 i listan som den returnerar. ```python diff --git a/data/osa-11/2-lisaa-koosteesta.md b/data/osa-11/2-lisaa-koosteesta.md index e60620903..252dd3cc3 100644 --- a/data/osa-11/2-lisaa-koosteesta.md +++ b/data/osa-11/2-lisaa-koosteesta.md @@ -1,23 +1,21 @@ --- path: '/osa-11/2-lisaa-koosteesta' -title: 'Lisää koosteesta' +title: 'Fler comprehensions' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, miten koosteita voidaan hyödyntää merkkijonojen kanssa -- Osaat käyttää omia olioita koosteissa -- Osaat muodostaa myös sanakirjakoosteita +- Kommer du att kunna använda comprehensions med strängar +- Vet du hur du använder comprehensions med dina egna klasser +- Kommer du att kunna skapa ordlistscomprehensions -Koska koosteen lähteenä voi olla mikä tahansa sarja, voidaan sitä soveltaa myös merkkijonojen käsittelyyn. Merkkijonon läpikäynnissä poimitaan merkit yksitellen jonosta, suoritetaan nille annettu lauseke ja tallennetaan lopputulos uuden listan alkioksi. - -Esimerkiksi +Listor är kanske det vanligaste målet för comprehensions, men comprehensions fungerar på alla serier av föremål, inklusive strängar. Liksom listexemplen i föregående avsnitt, ifall en list comprehension utförs på en sträng, plockas föremålen (dvs. tecknen) i strängen en efter en, bearbetas enligt det givna uttrycket och lagras i en lista. ```python @@ -34,9 +32,7 @@ print(isot_kirjaimet) -Huomaa, että lopputuloksena on lista. Jos halutaan muodostaa merkkijonon perusteella uusi merkkijono, voidaan hyödyntää aikaisemmin esiteltyä `join`-metodia. Metodin avulla voidaan yhdistää listan alkiot merkkijonoksi. Metodi kohdistuu välimerkkiin, jolla alkiot yhdistetään. - -Metodi toimii siis esimerkiksi näin: +Resultatet är en lista, vilket dikteras av parentesnotationen runt comprehension-satsen. Om vi ville ha en sträng istället skulle vi kunna använda strängmetoden `join` för att tolka listan till en sträng. Kom ihåg att metoden anropas på den sträng som vi vill använda som "lim" mellan tecknen. Låt oss ta en titt på några exempel: ```python @@ -61,7 +57,7 @@ P ja e ja k ja k ja a -Kun yhdistetään `join`-metodin koosteeseen, voidaan muodostaa merkkijonosta uusi merkkijono helposti. Tarkastellaan esimerkkiä `join`-metodin ja koosteen yhdistelmästä, joka muodostaa alkuperäisen merkkijonon pohjalta uuden merkkijonon, jossa on ainoastaan vokaalit: +List comprehensions och `join`-metoden gör det enkelt att skapa nya strängar baserade på andra strängar. Vi kan t.ex. skapa en sträng som bara innehåller vokalerna från en annan sträng: ```python @@ -80,7 +76,7 @@ eiaaaaiiääoei -Esimerkissä on selkeyden vuoksi jaettu kooste ja `join`-metodin kutsu omille riveilleen, mutta toki ne voi kirjoittaa myös yhdeksi lausekkeeksi: +I exemplet ovan står list comprehension och `join`-metoden på separata rader, men de kan kombineras till ett enda uttryck: ```python @@ -92,7 +88,7 @@ print(vokaalijono) ``` -Hyödyntämällä samassa yhteydessä vielä `split`-metodia, voidaan käsitellä esimerkiksi kokonaisia lauseita tehokkaasti yhdellä lausekkeella. Esimerkissä poistetaan lauseen jokaisesta sanasta ensimmäinen kirjain: +Många Python-programmerare står trogna vid dessa oneliners, så det är väl värt besväret att lära sig läsa dem. Vi kan till och med lägga till `split`-metoden i mixen, så att vi kan bearbeta hela meningar effektivt med ett enda uttalande. I exemplet nedan tas det första tecknet från varje ord i en mening bort: ```python @@ -109,13 +105,13 @@ esihiisi e uulkaa aan ihisi ississä -Käydään läpi tarkemmin mitä koko lausekkeessa tapahtuu: +Låt oss gå igenom detta steg för steg: -`sana[1:]` ottaa osajonon sanasta alkaen toisesta merkistä (eli indeksistä 1) -`lause.split()` purkaa merkkijonon listaksi annetun välimerkin kohdalta. Kun välimerkkiä ei ole määritelty, käytetään oletuksena tyhjiä välejä -`" ".join()` yhdistää listan palaset uudeksi jonoksi käyttäen välilyöntiä palojen välissä. +- `ord[1:]` extraherar en delsträng från det andra tecknet (vid index 1) och framåt +- `mening.split()` delar upp meningen i avsnitt vid det angivna tecknet. I det här fallet ges inget argument till metoden, så meningen delas upp vid mellanslagstecken som standard +- `" ".join()` kombinerar föremålen i listan till en ny sträng med ett mellanslag mellan föremålen -Sama esimerkki perinteisemmällä tavalla näyttäisi esimerkiksi tältä: +En mer traditionell iterativ metod skulle kunna se ut så här: ```python @@ -156,11 +152,11 @@ Suo kuokka ja python hieno yhdistelmä -## Omat oliot koosteissa +## Egna klasser och comprehensions -Joskus omia olioita on näppärä käsitellä tai muodostaa koosteiden avulla. Tarkastellaan seuraavaksi muutamaa esimerkkiä tähän liittyen. +Comprehensions kan vara ett användbart verktyg för att bearbeta eller formulera instanser av dina egna klasser, vilket vi kommer att se i följande exempel. -Ensimmäisessä esimerkissä luokka Maa mallintaa yhtä maata asukaslukuineen. Koosteessa poimitaan listalta kaikkien sellaisten maiden nimet, joiden asukasluku on suurempi kuin 5 miljoonaa. +Låt oss först ta en titt på klassen `Land` som är en enkel modell för ett enda land, med attribut för namn och befolkning. I huvudfunktionen nedan skapar vi först några Land-objekt och använder sedan en list comprehension för att bara välja dem vars befolkning är större än fem miljoner. ```python @@ -192,7 +188,7 @@ Ruotsi -Toinen vaihtoehto olisi luoda lista maa-olioista ja tulostaa sen jälkeen nimet. Tämä vaihtoehto olisi järkevämpi, jos maita tarvittaisiin vielä myöhemminkin (tai mikäli haluttaisiin esimerkiksi tarkemmin tarkastella maiden asukaslukuja silmukassa): +I list comprehension ovan valde vi bara namnattributet från Land-objekten, så innehållet i listan kunde skrivas ut direkt. Vi skulle också kunna skapa en ny lista med länderna och komma åt namnattributet i `for`-loopen. Detta skulle vara användbart om samma lista med länder skulle användas senare i programmet, eller om vi behövde befolkningsattributet i `for`-loopen också: ```python @@ -209,9 +205,9 @@ if __name__ == "__main__": print(maa.nimi) ``` -Toisessa esimerkissä luokka `Juoksumatka` mallintaa yhtä juoksumatkaa nimineen ja pituuksineen. Nyt koosteen avulla luodaan lista `Juoksumatka`-olioita annettujen pituuksien mukaaan. +I nästa exempel har vi en klass som heter `Springning` som modellerar ett enskilt lopp med attribut för loppets längd och namn. Vi kommer att använda list comprehension för att skapa `Springning`-objekt baserat på en lista med tävlingslängder. -Huomaa, että `Juoksumatka`-luokan konstruktorissa parametrilla `nimi` on oletusarvo, eikä sitä olioita luodessa esimerkissä erikseen annetakaan: +Parametern `namn` har ett standardvärde i konstruktorn för `Springning`-klassen, vilket är varför vi inte behöver skicka namnet som ett argument. ```python @@ -247,7 +243,7 @@ if __name__ == "__main__": -Jos oma luokka on viime kerran esimerkin mukaisesti iteroitava, voidaan sitä käyttää lähteenä listakoosteessa: +Låt oss nu ta reda på vad som gör en serie objekt "begripliga" (“comprehendible”). I föregående del lärde vi oss hur vi kan göra våra egna klasser itererbara. Det är exakt samma funktion som också möjliggör list comprehension. Om din egen klass är itererbar kan den användas som grund för en list comprehension. Följande klassdefinitioner är kopierade direkt från [del 10](https://programming-24.mooc.fi/part-10/3-oo-programming-techniques#iterators): ```python @@ -375,13 +371,13 @@ Kerava 4h ja keittiö hintaero 16500 euroa -## Koosteet sanakirjan kanssa +## Comprehensions och ordlistor -Koosteet toimivat samalla tavalla myös sanakirjan kanssa: jos vaihdetaan hakasulkeet aaltosulkeiksi, syntyy koosteen seurauksena listan sijasta sanakirja. Koska sanakirjan alkio muodostuu kahdesta komponentista - arvosta ja avaimesta, tulee molemmat komponentit antaa myös koostetta luodessa. +Det finns inget i sig "list-aktigt" med comprehensions. Resultatet är en lista eftersom comprehension-satsen är inkapslad i hakparenteser, som indikerar en Python-lista. Förståelser fungerar lika bra med Python-ordlistor om du använder rundparenteser istället. Kom dock ihåg att ordlistor kräver nyckel-värde-par. Båda måste anges när en ordlista skapas, även när det gäller comprehensions. -Lähteenä voidaan edelleen käyttää mitä tahansa sarjaa, eli esimerkiksi listaa, merkkijonoa, tuplea, sanakirjaa tai omaa iteroinnin toteuttavaa luokkaa. +Grunden för en comprehension kan vara vilken itererbar serie som helst, vare sig det är en lista, en sträng, en tupel, en ordlista, någon av dina egna itererbara klasser och så vidare. -Esimerkki, joka luo merkkijonon pohjalta sanakirjan, joka sisältää kaikki merkkijonon kirjaimet ja niiden esiintymämäärät: +I följande exempel använder vi en sträng som bas för en ordlista. Ordlistan innehåller alla unika tecken i strängen, tillsammans med antalet gånger de förekommer: ```python @@ -398,11 +394,11 @@ print(merkkimäärät) -Periaate on siis täsmälleen sama, mutta yksittäisen arvon sijasta annetaan erikseen avain ja arvo. Yleisesti merkittynä siis: +Principen för comprehension-satsen är exakt densamma som för listor, men i stället för ett enda värde består uttrycket nu av en nyckel och ett värde. Den allmänna syntaxen ser ut så här: -`{ : for in }` +`{ : för i }` -Tarkastellaan vielä toisena esimerkkinä ohjelmaa, joka laskee kaikkien listalla olevien positiivisten lukujen kertomat, mutta tällä kertaa sanakirjaan. Luku toimii avaimena ja kertoma arvona: +Som avslutning på det här avsnittet tittar vi på faktorialtal igen. Den här gången lagrar vi resultaten i en ordlista. Själva talet är nyckeln, medan värdet är resultatet av faktorn från vår funktion: ```python diff --git a/data/osa-11/3-rekursio.md b/data/osa-11/3-rekursio.md index c3df0b0da..a7d97573d 100644 --- a/data/osa-11/3-rekursio.md +++ b/data/osa-11/3-rekursio.md @@ -1,19 +1,19 @@ --- path: '/osa-11/3-rekursio' -title: 'Rekursio' +title: 'Rekursion' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät mitä tarkoitetaan rekursiolla -- Osaat kirjoittaa itse yksinkertaisen rekursiivisen funktion +- Vet du vad rekursion innebär +- Kommer du att kunna skriva en enkel rekursiv funktion -Kuten aiemmin on huomattu, funktiot voivat kutsua toisia funktioita. Esimerkiksi näin: +Som vi har sett många gånger tidigare kan funktioner anropa andra funktioner. Till exempel: ```python def tervehdi(nimi : str): @@ -24,7 +24,7 @@ def tervehdi_monesti(nimi : str, kerrat : int): tervehdi(nimi) ``` -Samaan tapaan funktio voi kutsua myös itseään. Jos kuitenkaan funktion parametrit eivät muutu kutsukertojen välissä, tästä syntyy "ikuinen silmukka": +En funktion kan också anropa sig själv, men vi som programmerare måste vara försiktiga när vi gör det. Det är lätt att hamna i en oändlig loop av funktionsanrop, precis som vi hamnade i en oändlig loop av upprepningar med `while`-loopar om vi utelämnade lämpliga brytvillkor. Så om man försöker anropa en `hello`-funktion med följande definition ```python def tervehdi(nimi : str): @@ -32,7 +32,7 @@ def tervehdi(nimi : str): tervehdi(nimi) ``` -Tällöin funktion kutsuminen millä tahansa merkkijonolla antaa virheilmoituksen: +skulle skapa ett nytt sort av fel: @@ -40,11 +40,11 @@ RecursionError: maximum recursion depth exceeded -## Mitä rekursio tarkoittaa? +## Vad betyder rekursion? -Virheilmoituksessakin mainitulla _rekursiolla_ tarkoitetaan sitä, että funktio kutsuu itseään. Rekursiossa funktion parametrien pitää kuitenkin muuttua niin, että jossain vaiheessa kutsuminen lopetetaan. Perusperiaate on sama kuin silmukoissa: jotta silmukka ei jatkuisi ikuisesti, siinä tulee olla päättymisehto, joka toteutuu jossain vaiheessa. +Den rekursion som nämns i felet ovan innebär att man definierar något i termer av sig självt. I programmeringssammanhang handlar det oftast om en funktion som anropar sig själv. För att detta ska fungera utan att orsaka oändliga loopar måste de argument som skickas till funktionen ändras varje gång, så att de nästlade funktionsanropen slutar vid något skede. Grundprincipen här är densamma som i `while`-loopar: det måste alltid finnas ett stoppvillkor av något slag, och det villkoret måste utlösas vid någon tidpunkt i exekveringen. -Tarkastellaan aluksi yksinkertaista funktiota, joka lisää listan loppuun 0-alkioita niin kauan kuin listan pituus on alle 10. Silmukan sijasta funktio kutsuukin itseään uudestaan, jos ehto ei täyty: +Låt oss ta en titt på en enkel funktion som lägger till nollor i en lista så länge det finns färre än 10 objekt i listan. Den här gången använder vi dock inte en loop. Om villkoret ännu inte är uppfyllt anropar funktionen sig själv ```python def tayta_lista(luvut: list): @@ -67,7 +67,7 @@ if __name__ == "__main__": -Perinteisellä silmukalla ohjelma näyttäisi esimerkiksi tältä: +Denna funktionalitet kunde lika väl bli uppnådd genom en vanlig `while`-loop: ```python @@ -83,13 +83,13 @@ if __name__ == "__main__": ``` -Esimerkeistä huomataan, että perinteinen (eli _iteratiivinen_) lähestymistapa tuottaa lyhyemmän ja selkeämmän ohjelman. Rekursiivinen ohjelma kuitenkin toimii ja tuottaa oikean lopputuloksen, koska funktio käsittelee jokaisella kutsukerralla samaa listaa viittauksen kautta. +Det mer traditionella iterativa tillvägagångssättet ger ett kortare program som förmodligen också är lättare att förstå. Med den rekursiva versionen är det inte lika tydligt att vi under hela processen arbetar med exakt samma lista. Så är det dock och därför fungerar den rekursiva funktionen lika bra. - + -Tietojenkäsittelytieteteessä erotetaan usein _iteratiiviset_ ja _rekursiiviset_ algoritmit. Iteratiivinen tarkoittaa kurssilla tähän asti yleensä käyttämäämme tapaa, jossa ratkaisu perustuu peräkkäisyyteen – yleensä siihen, että käsitellään rakenne silmukassa. Rekursiivinen tarkoittaa vaihtoehtoista tapaa, jossa funktio silmukan sijasta (tai lisäksi) kutsuu itseään muuttuvilla parametrien arvoilla. +Inom datavetenskapsteori skiljer man ofta mellan iterativa och rekursiva algoritmer, så det är bäst att bekanta sig med dessa termer redan från början. Iterativa lösningar är sådana som baseras på sekventiell bearbetning av objekt, ofta med hjälp av loopar. Hittills har vi behandlat iterativa metoder ganska exklusivt. Rekursiv, å andra sidan, avser en metod där funktionen anropar sig själv med förändrade parametervärden. -Mikä tahansa algoritmi on periaatteessa mahdollista toteuttaa sekä iteratiivisesti että rekursiivisesti, mutta monessa tapauksessa jompikumpi tapa soveltuu selkeästi paremmin ongelman ratkaisemiseen. +I princip borde det vara möjligt att lösa alla problem med antingen iterativa eller rekursiva metoder. I praktiken är det dock oftast så att den ena eller den andra metoden är klart bättre lämpad för varje problem. Förmågan att avgöra vilken som är bäst kommer till stor del med övning. @@ -115,9 +115,9 @@ print(luvut) -## Rekursio ja paluuarvot +## Rekursion och returvärden -Rekursiivisella funktiolla voi olla myös palautusarvo. Tarkastellaan tätä tarkoitusta varten esimerkkiä, joka laskee kertoman rekursiivisesti: +Rekursiva funktioner kan också ha returvärden. I de senaste avsnitten har vi arbetat med faktorialtal, så låt oss skriva en rekursiv faktorialfunktion: ```python @@ -148,13 +148,13 @@ Luvun 6 kertoma on 720 -Jos funktion parametrin arvo on 0 tai 1, funktio palauttaa 1 (koska kertoman määritelmän mukaan lukujen 0 ja 1 kertoma on 1). Muuten funktio palauttaa lausekkeen `n * kertoma(n - 1)`. Tämä tarkoittaa, että parametri `n` kerrotaan funktion itsensä kutsun palauttamalla arvolla. +Om parametern för den rekursiva faktoriella funktionen är 0 eller 1, returnerar funktionen 1, eftersom det är så faktoriell operation definieras. I alla andra fall returnerar funktionen värdet `n * faktorial(n - 1)`, vilket är värdet av dess parameter n multiplicerat med returvärdet av funktionsanropet `faktorial(n - 1)`. -Olennaista funktion toimivuuden kannalta on, että funktiossa on määritelty ehto, jolla se ei kutsu itseään enää uudestaan. Tässä tapauksessa ehto on `n < 2`. +Det avgörande här är att funktionsdefinitionen innehåller ett stoppvillkor. Om detta uppfylls avslutas rekursionen. I det här fallet är villkoret `n < 2`. Vi vet att det kommer att nås så småningom, eftersom det värde som skickas som argument till funktionen minskas med ett på varje nivå i rekursionen. -[Visualisaattori](http://www.pythontutor.com/visualize.html#mode=edit) on oivallinen väline rekursiota käyttävien ohjelmien tutkimiseksi. +[Visualiseringsverktyget](http://www.pythontutor.com/visualize.html#mode=edit) kan vara till stor hjälp när det gäller att förstå rekursiva program. -Laajennetaan kertoman laskevaa funktiota niin, että se käyttää apumuuttujia: +Exemplet ovan skulle kanske bli lite tydligare om vi använde oss av hjälpvariabler: ```python def kertoma(n: int): @@ -164,17 +164,21 @@ def kertoma(n: int): edellisen_luvun_kertoma = kertoma(n - 1) luvun_n_kertoma = n * edellisen_luvun_kertoma return luvun_n_kertoma - + kertoma(5) ``` -Kokeile, miten [visualisaattori](http://www.pythontutor.com/visualize.html#code=def%20kertoma%28n%3A%20int%29%3A%0A%20%20%20%20if%20n%20%3C%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%0A%20%20%20%20edellisen_luvun_kertoma%20%3D%20kertoma%28n%20-%201%29%0A%20%20%20%20luvun_n_kertoma%20%3D%20n%20*%20edellisen_luvun_kertoma%0A%20%20%20%20return%20luvun_n_kertoma%0A%20%20%20%20%0Akertoma%285%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) demonstroi rekursion etenemisen. +Ta en titt på hur[visualiseringsverktyget](http://www.pythontutor.com/visualize.html#code=def%20kertoma%28n%3A%20int%29%3A%0A%20%20%20%20if%20n%20%3C%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%0A%20%20%20%20edellisen_luvun_kertoma%20%3D%20kertoma%28n%20-%201%29%0A%20%20%20%20luvun_n_kertoma%20%3D%20n%20*%20edellisen_luvun_kertoma%0A%20%20%20%20return%20luvun_n_kertoma%0A%20%20%20%20%0Akertoma%285%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visar hur rekursionen fortskrider. + +Visualiseringsverktyget har en liten finess i hur det hanterar anropsstacken, eftersom den verkar "växa" nedåt. Vanligtvis avbildas anropsstaplar som just staplar, där de nya anropen placeras överst. I visualiseringsverktyget är det aktiva funktionsanropet det skuggade blocket längst ner, som har sina egna kopior av de variabler som syns. -Hieman normaalista poiketen visualisaattorissa kutsupino "kasvaa" alaspäin. Suorituksessa oleva funktiokutsu on kutsupinon alimpana oleva sinisellä merkitty "lohko", jolla on omat muuttujansa. Hetken kuluttua palautettava tulos on laskettu muuttujaan `luvun_n_kertoma`. +När den rekursiva faktorfunktionen anropas byggs anropsstapeln upp tills den gräns som utgörs av `n < 2` nås. Då återkommer det sista funktionsanropet i stacken med ett värde - det är `1`, eftersom `n` nu är mindre än 2. Detta återkomstvärde skickas till det föregående funktionsanropet i stacken, där det används för att beräkna det funktionsanropets återkomstvärde, och så vidare tillbaka ut ur stacken. + +Returvärdet för varje funktionsanrop lagras i hjälpvariabeln `faktorial_nu`. Gå igenom visualiseringen noggrant tills du förstår vad som händer i varje steg, och var särskilt uppmärksam på det värde som returneras i varje steg. -Tarkastellaan vielä toista funktiota, joka laskee halutun Fibonaccin luvun rekursiivisesti. Fibonaccin lukujonossa luku on aina kahden edellisen luvun summa. Niinpä jonon alku näyttää tältä: 1, 1, 2, 3, 5, 8, 13, 21, 34. +Låt oss ta en titt på ett annat vanligt rekursivt exempel: Fibonacci-talen. I en Fibonacci-sekvens är varje tal summan av de två föregående talen. De två första talen definieras här som 1 och 1, och sekvensen börjar då så här: 1, 1, 2, 3, 5, 8, 13, 21, 34. ```python def fibonacci(n: int): @@ -208,21 +212,21 @@ Fibonaccin 10. luku on 55 -Tällä kertaa lopetusehtona on, että luku on pienempi tai yhtä suuri kuin 2, koska Fibonaccin kaksi ensimmäistä lukua ovat molemmat ykkösiä. +Den här gången är stoppvillkoret att parametern är mindre än eller lika med 2, eftersom hela sekvensen definieras från de två första siffrorna och framåt, och vi definierade de två första siffrorna som lika med 1. -Miten algoritmi käytännössä oikein toimii? +Hur fungerar då den här funktionen i praktiken? -Luvuille 1 ja 2 algoritmi palauttaa arvon 1 ehdon `n <= 2` mukaisesti. +Om funktionen anropas med 1 eller 2 som argument returnerar den 1, vilket dikteras av villkoret `n <= 2`. -Luvulle 3 algoritmi palauttaa arvon lausekkeesta `fibonacci(n - 1) + fibonacci(n - 2)`, eli käytännössä lausekkeen `fibonacci(2) + fibonacci(1)`. Koska edellisessä kohdassa huomattiin, että näiden molempien arvo on 1, palauttaa funktio siis arvon 2 (joka onkin kolmas Fibonaccin luku) +Om argumentet är 3 eller större returnerar funktionen värdet av `fibonacci(n - 1) + fibonacci(n - 2)`. Om argumentet är exakt 3 är detta värde lika med `fibonacci(2) + fibonacci(1)`, och vi vet redan resultatet av båda dessa från föregående steg. `1 + 1` är lika med 2, som alltså är det tredje talet i Fibonacci-sekvensen. -Luvulle 4 algoritmi palauttaa arvon lausekkeesta `fibonacci(3) + fibonacci(2)`, mikä edellisten kohtien perusteella on siis `2 + 1` eli 3. +Om argumentet är 4 är returvärdet `fibonacci(3) + fibonacci(2)`, som vi nu vet är `2 + 1`, vilket är lika med 3. -Luvulle 5 algoritmi palauttaa arvon lausekkeesta `fibonacci(4) + fibonacci(3)`, mikä edellisten kohtien perusteella on siis `3 + 2` eli 5. +Om argumentet är 5 är returvärdet `fibonacci(4) + fibonacci(3)`, vilket vi nu vet är `3 + 2`, vilket är lika med 5. -jne. +Och så vidare, och så vidare. -Rekursiivinen algoritmimme siis toimii, koska voimme todistaa jokaisen luvun kohdalla ohjelman toimivuuden aikaisempien lukujen perusteella. +Vi kan i varje steg verifiera att funktionen ger rätt resultat, vilket ofta är tillräckligt i grundläggande programmeringsuppgifter. Den formella verifierbarheten av algoritmer är ett ämne för mer avancerade kurser, till exempel Data Structures and Algorithms. @@ -330,22 +334,22 @@ False -## Binäärihaku +## Binär sökning -Binäärihaussa yritetään löytää järjestyksessä olevasta listasta annettu alkio. Järjestys tarkoittaa tässä yhteydessä esimerkiksi lukujen järjestystä pienimmästä suurimpaan tai merkkijonoja aakkosjärjestyksessä. +I en binär sökning har vi en sorterad lista med objekt och vi försöker hitta ett visst objekt i den. Ordningen på objekten kan till exempel vara siffror från minst till störst, eller strängar från alfabetiskt först till sist. Sorteringsmetoden spelar ingen roll, så länge den är känd och relevant för det objekt som vi försöker hitta. -Binäärihaun ideana on, että tarkastellaan aina listan keskimmäistä alkiota. Jos -- keskimmäinen alkio on etsitty alkio, palautetaan tieto siitä, että alkio löytyi -- keskimmäinen alkio on pienempi kuin etsittävä alkio, rajataan haku listan jälkimmäiselle puolikkaalle -- keskimmäinen alkio on suurempi kuin etsittävä alkio, rajataan haku listan ensimmäiselle puolikkaalle +Tanken med en binär sökning är att alltid titta på objektet i mitten av listan. Vi har då tre möjliga scenarier. Om objektet i mitten är +- det vi letar efter: vi kan returnera en indikation på att vi hittade objektet +- mindre än det vi letar efter: vi kan göra om sökningen i den större halvan av listan +- större än den vi letar efter: vi kan göra om sökningen i den mindre halvan av listan. -Jos lista on tyhjä, palautetaan tieto siitä, että alkiota ei löytynyt. +Om listan är tom kan vi fastställa att objektet inte hittades och returnera en indikation på det. -Seuraava kuva havainnollistaa binäärihaun etenemistä, kun etsitään listasta lukua 24: +I följande bild kan vi se hur en binär sökning fortskrider när den letar efter talet 24: -Rekursiivinen algoritmi binäärihaulle: +Här är en rekursiv algoritm för en binär sökning: ```python def binaarihaku(lista: list, alkio: int, vasen : int, oikea : int): @@ -387,6 +391,8 @@ False -Tässä funktiolle `binaarihaku` annetaan neljä parametria: viite listaan, etsittävä alkio sekä hakualueen vasen ja oikea kohta. Alussa hakualue on koko lista, jolloin vasen kohta on 0 ja oikea kohta on `len(lista)-1`. Funktio tarkastaa hakualueen keskellä olevan alkion ja joko ilmoittaa, että haluttu alkio löytyi, tai jatkaa hakua vasemmasta tai oikeasta puoliskosta. +Funktionen `binar_sokning` tar fyra argument: mållistan, det objekt som söks samt vänster och höger kant på sökområdet. När funktionen anropas första gången täcker sökområdet hela mållistan. Den vänstra kanten ligger på index `0` och den högra kanten ligger på index `len(mål)-1`. Funktionen beräknar det centrala indexet och kontrollerar den positionen på listan. Antingen har objektet hittats eller så fortsätter sökningen till den mindre eller större halvan av mållistan. + +Låt oss jämföra detta med en enkel linjär sökning. Vid en linjär sökning är sökområdet från början och framåt, tills antingen objektet hittas eller sökområdet tar slut. Antalet steg som behövs för att täcka hela sökområdet växer linjärt i samma takt som sökområdets storlek. Varje söksteg täcker endast en sökkandidat från början av sökområdet. Låt oss anta att det sökta objektet inte hittas. Om sökområdet är en miljon objekt långt måste vi ta en miljon söksteg för att försäkra oss om att objektet inte finns i sökområdet. -Jos verrataan binäärihakua _peräkkäishakuun_, algoritmien tehokkuus erottuu selvästi. Peräkkäishaussa alkiota lähdetään etsimään listan alusta ja listaa käydään läpi yksi alkio kerrallaan, kunnes alkio on löytynyt tai on päästy listan loppuun. Jos listan pituus on miljoona alkiota, tarvitaan perättäishaussa koko listan läpikäyntiin miljoona askelta, mutta binäärihaussa askelia tarvitaan vain 20. +Vid en binär sökning växer däremot antalet steg som behövs logaritmiskt. Låt oss återigen anta att det sökta objektet inte hittas. Sökområdet halveras för varje steg, eftersom vi vet att objektet antingen är mindre eller större än den aktuella sökkandidaten i mitten. 2 gånger 20 (2^20) är redan långt över 1 miljon, så det tar som mest 20 steg att täcka hela sökområdet med en binär sökning. När vi har att göra med sorterade sökområden, vilket ofta är fallet när vi har att göra med datorer och material som ska bearbetas automatiskt, är en binär sökning alltså mycket effektivare än en linjär sökning. diff --git a/data/osa-11/4-lisaa-esimerkkeja.md b/data/osa-11/4-lisaa-esimerkkeja.md index aa9c28c15..0bbd942f7 100644 --- a/data/osa-11/4-lisaa-esimerkkeja.md +++ b/data/osa-11/4-lisaa-esimerkkeja.md @@ -1,33 +1,35 @@ --- path: '/osa-11/4-lisaa-esimerkkeja' -title: 'Lisää esimerkkejä' +title: 'Fler exempel på rekursion' hidden: false --- - + -Tässä osiossa +Efter den här delen -- Käydään läpi muutamia binääripuuhun liittyviä rekursiivisia esimerkkialgoritmeja +- Kommer du att känna till binära träd och några rekursiva algoritmer som används för att bearbeta dem -Rekursion todellinen hyöty tulee esiin tilanteissa, joissa iteratiivinen ratkaisu on hankala kirjoittaa. Tarkastellaan esimerkkinä _binääripuuta_. Binääripuulla tarkoitetaan puurakennetta, jossa jokaisella alkiolla on korkeintaan kaksi "lasta". Binääripuu voisi siis näyttää esim. tältä (huomaa, että vaikka tietojenkäsittelijöitä pidetään joissain yhteyksissä luonnontieteilijöinä, käsityksemme puiden kasvusuunnasta on nurinkurinen): +De verkliga fördelarna med rekursion blir uppenbara när vi stöter på problem där iterativa lösningar är svåra att skriva. Låt oss ta en titt på binära träd, till exempel. Ett binärt träd är en förgrenad struktur där vi har noder och vid varje nod förgrenar sig strukturen, som mest, i två underordnade grenar med egna noder. Ett binärt träd skulle då kunna se ut så här (datavetenskap betraktas ofta som en gren av naturvetenskapen, men vår förståelse av träd är lite upp och ner, som du kommer att märka): -Binääripuiden (ja puiden yleensäkin) käsittely rekursiivisesti on ainakin teoriassa helppoa: jos halutaan tehdä jokin operaatio binääripuun kaikille alkioille - esim. etsiä jokin tietty alkio puusta, voidaan kirjoittaa rekursiivinen algoritmi, joka +Binära träd bör åtminstone teoretiskt sett vara lätta att hantera rekursivt: om vi vill utföra någon operation på varje nod i trädet behöver vår algoritm helt enkelt -1. Käsittelee nykyisen alkion -2. Kutsuu itseään vasemmasta lapsesta alkavalle "alipuulle" -3. Kutsuu itseään oikeasta lapsesta alkavalle "alipuulle" +1. Behandla den aktuella noden +2. Anropa sig själv på barnnoden till vänster +3. Anropa sig själv på barnnoden till höger -Kun koko rekursiivinen algoritmi on käsitelty, on vierailtu kerran puun jokaisessa solussa. Iteratiivinen versio algoritmista on yleensä hankalampi kirjoittaa, koska kirjanpito vieralluista alkioista menee äkkiä monimutkaiseksi. +Som du kan se på bilden ovan är både de vänstra och högra "underträden" fullfjädrade binära träd i sig, och den enda nod som lämnas utanför de rekursiva anropen är den överordnade noden, som bearbetas i steg 1 innan funktionen anropas rekursivt. På så sätt kan vi vara säkra på att varje nod har besökts exakt en gång när funktionen är klar. -Binääripuuta voidaan mallintaa helposti kirjoittamalla luokka, joka mallintaa yhtä alkiota puussa. Alkiolla on arvon lisäksi tieto vasemmasta ja oikeasta lapsestaan: +En iterativ version av en binär trädtraversering skulle vara mycket mer komplicerad, eftersom vi på något sätt skulle behöva hålla reda på alla noder som vi redan har besökt. Samma principer gäller för alla beräkningsbara trädstrukturer, inte bara binära. + +Ett binärt träd är också lätt att modellera i Python-kod. Vi behöver bara skriva en klassdefinition för en enda nod. Den har ett värdeattribut och attribut för de vänstra och högra underordnade noderna: ```python @@ -39,11 +41,11 @@ class Alkio: self.oikea_lapsi = oikea_lapsi ``` -Nyt jos halutaan mallintaa esimerkiksi oheisen kaltainen puu: +Låt oss anta att vi vill modellera följande träd: -...se voidaan muodostaa seuraavalla ohjelmalla: +Vi kunde uppnå detta med följande kod: ```python if __name__ == "__main__": @@ -58,11 +60,11 @@ if __name__ == "__main__": ``` -## Rekursiiviset binääripuualgoritmit +## Algoritmer för rekursiva binära träd -Tarkastellaan ensin algoritmia, joka tulostaa kaikki binääripuun alkiot allekkain. Käytetään esimerkkinä tässä ja tulevissa tehtävissä yllä muodostettua puuta. +Låt oss först ta en titt på en algoritm som skriver ut alla noder i ett binärt träd en efter en. I de följande exemplen kommer vi att arbeta med det binära träd som definieras ovan. -Funktio saa parametrikseen juurialkion (eli kaikkein ylimmäisenä olevan alkion, jonka _jälkeläisiä_ kaikki muut alkiot ovat): +Argumentet till utskriftsfunktionen är rotnoden i det binära trädet. Detta är noden högst upp i vår illustration ovan. Alla andra noder är barn till den här noden: ```python @@ -77,9 +79,9 @@ def tulosta_alkiot(juuri: Alkio): ``` -Funktio tulostaa annetun alkion arvon, ja sen jälkeen kutsuu itseään uudestaan vasemmalle ja oikealla alipuulle (edellyttäen, että vasen ja/tai oikea alkio on määritelty). Algoritmi on melko yksinkertainen, mutta käy tehokkaasti läpi kaikki puun alkiot riippumatta puun koosta. Algoritmi ei myöskään vieraile missään puun alkiossa kahta kertaa. +Funktionen skriver ut värdet på den nod som skickas som argument och anropar sedan sig själv på de vänstra och högra underordnade noderna, förutsatt att noderna är definierade. Det här är en mycket enkel algoritm, men den går på ett effektivt och tillförlitligt sätt igenom alla noder i trädet, oavsett trädets storlek. Avgörande är att ingen nod besöks två gånger. Varje värde skrivs bara ut en gång. -Kun funktiolle annetaan parametriksi aikaisemmin luodun binääripuun juurialkio `puu`, se tulostaa +Om vi skickar rotnoden `trad` i det binära trädet som illustreras ovan som ett argument till funktionen, skriver den ut @@ -92,7 +94,9 @@ Kun funktiolle annetaan parametriksi aikaisemmin luodun binääripuun juurialkio -Vastaavalla tavalla voidaan kirjoittaa algoritmi, joka laskee kaikkien puun alkioiden summan: +Som du kan se av ordningen på noderna i utskriften rör sig algoritmen först längs trädets "vänstra ben" ner till botten, och därifrån går den igenom de andra noderna i ordning. + +På samma sätt kan vi skriva en algoritm för att beräkna summan av alla de värden som finns lagrade i trädets noder: ```python @@ -109,7 +113,7 @@ def alkioiden_summa(juuri: Alkio): ``` -Muuttuja `summa` alustetaan nykyisen alkion arvolla. Tämän jälkeen siihen lisätään rekursiivisesti vasemman ja oikean alipuun summat (tarkastaen taas ensin, että ne ovat olemassa). Lopuksi summa palautetaan. +Variabeln `nod_summa` initieras till att vara lika med värdet för den aktuella noden. Värdet i variabeln ökas sedan genom rekursiva anrop till nodens summor i det vänstra och högra underordnade trädet (först kontrolleras naturligtvis att de finns). Detta resultat returneras sedan. @@ -145,15 +149,15 @@ if __name__ == "__main__": -## Järjestetty binääripuu +## Sorterat binärt träd -Binääripuusta on erityisesti hyötyä silloin, kun alkiot on järjestetty tietyllä tavalla. Alkion löytäminen järjestetystä puusta on nopeaa. +Ett binärt träd är särskilt användbart när noderna är sorterade på ett visst sätt. Det gör att det går snabbt och effektivt att hitta noder i trädet. -Tarkastellaan esimerkkinä puuta, jossa alkiot on järjestetty seuraavasti: jokaisen alkion vasen lapsi on pienempi kuin alkio itse, ja vastaavasti oikea alkio on suurempi kuin alkio itse. +Låt oss ta en titt på ett träd som är sorterat på följande sätt: det vänstra barnet till varje nod är mindre än själva noden och det högra barnet är på motsvarande sätt större. -Nyt alkion etsimiseen voidaan kirjoittaa rekursiivinen algoritmi, joka toimii hyvin samankaltaisesti kuin aiemmin tarkastelemamme binäärihaku: jos juurialkio on tarkasteltava alkio, palautetaan arvo `True`. Muuten jatketaan rekursiivisesti hakua joko vasemmasta tai oikeasta alipuusta. Jos alkio on tyhjä, palautetaan `False`. +Nu kan vi skriva en rekursiv algoritm för att söka efter noder. Idén är mycket lik den binära sökningen från föregående avsnitt: om den aktuella noden är den nod vi letar efter, returnera `True`. Annars fortsätter vi rekursivt med antingen det vänstra eller det högra underordnade trädet. Om noden inte är definierad returneras `False`. ```python @@ -217,9 +221,9 @@ if __name__ == "__main__": -## Paluu aikaan ennen rekursiota +## Besök till tiden innan rekursion -Harjoitellaan vielä osan lopussa hieman laajemman ohjelman tekemistä olioita hyödyntäen. Tässä tehtäväsarjassa ei rekursiota tarvitse eikä edes kannata käyttää. Listakoosteita sen sijaan pääsee hyödyntämään! +Låt oss avsluta denna del av materialet med en lite större övning som koncentrerar sig på objektorienterade programmeringsprinciper. Vi rekommenderar inte att du använder rekursion i denna serie av uppgifter, men tekniker för list comprehension kommer att vara användbara. @@ -498,4 +502,3 @@ virheellinen syöte Vastaa lopuksi osion loppukyselyyn: - diff --git a/data/osa-12/1-funktio-parametrina.md b/data/osa-12/1-funktio-parametrina.md index 71655b0fd..f5a1097d0 100644 --- a/data/osa-12/1-funktio-parametrina.md +++ b/data/osa-12/1-funktio-parametrina.md @@ -1,23 +1,23 @@ --- path: '/osa-12/1-funktio-parametrina' -title: 'Funktio parametrina' +title: 'Funktioner som argument' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Osaat järjestää listoja eri kriteerien mukaan -- Tiedät mitä tarkoitetaan lambda-lausekkeella -- Osaat hyödyntää lambda-lauseketta myös muiden Pythonin funktioiden kanssa -- Osaat välittää funktion parametrina funktiolle +- Kan du sortera listor enligt olika kriterier +- Kommer du att veta vad ett lambdauttryck är +- Kan du använda lambda-uttryck med andra Python-funktioner +- Vet du hur en funktion skickas som argument till en annan funktion -Olemme jo aikaisemmin käyttäneet metodia `sort` ja funktiota `sorted` järjestämään listoja luonnolliseen järjestykseen. Metodit toimivat sellaisenaan hyvin luvuista ja merkkijonoista koostuvien listojen kanssa, mutta jos lista sisältää monimutkaisempia alkioita, Python ei välttämättä järjestä listaa niin kuin ohjelmoija toivoisi. +Vi är redan bekanta med metoden `sort` och funktionen `sorted`, som används för att sortera listor i deras naturliga ordning. För siffror och strängar fungerar detta vanligtvis bra. För allt som är mer komplicerat än dem så är dock den naturliga ordningen på föremål enligt Python inte alltid det som vi som programmerare avser. -Esimerkiksi lista tupleja järjestetään oletuksena jokaisen tuplen ensimmäisen alkion perusteella: +Till exempel sorteras som standard en lista med tupler baserat på det första objektet i varje tupel: ```python tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)] @@ -37,13 +37,13 @@ for tuote in tuotteet: -Mitä jos haluaisimme järjestää tuotelistan hinnan perusteella? +Men vad händer om vi vill sortera listan baserat på priset? -## Funktiot parametrina +## Funktioner som argument -Järjestysmetodille tai -funktiolle voidaan antaa toisena parametrina järjestyksen määräävä avain. Avaimeksi annetaan funktio, joka kertoo, miten yksittäisen alkion arvo määritetään. Python kutsuu tätä funktiota järjestämisen aikana alkioiden vertailemiseen. +En sorteringsmetod eller -funktion accepterar vanligtvis ett valfritt andra argument som gör att du kan kringgå standardsorteringskriterierna. Detta andra argument är en funktion som definierar hur värdet på varje föremål i listan bestäms. När listan sorteras anropar Python denna funktion när den jämför föremålen med varandra. -Esimerkiksi: +Låt oss ta en titt på ett exempel: ```python def hintajarjestys(alkio: tuple): @@ -69,15 +69,15 @@ if __name__ == "__main__": -Nyt ohjelma järjestää listan hinnan mukaiseen järjestykseen. Mutta mitä ohjelmassa oikeastaan tapahtuu? +Nu är listan sorterad utifrån artiklarnas priser, men vad händer egentligen i programmet? -Funktion `hintajarjestys` määrittely on melko yksinkertainen: se saa parametrikseen yhden alkion ja palauttaa alkiolle arvon - tässä tapauksessa tuplen toisen alkion (joka esimerkissämme esittää tuotteen hintaa). Tarkastellaan kuitenkin lähemmin järjestysmetodia kutsuvaa riviä: +Funktionen `ordning_enligt_pris` är faktiskt ganska enkel. Den tar ett objekt som sitt argument och returnerar ett värde för det objektet. Mer specifikt returnerar den det andra objektet i tupeln, som representerar priset. Men sedan har vi den här kodraden, där `sort`-metoden anropas: -`tuotteet.sort(key=hintajarjestys)` +`produkter.sort(key=ordning_enligt_pris)` -Rivillä annetaan metodille `sort` parametriksi funktio. Ei siis funktion paluuarvoa, vaan _viittaus funktioon_. Järjestysmetodi kutsuu tätä funktiota jokaiselle alkiolle. +Här anropas `sort`-metoden med en funktion som argument. Detta är inte en referens till funktionens returvärde, utan en referens till själva funktionen. `Sort`-metoden anropar denna funktion flera gånger och använder varje objekt i listan som argument i tur och ordning. -Kutsut nähdään selkeästi lisäämällä vertailufunktioomme ylimääräinen tulostuslause: +Om vi inkluderar en extra print-sats i funktionsdefinitionen för `ordning_enligt_pris` kan vi verifiera att funktionen verkligen anropas en gång för varje objekt i listan: ```python def hintajarjestys(alkio: tuple): @@ -110,7 +110,7 @@ Kutsuttiin hintajarjestys(('vesimeloni', 4.95)) -Järjestys saadaan käännettyä _päinvastaiseksi_ hyödyntämällä sekä metodista `sort` että funktiosta `sorted` löytyvää toista parametria `reverse`: +Ordningen kan vändas med ett annat nyckelordsargument; `reverse`, som är tillgängligt med både `sort`-metoden och funktionen `sorted`: ```python tuotteet.sort(key=hintajarjestys, reverse=True) @@ -118,9 +118,9 @@ tuotteet.sort(key=hintajarjestys, reverse=True) t2 = sorted(tuotteet, key=hintajarjestys, reverse=True) ``` -## Funktion sisällä määritelty funktio +## En funktionsdefinition inom en funktionsdefinition -Jos haluaisimme siirtää edellisessä esimerkissä tehdyn järjestämisen omaan funktioonsa `jarjesta_hinnan_mukaan`, voisimme toteuttaa sen seuraavasti: +Vi kan också inkludera en namngiven funktion för den nya prisbaserade sorteringsfunktionen som vi har skapat. Låt oss lägga till en funktion med namnet `sortera_enligt_pris`: ```python def hintajarjestys(alkio: tuple): @@ -136,7 +136,7 @@ for tuote in jarjesta_hinnan_mukaan(tuotteet): print(tuote) ``` -Jos järjestämisen käyttämää apufunktiota `hintajarjestys` ei käytetä missään muussa kohtaa ohjelmaa kuin funktiossa `jarjesta_hinnan_mukaan`, sen määrittely voitaisiin siirtää funktion sisälle: +Om vi vet att hjälpfunktionen `ordning_enligt_pris` inte används någonstans utanför funktionen `sortera_enligt_pris`, kan vi placera den första funktionsdefinitionen inom den senare: ```python def jarjesta_hinnan_mukaan(alkiot: list): @@ -211,9 +211,9 @@ Dexter 8.6 -## Omien olioiden alkioiden järjestäminen +## Sortering av samlingar av egna objekt -Kirjoitetaan samaa periaatetta hyödyntäen ohjelma, joka järjestää listan omasta `Opiskelija`-luokasta luotuja olioita kahden eri kriteerin avulla: +Låt oss med samma princip skriva ett program som sorterar en lista med objekt från vår egen klass `Student` på två olika sätt: ```python class Opiskelija: @@ -265,7 +265,7 @@ Aapeli (a123), 220 op. -Järjestäminen toimii niinkuin pitää. Jos olioille arvon antavia funktioita `tunnuksen_mukaan` ja `pisteiden_mukaan` ei tarvita muuten, voimme kuitenkin vielä yksinkertaistaa ohjelmaa. +Som du kan se ovan fungerar sortering efter olika kriterier precis som det är tänkt. Om funktionerna `enligt_id` och `enligt_studiepoäng` inte behövs någon annanstans finns det sätt att göra implementeringen enklare. Vi återkommer till detta ämne efter dessa övningar. @@ -432,13 +432,15 @@ Olhava, 3 reittiä, vaikein 6B -## Lambda-lauseke +## Lambda-uttryck -Lambda-lausekkeen avulla voidaan luoda anonyymi funktio eli funktio, joka muodostetaan sillä hetkellä, kun sitä tarvitaan. Lausekkeen yleinen syntaksi on seuraava: +Vi har mest arbetat med funktioner ur modularitetssynpunkt. Det är sant att funktioner spelar en viktig roll när det gäller att hantera komplexiteten i dina program och undvika upprepning av kod. Funktioner skrivs vanligtvis så att de kan användas många gånger. -`lambda : ` +Men ibland behöver man något som liknar en funktion som man bara använder en gång. Med lambda-uttryck kan du skapa små, anonyma funktioner som skapas (och kasseras) när de behövs i koden. Den allmänna syntaxen är som följer: -Esimerkiksi tuplelistan järjestys onnistuisi näin käyttämällä lambda-lauseketta: +`lambda : ` + +Att sortera en lista med tupler efter det andra objektet i varje tupel skulle se ut så här implementerat med ett lambda-uttryck: ```python tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)] @@ -459,11 +461,11 @@ for tuote in tuotteet: -Lauseke +Uttrycket -`lambda alkio: alkio[1]` +`lambda föremål: föremål[1]` -vastaa funktiomäärittelyä +Är ekvivalent med funktionsdefinitionen ```python @@ -471,9 +473,9 @@ def hinta(alkio): return alkio[1] ``` -paitsi että lambda-lauseketta käytettäessä funktiolle ei anneta nimeä. Tämän takia muodostettavaa funktiota kutsutaan anonyymiksi funktioksi. +förutom det faktum att en lambdafunktion inte har något namn. Det är därför lambda-funktioner kallas anonyma funktioner. -Muuten lambdan avulla muodostettava funktio on kuin mikä tahansa muukin funktio. Esimerkiksi seuraava esimerkki järjestää merkkijonot niiden viimeisten merkkien mukaiseen aakkosjärjestykseen: +I alla andra avseenden skiljer sig inte en lambda-funktion från någon annan funktion, och de kan användas i alla samma sammanhang som en motsvarande namngiven funktion. Följande program sorterar till exempel en lista med strängar i alfabetisk ordning enligt det sista tecknet i varje sträng: ```python mjonot = ["Mikko", "Makke", "Maija", "Markku", "Mikki"] @@ -492,7 +494,7 @@ Markku -Mennään vielä pidemmälle: yhdistämällä listakooste ja `join`-metodi lambda-lausekkeeseen voidaan esimerkiksi järjestää merkkijonot niistä löytyvien vokaalien mukaiseen järjestykseen välittämättä muista merkeistä: +Vi kan också kombinera list comprehensions, `join`-metoden och lambda-uttryck. Vi kan till exempel sortera strängar baserat på enbart vokalerna i dem och ignorera alla andra tecken: ```python mjonot = ["Mikko", "Makke", "Maija", "Markku", "Mikki"] @@ -511,9 +513,9 @@ Mikko -Anonyymejä funktioita voi hyödyntää Pythonissa monien muidenkin valmiiden funktioiden yhteydessä. Esimerkiksi funktioille `min` ja `max` voidaan määritellä samalla tavalla parametri `key`, jonka perusteella minimi- tai maksimiarvo valitaan. +Anonyma funktioner kan också användas med andra inbyggda Python-funktioner, inte bara de som används för sortering. Till exempel tar funktionerna `min` och `max` också ett nyckelordsargument som heter `key`. Det används som kriterium för att jämföra objekten när det lägsta eller högsta värdet väljs. -Esimerkissä poimitaan levyistä aluksi vanhin ja sitten pisin: +I följande exempel handlar det om ljudinspelningar. Först väljer vi den äldsta inspelningen och sedan den längsta: ```python @@ -613,9 +615,9 @@ Palloilija(nimi=Hessu Hopo, pelinumero=4, maalit=3, syotot=9, minuutit=12) -## Funktiot parametreina omissa funktioissa +## Funktioner som argument inom egna funktioner -Pythonissa on siis mahdollista välittää viittaus johonkin funktioon toiselle funktiolle. Tarkastellaan vielä esimerkkinä omaa funktiota, joka saa parametrikseen toisen funktion: +Vi konstaterade ovan att det är möjligt att skicka en referens till en funktion som argument till en annan funktion. Som avslutning på detta avsnitt skriver vi en egen funktion som tar en funktion som argument. ```python # tyyppivihje callable viittaa funktioon @@ -645,9 +647,9 @@ if __name__ == "__main__": -Funktion `suorita_operaatio` lopputulos siis riippuu siitä, mikä funktio sille on välitetty parametrina. Funktioksi kelpaa mikä tahansa funktio (niin `def`-lauseella määritelty kuin anonyymikin) jolla on kaksi parametria. +Det värde som returneras av funktionen `utfor_operation` beror på vilken funktion som skickades som argument. Vilken funktion som helst som tar emot två argument skulle duga, oavsett om den är anonym eller namngiven. -Vaikkei funktioiden välittäminen parametrina olekaan kaikkein yleisimmin tarvittava operaatio, on se joka tapauksessa hyödyllinen mekanismi. Esimerkiksi seuraava ohjelma kirjoittaa tiedostosta 1 halutut rivit tiedostoon 2. Rivien valintakriteeri annetaan funktiona, joka palauttaa `True`, jos rivi tulee kirjoittaa toiseen tiedostoon: +Att skicka referenser till funktioner som argument till andra funktioner är kanske inte något som du kommer att göra dagligen under din programmeringskarriär, men det kan vara en användbar teknik. Följande program väljer ut några rader från en fil och skriver dem till en annan fil. Hur raderna väljs ut bestäms av en funktion som returnerar True endast om raderna ska kopieras: ```python def kopioi_rivit(lahde_nimi: str, kohde_nimi: str, kriteeri= lambda x: True): @@ -674,7 +676,7 @@ if __name__ == "__main__": kopioi_rivit("eka.txt", "toka.txt", lambda rivi: rivi[-1] != ".") ``` -Funktiossa parametrille `kriteeri` on määritelty oletusarvoksi lambda-lauseke `lambda x: True`, jonka tuottama anonyymi funktio palauttaa arvon `True` kaikille syötteille. Niinpä oletuksena kopioidaan kaikki rivit tiedostosta toiseen. Jos käyttäjä antaa kolmannelle parametrille arvon, tämä korvaa oletusarvon. +Funktionsdefinitionen innehåller ett standardvärde för nyckelordsparametern `kriterie`: `lambda x: True`. Denna anonyma funktion returnerar alltid `True` oavsett indata. Standardbeteendet är alltså att kopiera alla rader. Som vanligt gäller att om ett värde anges för en parameter med ett standardvärde, ersätter det nya värdet standardvärdet. @@ -726,4 +728,3 @@ for tuote in hae(tuotteet, lambda t: t[2]>10): - diff --git a/data/osa-12/2-generaattorit.md b/data/osa-12/2-generaattorit.md index 3ee3fc2c6..2cda6e7c8 100644 --- a/data/osa-12/2-generaattorit.md +++ b/data/osa-12/2-generaattorit.md @@ -1,26 +1,28 @@ --- path: '/osa-12/2-generaattorit' -title: 'Generaattorit' +title: 'Generatorer' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, mitä tarkoitetaan generaattorilla Pythonissa -- Tiedät, mitä avainsana yield tekee -- Osaat kirjoittaa itse generaattorifunktioita +- Vet du vad en Python-generator är +- Kommer du att känna till nyckelordet `yield` +- Kommer du att kunna skriva dina egna generator-funktioner -Eräissä tilanteissa olisi kätevää saada ohjelmassa seuraava alkio (tai useampi alkio) tietystä sarjasta ilman että muodostetaan koko sarjaa kerralla. Pythonissa tämä onnistuu näppärästi _generaattoreiden_ avulla. Generaattorifunktio muistuttaa normaalia arvon palauttavaa funktiota, mutta kun normaalifunktio palauttaa (tai ainakin sen pitäisi palauttaa) samalla syötteellä saman arvon, generaattorifunktio palauttaa seuraavan luvun sarjasta. +Vi har redan stött på situationer där vi har att göra med en serie föremål och vi behöver nästa föremål i serien, men vi vill inte nödvändigtvis formulera hela serien fram till den punkten varje gång ett nytt föremål krävs. Vissa rekursiva serier, till exempel Fibonacci-numret, är ett bra exempel på en sådan situation. Om varje funktionsanrop rekursivt genererar hela serien fram till önskad punkt, slutar det med att vi genererar början av serien många gånger om. -Generaattorien toiminta voidaan toteuttaa ohjelmissa myös muilla keinoilla (itse asiassa sama pätee useimpiin ohjelmointitekniikoihin), mutta niiden käyttö selkeyttää ja mahdollisesti säästää muistia tai muita resursseja tietyntyylisissä ohjelmissa. +Python-generatorer är ett sätt att bara producera nästa föremål i en serie när det behövs, vilket i princip innebär att genereringsprocessen för serien bara körs en gång (för en viss exekvering av ett program). De fungerar i stort sett som vanliga funktioner, eftersom de kan anropas och returnerar värden, men det värde som en generatorfunktion returnerar skiljer sig från en vanlig funktion. En normal funktion ska returnera samma värde varje gång, givet samma argument. En generatorfunktion, å andra sidan, ska komma ihåg sitt nuvarande tillstånd och returnera nästa föremål i serien, som kan skilja sig från föregående föremål. -## Avainsana yield +Precis som det finns många sätt att lösa de flesta programmeringsproblem finns det många sätt att uppnå en funktionalitet som liknar generatorer, men generatorer kan bidra till att göra programmet lättare att förstå och kan i vissa situationer spara minne eller andra beräkningsresurser. -Generaattorifunktion toiminta perustuu avainsanaan `yield`. Tarkastellaan esimerkkinä funktiota, joka palauttaa yksi kerrallaan kokonaislukuja nollasta alkaen kunnes maksimiarvo on saavutettu: +## Nyckelordet yield + +En generatorfunktion måste innehålla nyckelordet `yield`, som markerar det värde som funktionen returnerar. Låt oss titta på en funktion som genererar heltal, med början från noll och slut vid ett förutbestämt maxvärde: ```python @@ -32,7 +34,7 @@ def laskuri(maksimi: int): ``` -Nyt laskurilta voi pyytää seuraavan arvon funktiolla `next()`: +Nu kan `raknare`-funktionen skickas som argument till funktionen `nasta()` ```python if __name__ == "__main__": @@ -52,9 +54,9 @@ Toka arvo: -Niinkuin esimerkistä huomataan, `yield` muistuttaa `return`-komentoa siinä, että se palauttaa arvon funktiosta. Eroavaisuus on kuitenkin siinä, että yield palauttaa yksittäisen arvon, ja funktio "muistaa" mihin tilaan se jäi. +Som du kan se i exemplet ovan liknar nyckelordet `yield` nyckelordet `return`: båda används för att definiera ett returvärde. Skillnaden är att `yield` inte "stänger" funktionen på samma sätt som `return`. En generatorfunktion med nyckelordet `yield` håller reda på sitt tillstånd och nästa gång den anropas kommer den att fortsätta från samma tillstånd. -Arvoja voi pyytää vain niin kauan kun niitä on generaattorissa jäljellä - tämän jälkeen generaattorifunktio antaa poikkeuksen `StopIteration`: +Den här generatorn kräver också ett maxvärde, i exemplet ovan var det `10`. När generatorn får slut på värden kommer den att ge upphov till ett `StopIteration`-undantag: ```python if __name__ == "__main__": @@ -75,7 +77,7 @@ StopIteration -Poikkeuksen voi ottaa kiinni `try`-`except` lohkolla: +Undantaget kan bli fångat med ett `try`- `except` block: ```python if __name__ == "__main__": @@ -96,7 +98,7 @@ Luvut loppuivat kesken -Jos halutaan palauttaa kaikki generaattorin tuottamat alkiot, helpointa on iteroida ne läpi `for`-lauseella: +Att gå igenom alla objekt i en generator görs enkelt med en `for`-loop: ```python if __name__ == "__main__": @@ -116,6 +118,10 @@ if __name__ == "__main__": +Generatorer behöver inte ha ett definierat maxvärde eller en slutpunkt. De kan generera värden i det oändliga (naturligtvis inom andra beräkningsmässiga och fysiska begränsningar). + +Tänk dock på att det bara fungerar att genomkorsa en generator med en `for`-loop om generatorn avslutas vid någon punkt. Om generatorn är uppbyggd på en oändlig loop kommer en enkel `for`-loop att orsaka en oändlig exekvering, precis som en `while`-loop utan slut- eller brytvillkor. + Kirjoita generaattorifunktio `parilliset(alku: int, maksimi: int)`, joka saa parametrikseen alkuarvon ja maksimin. Funktio tuottaa alkuarvosta lähtien parillisia lukuja. Kun saavutetaan maksimi, generaattori pysähtyy. @@ -188,11 +194,9 @@ Vinkki: Voit tarkastaa, onko luku _x_ alkuluku, silmukalla, joka käy läpi luvu -## Generaattorikoosteet +## Generator comprehensions -Generaattorin voi luoda myös listakoostetta (list comprehension) muistuttavalla syntaksilla. Erotuksena listakoosteeseen on, että lauseke ympäröidään kaarisulkeilla hakasulkeiden sijasta. - -Esimerkiksi +Du behöver inte nödvändigtvis en funktionsdefinition för att skapa en generator. Vi kan använda en struktur som liknar en list comprehension istället. Den här gången använder vi runda parenteser för att beteckna en generator i stället för en lista eller en ordlista: ```python # Generaattori palauttaa 2:n potensseja @@ -215,7 +219,7 @@ for i in range(5): -Toinen esimerkki, jossa generaattori tuottaa kolmimerkkisiä alijonoja englanninkielisistä aakkosista. Esimerkissä tulostetaan generaattorin 10 ensimmäistä alkiota: +I följande exempel skriver vi ut delsträngar av det engelska alfabetet, var och en tre tecken lång. Detta skriver ut de första 10 objekten i generatorn: ```python alijonot = ("abcdefghijklmnopqrstuvwxyz"[i : i + 3] for i in range(24)) @@ -269,5 +273,3 @@ ccc Huom! Voit ratkaista tehtävän itse valitsemallasi tavalla (eli käyttäen joko generaattorikoostetta tai "perinteistä" generaattoria). - - diff --git a/data/osa-12/3-funktionaalista-ohjelmointia.md b/data/osa-12/3-funktionaalista-ohjelmointia.md index 18d2ff49e..2c637428d 100644 --- a/data/osa-12/3-funktionaalista-ohjelmointia.md +++ b/data/osa-12/3-funktionaalista-ohjelmointia.md @@ -1,37 +1,39 @@ --- path: '/osa-12/3-funktionaalista-ohjelmointia' -title: 'Funktionaalista ohjelmointia' +title: 'Funktionell programmering' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät mitä tarkoitetaan funktonaalisella ohjelmoinilla -- Osaat hyödyntää operaatioita map, reduce ja filter omissa ohjelmissasi +- Vet du vad funktionell programmering innebär +- Kommer du att kunna använda funktionerna `map`, `reduce` och `filter` i dina egna program -Funktionaalisella ohjelmoinnilla tarkoitetaan _ohjelmointiparadigmaa_, jossa vältetään tilan muutoksia mahdollisimman pitkälle. Muuttujien sijasta ohjelman suoritus perustuu funktionaalisessa ohjelmoinnissa mahdollisimman pitkälti funktioiden keskinäisiin kutsuihin. +Funktionell programmering avser ett programmeringsparadigm som undviker förändringar i programtillståndet så mycket som möjligt. Variabler undviks i allmänhet. Istället är det kedjor av funktionsanrop som utgör ryggraden i programmet. -Aikaisemmin esitetyt lambda-lausekkeet ja listakoosteet ovat esimerkkejä funktionaalisesta ohjelmointityylistä, koska niitä käyttämällä voidaan välttää ohjelman tilan muutokset - esimerkiksi lambda-lausekkeella voimme luoda funktion ilman että viittausta siihen tallennetaan mihinkään. +Lambda-uttryck och olika typer av förståelser är vanliga tekniker i den funktionella programmeringsstilen, eftersom de låter dig bearbeta data utan att lagra dem i variabler, så att programmets tillstånd inte ändras. Ett lambdauttryck är till exempel i alla avseenden en funktion, men vi behöver inte lagra en namngiven referens till den någonstans. -Funktionaalinen ohjelmointi on esimerkki ohjelmointiparadigmasta eli ohjelmointityylistä. Muita tyypillisiä ja kurssilla jo aiemmin käsiteltyjä paradigmoja ovat esimerkiksi +Som nämnts ovan är funktionell programmering ett programmeringsparadigm, eller en programmeringsstil. Det finns många olika programmeringsparadigm, och vi har redan stött på några av dem: -* imperatiivinen paradigma, joka perustuu peräkkäisiin komentoihin ja niiden suorittamiseen järjestyksessä -* proseduraalinen paradigma, jossa ohjelma jaetaan pienempiin aliohjelmiin. Imperatiivinen ja proseduraalinen paradigma tarkoittavat joidenkin määrittelyjen mukaan samaa asiaa. -* olio-ohjelmointi, jossa ohjelma ja sen tila mallinnetaan luokista muodostettujen olioiden avulla. +* imperativ programmering, där programmet består av en sekvens av instruktioner som utförs i tur och ordning +* procedurprogrammering, där programmet är uppdelat i procedurer eller underprogram +* objektorienterad programmering, där programmet och dess tillstånd lagras i objekt som definieras i klasser. -Pythonin monipuolisuus tulee hyvin esille siinä, että voimme hyödyntää siinä useita eri paradigmoja - jopa samoissa ohjelmissa. Näin voimme hyödyntää tehokkainta ja selkeintä tapaa ongelmien ratkaisemiseksi. +Det finns olika uppfattningar om gränsdragningen mellan de olika paradigmen, t.ex. hävdar vissa att imperativ och procedurell programmering betyder samma sak, medan andra placerar imperativ programmering som ett paraplybegrepp som täcker både procedurell och objektorienterad programmering. Terminologin och uppdelningen är inte så viktig, och det är inte heller viktigt att strikt hålla sig till det ena eller andra paradigmet, men det är viktigt att förstå att det finns sådana olika synsätt eftersom de påverkar de val som programmerare gör. -Tarkastellaan vielä muutamaa funktionaalisen ohjelmoinnin työkalua Pythonissa. +Många programmeringsspråk är utformade med det ena eller det andra programmeringsparadigmet i åtanke, men Python är ett ganska mångsidigt programmeringsspråk och gör det möjligt att följa flera olika programmeringsparadigm, även inom ett enda program. Detta gör att vi kan välja den mest effektiva och tydliga metoden för att lösa varje problem. + +Låt oss ta en titt på några funktionella programmeringsverktyg som tillhandahålls av Python. ## map -Funktio `map` suorittaa annetun operaation kaikille annetun iteroitavan sarjan alkioille. Niinpä `map` muistuttaa koostetta monessa mielessä, syntaksi tosin näyttää erilaiselta. +Funktionen `map` utför någon operation på varje objekt i en iterabel serie. Det här låter ungefär som den effekt en comprehension har, men syntaxen är annorlunda. -Tarkastellaan esimerkkinä funktiokutsua, joka muuttaa merkkijonot kokonaisluvuiksi: +Låt oss anta att vi har en lista med strängar som vi vill konvertera till en lista med heltal: ```python mjonolista = ["123","-10", "23", "98", "0", "-110"] @@ -56,11 +58,13 @@ for luku in luvut: -Funktion `map` yleinen syntaksi on siis +Den allmänna syntaxen för `map`-funktionen är + +`map(, )` -`map(, )` +där `funktion` är den operation vi vill utföra på varje föremål i `serie`n. -Funktio palauttaa map-tyyppisen objektin, jonka voi joko iteroida läpi for-lauseella tai esimerkiksi muuttaa listaksi `list`-funktiolla: +`map`-funktionen returnerar ett objekt av typen `map`, som är itererbart och kan konverteras till en lista: ```python def alkukirjain_isoksi(mjono: str): @@ -82,9 +86,9 @@ print(valmiit_lista) -Kuten esimerkistä huomataan, map-funktiossa voi tietysti käyttää lambda-lausekkeella luodun funktion lisäksi myös `def`-avainsanalla aiemmin määriteltyä nimettyä funktiota. +Som du kan se i exemplen ovan accepterar `map`-funktionen både en anonym lambda-funktion och en namngiven funktion som definieras med nyckelordet `def`. -Edellinen esimerkki voitaisiin toteuttaa myös vaikkapa listakoosteen avulla, esimerkiksi: +Vi skulle kunna uppnå samma resultat med en list comprehension: ```python def alkukirjain_isoksi(mjono: str): @@ -99,11 +103,9 @@ valmiit_lista = [alkukirjain_isoksi(alkio) for alkio in testilista] print(valmiit_lista) ``` -...tai esimerkiksi iteroimalla lista läpi for-lauseella ja tallentamalla käsitellyt alkiot uuteen listaan `append`-metodilla. Onkin tyypillistä, että saman asian voi toteuttaa usealla eri tavalla. Eri vaihtoehtojen tunteminen auttaa valitsemaan niistä ohjelmaan (ja omaan makuun) parhaiten sopivan. - -Kannattaa huomata, että `map`-funktion palauttama lopputulos ei ole lista, vaan _iteraattori_-olio ja vaikka se käyttäytyykin listan tapaan monissa tilanteissa, niin näin ei ole aina. +...eller så kan vi gå igenom den ursprungliga listan med en `for`-loop och spara de bearbetade objekten i en ny lista med `append`-metoden. I programmering finns det vanligtvis många olika lösningar på varje problem. Det finns sällan några absolut rätta eller felaktiga svar. Att känna till många olika tillvägagångssätt hjälper dig att välja den mest lämpliga för varje situation, eller den som bäst passar din egen smak. -Tarkastellaan seuraavaa esimerkkiä: +Det är värt att påpeka att `map`-funktionen inte returnerar en lista, utan ett iteratorobjekt av typen map. En iterator beter sig på många sätt som en lista, men det finns undantag, vilket kan ses i följande exempel: ```python def alkukirjain_isoksi(mjono: str): @@ -124,7 +126,7 @@ for sana in valmiit: print(sana) ``` -Tulostus on seuraava: +Detta skulle skriva ut följande: @@ -136,9 +138,9 @@ sama uusiksi: -Eli kun `map`-funktion tuloksena olevat nimet yritetään tulostaa toiseen kertaan, ei tulostu mitään. Syynä tälle on se, läpikäynti `for`-lauseella käy iteraattorin oliot jo läpi, ja kun samaa yritetään toistamiseen, ei ole enää mitään läpikäytävää! +Ovan försökte vi skriva ut innehållet i `map`-iteratorn två gånger, men det andra försöket gav ingen utskrift. Anledningen är att `map` är en iterator; när man går igenom den med en `for`-loop "töms" den, ungefär som en generator töms när dess maximala värde har uppnåtts. När objekten i iteratorn har genomgåtts med en `for`-loop finns det inget kvar att gå igenom. -Jos ohjelma haluaa tarkastella `map`-funktion tulosta useampaan kertaan, tulee tulos esimerkiksi muuttaa listaksi antamalla se parametriksi `list`-konstruktorille: +Om du behöver gå igenom innehållet i en `map`-iterator mer än en gång kan du t.ex. konvertera map till en lista: ```python testilista = ["eka", "toka", "kolmas", "neljäs"] @@ -168,9 +170,9 @@ Neljäs -## map ja oliot +## Map-funktionen och dina egna klasser -Funktiolla `map` voidaan toki käsitellä myös omien luokkien olioita. Asiaan ei liity mitään tavanomaisesta poikkeavaa. Tarkastellaan seuraavaa esimerkkiä +Du kan naturligtvis också bearbeta instanser av dina egna klasser med `map`-funktionen. Det krävs inga speciella knep, som du kan se i exemplet nedan: ```python class Pankkitili: @@ -212,13 +214,13 @@ Maija Miljonääri -Koodissa selvitetään ensin funktion `map` avulla tilien omistajat. Huomaa miten lambda-funktiolla haetaan attribuuttina oleva nimi pankkitiliolioista: +Här samlar vi först in namnen på kontoinnehavarna med `map`-funktionen. En anonym lambda-funktion används för att hämta värdet på `namn`-attributet från varje Bankkonto-objekt: ```python asiakkaat = map(lambda t: t.nimi, tilit) ``` -Tämän jälkeen haetaan samalla tyylillä jokaisen pankkitilin saldo. Lambda-funktio on nyt hieman erilainen, sillä saldo saadaan selville kutsumalla pankkitiliolion metodia: +På samma sätt samlas saldot för varje Bankkonto in. Lambda-funktionen ser lite annorlunda ut, eftersom saldot hämtas med ett metodanrop, inte direkt från attributet: ```python saldot = map(lambda t: t.hae_saldo(), tilit) @@ -293,9 +295,9 @@ Hyödynnä funktion toteutuksessa `map`-funktiota. Se ei tosin yksistään riit ## filter -Funktio `filter` muistuttaa funktiota `map`, mutta nimensä mukaisesti se ei poimi kaikkia alkioita lähteestä, vaan ainoastaan ne, joille annettu funktio palauttaa arvon True. +Den inbyggda Python-funktionen `filter` liknar `map`-funktionen, men som namnet antyder tar den inte alla föremål från källan. Istället filtrerar den dem med en kriteriefunktion, som skickas som ett argument. Om kriteriefunktionen returnerar `True` väljs föremålet. -Tarkastellaan taas ensin esimerkkiä funktion käytöstä: +Låt oss titta på ett exempel med `filter`: ```python luvut = [1, 2, 3, 5, 6, 4, 9, 10, 14, 15] @@ -316,7 +318,7 @@ for luku in parilliset: -Sama esimerkki voitaisiin kirjoittaa ilman lambda-lauseketta määrittelemällä funktio `def`-avainsanalla: +Det kunde göra ovanstående exemplet en aning tydligare ifall vi använde en namngiven funktion istället: ```python def onko_parillinen(luku: int): @@ -332,9 +334,9 @@ for luku in parilliset: print(luku) ``` -Toiminnallisuuden kannalta ohjelmat ovat täysin yhtäläiset. Onkin mielipidekysymys kumpaa pitää selkeämpänä. +Dessa två program är funktionellt helt identiska. Det är mest en fråga om åsikt vilket du anser vara det bättre tillvägagångssättet. -Tarkastellaan vielä toista esimerkkiä suodattamisesta. Ohjelmassa poimitaan kalalistasta ainoastaan ne kalat, jotka ovat vähintään 1000 gramman painoisia: +Låt oss ta en titt på ett annat filtreringsexempel. Det här programmet modellerar fiskar och väljer bara ut dem som väger minst 1000 gram: ```python class Kala: @@ -369,15 +371,15 @@ Turska (2449 g.) -Taas kerran sama voitaisiin toteuttaa listakoosteena: +Vi kunde lika väl använda oss av en list comprehension för att uppnå samma resultat: ```python ylikiloiset = [kala for kala in kalat if kala.paino >= 1000] ``` -## filter palauttaa iteraattorin +## Returvärdet för filter är en iterator -Funktion `map` tapaan, myös funktio `filter` palauttaa listan sijaan _iteraattorin_ ja on tilanteita joissa on syytä olla varuillaan sillä iteraattorin voi käydä läpi vain kerran. Eli seuraava yritys tulostaa suuret kalat kahteen kertaan ei onnistu: +Funktionen `filter` liknar funktionen `map` även i det avseendet att den returnerar en iterator. Det finns situationer där du bör vara särskilt försiktig med `filter` eftersom iteratorer bara kan genomlöpas en gång. Så att försöka skriva ut samlingen av stora fiskar två gånger kommer inte att fungera så enkelt som du kanske tror: ```python k1 = Kala("Hauki", 1870) @@ -399,7 +401,7 @@ for kala in ylikiloiset: print(kala) ``` -Tulostuu +Detta skulle skriva ut följande: @@ -410,7 +412,7 @@ sama uudelleen -Jos funktion `filter` tulosta on tarve käsitellä useaan kertaan, tulee se muuttaa esimerkiksi listaksi: +Om du behöver gå igenom innehållet i en `filter` iterator mer än en gång kan du konvertera resultatet till en lista: ```python kalat = [k1, k2, k3, k4, k5] @@ -496,11 +498,11 @@ Toteuta funktio käyttäen funktioita `filter` ja `map`. ## reduce -Viimeinen tarkastelemamme funktio on `reduce`. Kuten funktion nimi vihjaa, sen tarkoituksena on vähentää sarjan alkioiden määrä. Itse asiassa alkioiden sijasta `reduce` palauttaa yksittäisen arvon. +En tredje hörnstensfunktion i denna introduktion till funktionella programmeringsprinciper är `reduce`, från modulen `functools`. Som namnet antyder är dess syfte att reducera objekten i en serie till ett enda värde. -Reduce toimii sitten, että se pitää mukanaan koko ajan _arvoa_, jota se muuttaa yksi kerrallaan käydessään läpi listan alkioita. +`reduce`-funktionen börjar med en operation och ett startvärde. Den utför den givna operationen på varje objekt i serien i tur och ordning, så att värdet ändras i varje steg. När alla objekt har bearbetats returneras det resulterande värdet. -Seuraavassa on esimerkki, joka summaa `reduce`-funktion avulla listan luvut yhteen. Huomaa, että Pythonin versiosta 3 alkaen funktio `reduce` pitää erikseen ottaa käyttöön moduulista `functools`. +Vi har gjort summering av listor med heltal på olika sätt tidigare, men här har vi ett exempel med hjälp av funktionen `reduce`. Notera `import`-satsen; i Python version 3 och senare är den nödvändig för att komma åt `reduce`-funktionen. I äldre Python-versioner behövdes inte `import`-satsen, så du kan stöta på exempel utan den på nätet. ```python from functools import reduce @@ -518,17 +520,17 @@ print(lukujen_summa) -Tarkastellaan esimerkkiä hieman tarkemmin. Funktio `reduce` saa kolme parametria. Parametreista toisena on läpikäytävä lista, ja kolmantena on laskennan alkuarvo. Koska laskemme listan alkioiden summaa, on sopiva alkuarvo nolla. +Låt oss ta en närmare titt på vad som händer här. `reduce`-funktionen tar emot tre argument: en funktion, en serie av föremål och ett startvärde. I det här fallet är serien en lista med heltal, och eftersom vi beräknar en summa är ett lämpligt startvärde noll. -Ensimmäisenä parametrina on funktio, joka suorittaa toimenpiteen yksi kerrallaan kullekin listan alkiolle. Tällä kertaa funktio on seuraava: +Det första argumentet är en funktion, som representerar den operation vi vill utföra på varje objekt. Här är funktionen en anonym lambda-funktion: ```python lambda summa, alkio: summa + alkio ``` -Funktiolla on kaksi parametria. Näistä ensimmäinen on laskennan sen hetkinen tulos ja toinen parametri on käsittelyvuorossa oleva listan alkio. Funktio laskee uuden arvon parametriensa perusteella. Tässä tapauksessa uusi arvio on vanha summa _plus_ kyseisen alkion arvo. +Denna funktion tar två argument: det aktuella reducerade värdet och det föremål vars tur det är att bearbetas. Dessa används för att beräkna ett nytt värde för det reducerade värdet. I detta fall är det nya värdet summan av det gamla värdet och det aktuella objektet. -Funktion `reduce` toiminta hahmottuu kenties selkeämmin, jos käytetään lambdan sijaan normaalia funktiota apuna ja tehdään funktiosta aputulostuksia: +Det kan vara lättare att förstå vad funktionen `reduce` faktiskt gör om vi använder en vanlig namngiven funktion i stället för en lambda-funktion. På så sätt kan vi också inkludera användbara utskrifter: ```python from functools import reduce @@ -546,7 +548,7 @@ lukujen_summa = reduce(summaaja, lista, 0) print(lukujen_summa) ``` -Ohjelma tulostaa: +Detta program skriver ut: @@ -558,11 +560,11 @@ summa nyt 6, vuorossa alkio 5 -Ensimmäisenä siis käsitellään listan alkio, jonka arvo on 2. Tässä vaiheessa summa on 0, eli sillä on reducelle annettu alkuarvo. Funktio laskee ja palauttaa näiden summan eli 0 + 2. +Först tar funktionen hand om objektet med värdet 2. Till att börja med är den reducerade summan 0, vilket är det ursprungliga värdet som skickas till `reduce`-funktionen. Funktionen beräknar och returnerar summan av dessa två: `0 + 2 = 2`. -Tämä arvo on parametrin `summa` arvona kun funktiota kutsutaan seuraavalle listan alkiolle eli luvulle 3. Funktio laskee ja palauttaa 2 + 3, joka taas toimii parametrina seuraavalle funktiokutsulle. +Detta är det värde som lagras i `reducerad_summa` när `reduce`-funktionen bearbetar nästa objekt i listan, med värdet 3. Funktionen beräknar och returnerar summan av dessa två: `2 + 3 = 5`. Detta resultat används sedan när nästa objekt bearbetas, och så vidare, och så vidare. -Toinen esimerkkimme laskee kaikkien listassa olevien kokonaislukujen tulon. +Nu är det enkelt att summera, eftersom det till och med finns en inbyggd `sum`-funktion för detta ändamål. Men hur är det med multiplikation? Det krävs bara små förändringar för att skapa en reducerad produkt: ```python from functools import reduce @@ -580,11 +582,11 @@ print(tulo) -Koska on kyse tulosta, ei alkuarvo voi olla nyt 0 (miten käy jos se olisi nolla?), vaan sopiva arvo sille on 1. +Eftersom vi har att göra med multiplikation är startvärdet inte noll. Istället använder vi 1. Vad skulle hända om vi använde 0 som startvärde? -Aivan kuten `filter` ja `map`, myös `reduce` voi käsitellä minkä tahansa tyyppisiä olioita. +Ovan har vi till stor del behandlat heltal, men `map`, `filter` och `reduce` kan alla hantera en samling objekt av alla typer. -Tarkastellaan esimerkkinä pankin tilien yhteenlasketun saldon selvittämistä reducella: +Låt oss som ett exempel generera en totalsumma av saldona för alla konton i en bank med hjälp av `reduce`: ```python class Pankkitili: @@ -617,7 +619,7 @@ print("pankissa rahaa yhteensä") print(saldot_yhteensa) ``` -Ohjelma tulostaa: +Detta program skulle skriva ut: @@ -626,7 +628,7 @@ pankissa rahaa yhteensä -Huomaa miten funktio `saldojen_summaaja` "kaivaa" saldon jokaisen tiliolion sisältä kutsumalla tilille saldon palauttavaa metodia: +Funktionen `saldo_summa_hjalpare` tar saldot på varje bankkonto, med den metod som är avsedd för ändamålet i klassdefinitionen `Bankkonto`: ```python def saldojen_summaaja(yht_saldo, tili): @@ -647,19 +649,19 @@ print(lukujen_summa) Jos alkuarvoa ei anneta, toimii listan ensimmäinen luku alkuarvona ja "redusointi" aloitetaan vasta listan toisesta alkiosta. -Huomaa, että jos käsiteltävän listan alkiot ovat eri tyyppisiä kuin laskettava arvo, on `reduce`-funktion kolmas parametri välttämätön. Jos yrittäisimme olla tili-esimerkissä käyttämättä alkuarvoa: +OBS: Om föremålen i serien är av en annan typ än det avsedda reducerade resultatet, är det tredje argumentet obligatoriskt. Exemplet med bankkontona skulle inte fungera utan det ursprungliga värdet. Det vill säga att prova detta ```python saldot_yhteensa = reduce(saldojen_summaaja, tilit) ``` -olisi seurauksena virhe +Skulle producera ett fel: ```python TypeError: unsupported operand type(s) for +: 'Pankkitili' and 'int' ``` -sillä `reduce`-funktion parametri `yht_saldo` saisi ensimmäisellä kerralla arvokseen listan ensimmäisen pankkitilin ja sen summaaminen pankkitilin saldoon ei olisi mahdollista. +I ovanstående fall, när `reduce` försöker utföra funktionen `saldo_summa_hjalpare` för första gången, är de argument som används de två första föremålen i listan, som båda är av typen Bankkonto. Specifikt är det värde som tilldelats parametern `saldo_summa` det första föremålet i listan. Funktionen `saldo_summa_hjalpare` försöker lägga till ett heltalsvärde till den, men att lägga till ett heltal direkt till ett Bankkonto-objekt är inte en åtgärd som stöds. diff --git a/data/osa-12/4-saannolliset-lausekkeet.md b/data/osa-12/4-saannolliset-lausekkeet.md index b882a969c..185c07cc5 100644 --- a/data/osa-12/4-saannolliset-lausekkeet.md +++ b/data/osa-12/4-saannolliset-lausekkeet.md @@ -1,26 +1,25 @@ --- path: '/osa-12/4-saannolliset-lausekkeet' -title: 'Säännölliset lausekkeet' +title: 'Reguljära uttryck' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät mitä tarkoitetan säännöllisellä lausekkeella -- Osaat hyödyntää säännöllisiä lausekkeita omissa ohjelmissasi +- Vet du vad reguljära uttryck är +- Kommer du att kunna använda reguljära uttryck i dina egna program -Python on mainio työkalu tekstin käsittelemiseen. Yksi työkalu tekstin käsittelemisessä ovat -_säännölliset lausekkeet_ (_regular expressions_), joiden avulla voi esimerkiksi poimia ja etsiä merkkijonoja, jotka ovat tietyn muotoisia. Tässä osiossa käydään läpi säännöllisten lausekkeiden perusteita, ja löydät lisää tietoa Pythonin omasta [tutoriaalista](https://docs.python.org/3/howto/regex.html). +Vi har redan konstaterat att Python är en utmärkt miljö för att bearbeta text. Ett ytterligare kraftfullt verktyg för textbehandling är reguljära uttryck (eng. regular expressions), ofta förkortat som regex eller regexp. De är ett sätt att välja ut och söka efter strängar som följer ett visst mönster. I det här avsnittet får du en introduktion till grunderna i reguljära uttryck, men du hittar mycket mer information på nätet, bland annat i Pythons egna [handledning](https://docs.python.org/3/howto/regex.html). -## Mitä ovat säännölliset lausekkeet? +## Vad är reguljära uttryck? -Säännölliset lausekkeet ovat tavallaan ohjelmointikieli ohjelmointikielen sisällä. Lausekkeilla on oma syntaksinsa, jonka mukaan ne määritellään. Ideana on, että säännöllisellä lausekkeella määritellään sellaisten merkkijonojen joukko, jotka ovat tiettyjen sääntöjen mukaisia. +Reguljära uttryck är inte bara en Python-funktion. De representerar på sätt och vis ett programmeringsspråk inom ett programmeringsspråk. De är i viss utsträckning kompatibla med många olika programmeringsspråk. Reguljära uttryck har sin egen specifika syntax. Tanken är att definiera en samling strängar som följer vissa regler. -Tarkistellaan yksinkertaista esimerkkiä lausekkeiden käytöstä ennen tarkempaa perehtymistä sääntöihin: +Låt oss börja med ett enkelt exempel innan vi dyker djupare in i syntaxen: ```python import re @@ -41,11 +40,9 @@ Pallon löytyy! -Pythonissa säännöllisiä lausekkeita voi käsitellä moduulin `re` avulla. Esimerkiksi yllä olevassa koodissa oleva metodi `search` etsii merkkijonosta osaa, joka täsmää annettuun säännölliseen lausekkeeseen. +Vi behöver importera modulen `re` för att kunna använda reguljära uttryck i Python. Modulen `re` innehåller många funktioner för att arbeta med reguljära uttryck. I exemplet ovan tar `search`-funktionen två strängargument: mönstersträngen och den målsträng där mönstret ska sökas. -Huomaa, että säännöllinen lauseke annetaan _merkkijonona_ funktion `search` parametriksi. - -Toinen esimerkki etsii merkkijonosta luvut. Metodi `findall` palauttaa kaikki säännölliseen lausekkeeseen täsmäävät osajonot listana: +I det här andra exemplet letar man efter alla siffror i en sträng. Funktionen `findall` returnerar en lista över alla instanser som matchar mönstret: ```python import re @@ -67,9 +64,9 @@ for luku in luvut: -## Säännöllisten lausekkeiden syntaksi +## Syntaxen för reguljära uttryck -Tarkastellaan seuraavaksi syntaksia, jota säännöllisissä lausekkeissa käytetään. Useimmissa esimerkeissä käytetään samaa testiohjelmaa eri syötteillä. +Låt oss bekanta oss med den grundläggande syntaxen för reguljära uttryck. De flesta av följande exempel använder sig av detta testprogram: ```python import re @@ -86,11 +83,11 @@ while True: print("Ei osumaa.") ``` -### Vaihtoehtoiset alijonot +### Alternativa delsträngar -Pystyviivalla voidaan erottaa vaihtoehtoisia osajonoja. Esimerkiksi lauseke `911|112` täsmää merkkijonoihin, joista löytyy joko osajono `911` tai osajono `112`. +Lodstrecket (eng. vertical bar) `|`, gör att du kan matcha alternativa delsträngar. Dess betydelse är alltså eller. Uttrycket `911|112` matchar t.ex. strängar som innehåller antingen delsträngen `911` eller delsträngen `112`. -Esimerkiksi +Ett exempel med testprogrammet: @@ -109,11 +106,15 @@ Ei osumaa. -### Merkkijoukot +### Grupper av tecken + +Hakparenteser används för att beteckna grupper av accepterade tecken. Uttrycket `[aeio]` skulle t.ex. matcha alla strängar som innehåller något av tecknen a, e, i eller o. -Hakasulkeiden väliin voidaan merkitä joukko hyväksyttyjä merkkejä. Esimerkiksi merkintä `[aeio]` täsmää jonoihin, joista löytyy jokin merkeistä a, e, i, tai o. Merkintätapa sallii myös väliviivan käytön. Merkintä `[0-68a-d]` hyväksyy jonot, joista löytyy numero nollasta kuuteen, kahdeksikko tai merkki väliltä a...d. Merkintä `[1-3][0-9]` hyväksyy kaksinumeroiset luvut väliltä 10...39. +Ett bindestreck är också tillåtet för att matcha intervall av tecken. Uttrycket `[0-68a-d]` skulle till exempel matcha alla strängar som innehåller en siffra mellan 0 och 6, eller en åtta, eller ett tecken mellan a och d. I den här notationen är alla intervall inkluderande. -Esimerkiksi: +Om du kombinerar två uppsättningar parenteser kan du matcha två tecken i följd. Till exempel skulle uttrycket `[1-3][0-9]` matcha alla tvåsiffriga tal mellan 10 och 39, inklusive. + +Ett exempel med testprogrammet: @@ -133,17 +134,17 @@ Ei osumaa. -### Toistaminen +### Upprepade matchningar -Lausekkeen osaa voidaan toistaa esimerkiksi seuraavien operaattorien avulla: +Varje del av ett uttryck kan upprepas med följande operatorer: -* `*` toistaa osaa minkä tahansa määrän kertoja (myös nolla) -* `+` toistaa osaa minkä tahansa määrän kertoja (ainakin yhden) -* `{m}` toistaa osaa täsmälleen `m` kertaa +* `*` upprepas hur många gånger som helst, inklusive noll +* `+` upprepas hur många gånger som helst, men minst en gång +* `{m}` upprepas exakt `m` gånger -Operaattorit viittaavat niitä edeltävään lausekkeen osaan. Esimerkiksi lauseke `ba+b` hyväksyy esimerkiksi osajonot `bab`, `baab` ja `baaaaaaaaaaab`. Lauseke `A[BCDE]*Z` puolestaan hyväksyy esimerkiksi osajonot `AZ`, `ADZ` tai `ABCDEBCDEBCDEZ`. +Dessa operatorer fungerar på den del av uttrycket som kommer omedelbart före operatorn. Uttrycket `ba+b` skulle t.ex. matcha delsträngarna `bab`, `baab` och `baaaaaaaaaaab`, bland andra. Uttrycket `A[BCDE]*Z` skulle matcha delsträngarna `AZ`, `ADZ` eller `ABCDEBCDEBCDEZ`, bland andra. -Esimerkiksi: +Ett exempel med testprogrammet: @@ -164,9 +165,11 @@ Ei osumaa. -### Muita erikoismerkkejä +### Andra specialtecken + +En punkt är ett jokertecken som kan matcha vilket enskilt tecken som helst. Uttrycket `c...o` skulle till exempel matcha alla delsträngar med fem tecken som börjar med ett `c` och slutar med ett `o`, till exempel `c-3po` eller `cello`. -Pisteellä merkitään mitä tahansa yksittäistä merkkiä. Niinpä merkintä `c...o` vastaa esimerkiksi merkkijonoja `c-3po` tai `combo`. Merkillä `^` voidaan määritellä, että osuman pitää löytyä merkkijonon alusta, ja vastaavasti merkillä `$`, että sen on oltava lopussa. Näillä voidaan näppärästi myös rajata sääntö koskemaan vain annettuja merkkejä: +Tecknet `^` anger att matchningen måste ske i början av strängen och `$` anger att matchningen måste ske i slutet av strängen. Dessa tecken kan också användas för att utesluta andra tecken än de angivna från matchningen: @@ -180,9 +183,7 @@ Osuma! -Kenoviivaa voidaan käyttää etsimään erikoismerkkejä. Merkintä `1+` tarkoittaa yhtä tai useampaa ykköstä, mutta merkintä `1\+` merkkijonoa `1+`. - -Esimerkiksi +Ibland behöver du matcha för specialtecken som är reserverade för syntaxen för reguljära uttryck. Omvänt snedstreck (eng. backslash) `\` kan användas för att undkomma specialtecken. Uttrycket `1+` matchar alltså ett eller flera tal `1`, men uttrycket `1\+` matchar strängen `1+`. @@ -196,9 +197,7 @@ Osuma! -Kaarisulkeilla voidaan ryhmitellä lausekkeen osia. Esimerkiksi lauseke `(ab)+c` hyväksyy jonot `abc`, `ababc` ja `ababababababc`, mutta ei esimerkiksi jonoja `ac` tai `bc`. - -Esimerkiksi +Runda parenteser kan användas för att gruppera ihop olika delar av uttrycket. Till exempel skulle uttrycket `(ab)+c` matcha delsträngarna `abc`, `ababc` och `abababababababc`, men inte strängarna `ac` eller `bc`, eftersom hela delsträngen `ab` måste förekomma minst en gång. @@ -280,11 +279,11 @@ False -## Loppuhuipennus +## Den stora finalen -Harjoitellaan vielä osan lopussa hieman laajemman ohjelman tekemistä olioita hyödyntäen. Tämä tehtävä ei sijainnistaan huolimatta liity mitenkään säännöllisiin lausekkeisiin, mutta luvun [Funktio parametrina](/osa-12/1-funktio-parametrina) asia tulee olemaan tarpeen ja myös [listakoosteet](/osa-11/1-koosteet) voivat olla käyttökelpoisia. +Som avslutning på denna del av materialet ska vi arbeta lite mer med objekt och klasser genom att bygga ett lite mer omfattande program. Denna övning innefattar inte nödvändigtvis reguljära uttryck, men avsnitten om [Funktioner som argument ](/osa-12/1-funktio-parametrina) och [list comprehension](/osa-11/1-koosteet) kommer sannolikt att vara användbara. -Sovelluksen rakenteelle voi ottaa inspiraatiota osan 10 [viimeisestä luvusta](/osa-10/4-lisaa-esimerkkeja). +Du kan också ha nytta av de exempel som finns i [del 10](/osa-10/4-lisaa-esimerkkeja). @@ -486,5 +485,3 @@ komento: **0** Vastaa lopuksi osion loppukyselyyn: - - diff --git a/data/osa-13/1-pygame-kayttoon.md b/data/osa-13/1-pygame-kayttoon.md index eb9d25409..c5c92eeff 100644 --- a/data/osa-13/1-pygame-kayttoon.md +++ b/data/osa-13/1-pygame-kayttoon.md @@ -1,67 +1,67 @@ --- path: '/osa-13/1-pygame-kayttoon' -title: 'Pygame käyttöön' +title: 'Pygame' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Olet asentanut Pygame-kirjaston koneellesi -- Osaat luoda ikkunan ja sulkea ohjelman -- Osaat piirtää ikkunaan tiedostossa olevan kuvan +- Har du installerat pygame-biblioteket på din dator +- Vet du hur man skapar ett pygame-fönster och hur man avslutar ett program +- Kommer du att kunna använda en bild som lagras i en fil i ett pygame-fönster -Kurssin kahdella viimeisellä viikolla tutustumme Pygame-kirjastoon, joka on peliohjelmointiin tarkoitettu Python-kirjasto. Pygamen avulla pystyy piirtämään grafiikkaa, käsittelemään näppäimistön ja hiiren tapahtumia ja tekemään muuta peleissä tarvittavaa. +I de här två sista delarna av kursmaterialet kommer vi att bekanta oss med pygame-biblioteket. Det är ett Python-bibliotek för programmering av spel. Det hjälper dig att skapa grafiska element, hantera händelser från tangentbordet och musen samt implementera andra funktioner som är nödvändiga i spel. -## Pygamen asentaminen +## Att installera pygame ### Linux -Avaa komentorivi ja kirjoita `pip3 install pygame`. +Öppna en instruktionsrad, skriv in `pip3 install pygame` och tryck på `enter`. +Detta borde installera pygame-biblioteket på din dator. + ### Windows -Avaa Windowsin terminaali napauttamalla vasemman alakulman Windows-painiketta. Kirjoita aukeavaan ikkunaan `cmd` ja paina enter: +Öppna Windows-terminalen genom att öppna menyn, skriva `cmd` och trycka på `enter`: -Kirjoita auenneeeseen komentokehoteikkunaan seuraava komentosarja ja paina `enter`: - -`pip3 install pygame` +Fönstret för kommandoradstolken borde öppnas. Skriv in `pip3 install pygame` och tryck på `enter`. -Tämä asentaa Pygame-paketin koneellesi. +Detta borde installera pygame-biblioteket på din dator. -Asennus voi vaatia järjestelmänvalvojan oikeuksia. Jos ylläoleva ei toimi, voit yrittää ajaa terminaalin järjestelmänvalvojana (valitse Windows-valikko, paina hiiren kakkospainiketta CMD-valinnan päällä ja valitse "Run as administrator" tai "Aja järjestelmänvalvojana"). +Installationen kan kräva systemadministratörsbehörighet. Om ovanstående inte fungerar kan du prova på att köra terminalprogrammet som administratör: öppna Windows-menyn, leta reda på CMD-programmet, högerklicka på det och välj "Kör som administratör". -Huomaa, että asennus vaatii että olet asennusvaiheessa ohjeiden mukaisesti valinnut kohdan "Add Python 3.XX to path", katso [ohjeet](https://www.mooc.fi/fi/installation/vscode#python3) +För att installera och få åtkomst till pygame krävs att din Python-installation läggs till i sökvägen, enligt [instruktionerna](https://www.mooc.fi/fi/installation/vscode#python3) här. ### Mac -Avaa _Terminaali_, esim. painamalla oikean yläkulman suurennuslasi-symbolia: +Öppna Terminalen, t.ex. genom förstoringsglas-symbolen i det övre högra hörnet: - Kirjoita aukeavaan teksikenttään `terminal` ja paina enter: +Sökverktyget borde öppna. Skriv in `terminal` och tryck `enter`: -Kirjoita auenneeeseen komentokehoteikkunaan seuraava komentosarja ja paina `enter`: +Skriv in följande och tryck `enter`: `pip3 install pygame` -Tämä asentaa Pygame-paketin koneellesi. +Detta borde installera pygame-biblioteket på din dator. -## Ensimmäinen ohjelma +## Ditt första program -Tässä on yksinkertainen Pygamea käyttävä testiohjelma: +Här är ett enkelt program för att kontrollera att din pygame-installation fungerar korrekt: ```python import pygame @@ -78,31 +78,31 @@ while True: exit() ``` -Kun ohjelma käynnistetään, se näyttää käyttäjälle seuraavanlaisen ikkunan: +När detta program körs borde det visa ett fönster: -Ohjelmassa ei ole kuitenkaan vielä muuta sisältöä kuin ikkunan näyttäminen. Ohjelman suoritus jatkuu niin kauan, kunnes käyttäjä sulkee ikkunan. +Programmet visar endast ett fönster, och det körs tills användaren stänger fönstret. -Katsotaan seuraavaksi tarkemmin, miten ohjelma on rakentunut. Ohjelman alussa rivi `import pygame` ottaa mukaan Pygame-kirjaston. Kirjaston käyttäminen alkaa kutsumalla funktiota `pygame.init`, minkä jälkeen ohjelma luo ikkunan funktiolla `pygame.display.set_mode`. +Låt oss ta en närmare titt på de steg som krävs för att uppnå detta. Den första raden tar pygame-biblioteket i bruk: `import pygame`. Nästa instruktion, `pygame.init`, initierar pygame-modulerna, och nästa skapar ett fönster med funktionen `pygame.display.set_mode`. ```python pygame.init() naytto = pygame.display.set_mode((640, 480)) ``` -Muuttujan `naytto` kautta ikkunaan voidaan viitata myöhemmin esimerkiksi grafiikan piirtämistä varten. Parametri `(640, 480)` tarkoittaa, että tässä ohjelmassa ikkunan leveys on 640 pikseliä ja korkeus on 480 pikseliä. +Funktionen `set_mode` tar fönstrets dimensioner som ett argument. Tupeln `(640, 480)` anger att fönstret är 640 pixlar brett och 480 pixlar högt. Variabelnamnet `fonster` kan senare användas för att komma åt fönstret, t.ex. för att rita något i det. -Seuraavaksi ohjelmassa on kaksi komentoa: +De följande två instruktionerna gör just detta: ```python naytto.fill((0, 0, 0)) pygame.display.flip() ``` -Metodi `fill` täyttää näytön annetulla värillä. Tässä tapauksessa värinä on `(0, 0, 0)`, mikä tarkoittaa mustaa. Sitten metodi `pygame.display.flip` päivittää näytön sisällön. +Metoden `fill` fyller fönstret med den färg som anges som argument. I det här fallet är färgen svart, som skickas som ett RGB-värde i tupeln `(0, 0, 0)`. Metoden `pygame.display.flip` uppdaterar innehållet i fönstret. -Tämän jälkeen alkaa ohjelman _pääsilmukka_: +Efter dessa initialiseringsinstruktioner börjar programmets huvudloop: ```python while True: @@ -111,15 +111,15 @@ while True: exit() ``` -Pääsilmukka käsittelee tapahtumat, jotka käyttöjärjestelmä välittää ohjelmalle. Joka kierroksella funktio `pygame.event.get` antaa listan tapahtumista, jotka ovat syntyneet funktion edellisen kutsukerran jälkeen. +Huvudloopen hanterar alla händelser som operativsystemet skickar till programmet. Vid varje iteration returnerar funktionen `pygame.event.get` en lista över alla händelser som har samlats in sedan föregående iteration. -Tässä tapauksessa ohjelma käsittelee vain tyyppiä `pygame.QUIT` olevat tapahtumat. Tällainen tapahtuma syntyy, kun käyttäjä sulkee ohjelman esimerkiksi painamalla ikkunan ylänurkassa olevaa raksia. Tämän tapahtuman seurauksena ohjelma sulkee itsensä kutsumalla `exit`-funktiota. +I exemplet ovan hanterar programmet endast händelser av typen `pygame.QUIT`. Denna händelse uppstår t.ex. genom att man klickar på exit-knappen i fönstrets hörn. Om händelsen `pygame.QUIT` utlöses avslutas programmet genom funktionen exit. -Voit kokeilla, mitä tapahtuu, jos ohjelma ei käsittele tapahtumaa `pygame.QUIT`. Tällöin raksin painamisen ei pitäisi vaikuttaa ohjelman toimintaan, mikä on hämmentävää käyttäjälle. Ohjelman voi kuitenkin tässäkin tapauksessa sulkea väkisin komentoriviltä painamalla Control+C. +Du kan prova och se vad som händer om ditt program inte hanterar händelsen `pygame.QUIT`. Detta borde innebära att det inte händer någonting om man klickar på exit-knappen, vilket skulle vara förvirrande för användaren. Eftersom programmet körs från kommandoraden kan du fortfarande stoppa det från kommandoraden med Control+C. -## Kuva ohjelmaan +## Lägg till en bild -Laajennetaan seuraavaksi ohjelmaa niin, että se näyttää ikkunassa kuvan. Tämä onnistuu seuraavasti: +Låt oss lägga till en bild i fönstret: ```python import pygame @@ -139,21 +139,21 @@ while True: exit() ``` -Koodi käyttää kuvaa `robo.png`, jossa on robotin kuva: +Programmet använder denna bild av en robot, som finns lagrad i filen `robot.png`: -Tiedoston `robo.png` tulee olla samassa hakemistossa ohjelman lähdekoodin kanssa, jotta ohjelma löytää kuvan. Tämän viikon tehtävissä robotin kuva on valmiina tehtäväpohjissa. +Filen `robot.png` måste finnas i samma katalog som källkoden för ditt program, annars kan programmet inte hitta den. I övningsmallarna för denna del väntar bilderna i övningskatalogen. -Nyt ikkuna näyttää tältä: +Fönstret borde nu se ut så här: -Tässä funktio `pygame.image.load` lataa muuttujaan tiedostossa `robo.png` olevan kuvan. Tämän jälkeen metodi `blit` piirtää kuvan ikkunaan kohtaan `(100, 50)` ja sitten funktio `pygame.display.flip` päivittää ikkunan sisällön. Kohta `(100, 50)` tarkoittaa, että kuvan _vasen yläkulma_ on kyseisessä kohdassa. +Funktionen `pygame.image.load` laddar in bilden i filen `robot.png` och lagrar en referens till den i variabeln `robot`. Metoden `blit` ritar bilden på platsen `(100, 50)`, och funktionen `pygame.display.flip` uppdaterar fönstrets innehåll, som tidigare. Platsen `(100, 50)` innebär att bildens övre vänstra hörn befinner sig på den platsen i fönstret. -Huomaa, että Pygamessa ja yleensä muutenkin ohjelmoinnissa koordinaatisto on rakennettu niin, että piirtoalueen vasen yläkulma on kohdassa `(0, 0)` ja koordinaatit kasvavat x-suunnassa oikealle ja y-suunnassa alaspäin. Tässä tapauksessa ikkunan oikean alakulman koordinaatit ovat `(640, 480)`. +I pygame ligger origo-punkten `(0, 0)` i fönstrets övre vänstra hörn. X-koordinaterna ökar åt höger och y-koordinaterna ökar nedåt, så att det nedre högra hörnet har koordinaterna `(640, 480)`. Detta är tvärtemot hur koordinater brukar hanteras inom t.ex. matematiken, men det är ganska vanligt i programmeringssammanhang och värt att vänja sig vid. -Kuvan voi piirtää moneenkin kohtaan ikkunassa. Esimerkiksi seuraava koodi piirtää kuvan kolmeen eri kohtaan: +När du har laddat en bild kan du använda den många gånger i samma fönster. I följande kod ritas bilden av roboten på tre olika platser: ```python naytto.blit(robo, (0, 0)) @@ -161,11 +161,11 @@ naytto.blit(robo, (300, 0)) naytto.blit(robo, (100, 200)) ``` -Tällöin ikkuna näyttää seuraavalta: +Resultatet borde vara att fönstret ser ut så här: -Seuraava koodi puolestaan piirtää kuvan ikkunan keskelle: +Här sätter vi lokationen av bilden så, att den ligger i mitten av fönstret: ```python leveys = robo.get_width() @@ -173,17 +173,17 @@ korkeus = robo.get_height() naytto.blit(robo, (320-leveys/2, 240-korkeus/2)) ``` -Nyt ikkuna näyttää tältä: +Fönstret borde nu se ut så här: -Tässä metodi `get_width` antaa kuvan leveyden ja vastaavasti metodi `get_height` antaa kuvan korkeuden. Ikkunan keskikohta on `(320, 240)`, joten tämän avulla saadaan laskettua sopiva kohta kuvan vasemmalle yläkulmalle niin, että kuva sijoittuu ikkunan keskelle. +Metoden `get_width` ger bildens bredd och metoden `get_height` ger dess höjd, båda i pixlar. Fönstrets mittpunkt ligger på halva bredden och höjden, alltså på `(320, 240)`, vilket vi kan använda för att beräkna en lämplig plats för bildens övre vänstra hörn, så att det ligger exakt i mitten. - + -Tämän osan tehtävissä ei ole automaattisia testejä, vaan testi antaa pisteet automaattisesti, kun lähetät ratkaisun palvelimelle. Lähetä ratkaisu vasta sitten, kun se on valmis ja vastaa tehtävänannon vaatimuksia. Vaikka tehtävissä ei ole testejä, kurssin henkilökunta näkee lähetetyt ratkaisut. Myös keskeneräisen ratkaisun lähettäminen TMC Pasteen antaa pisteet automaattisesti, joten sitä ei tule käyttää kysyessä apua tämän osan tehtäviin. Voit kurssin tukikanavilla apua kysyessä käyttää [Pastebin.com](https://pastebin.com/)ia tai jotain muuta internetin pastebin-palvelua. +Övningarna i den här delen av kursen har inga automatiserade tester, eftersom resultaten verifieras visuellt. Testerna ger poäng automatiskt när du skickar in din lösning till servern, oavsett hur du har implementerat den. Skicka bara in din lösning när du är redo och din lösning matchar övningsbeskrivningen. Övningarna kanske inte har automatiska tester, men kurspersonalen kommer ändå att se din lösning. Att skicka in en ofullständig lösning till TMC Paste ger också poäng automatiskt, så det bör inte användas när du ber om hjälp med övningarna i den här delen. Du kan använda [Pastebin.com](https://pastebin.com/) eller någon annan pastebin-tjänst på internet när du ber om hjälp i kursens stödkanaler. -Jos lähetät palvelimelle ratkaisun, joka selkeästi ei vastaa tehtävänantoa, voit menettää pisteet tämän osan tehtävistä. +Om din lösning helt klart inte stämmer överens med övningsbeskrivningen kan du förlora de poäng som du har fått för övningarna i den här delen. diff --git a/data/osa-13/2-animaatio.md b/data/osa-13/2-animaatio.md index d35988b50..9b984ba1a 100644 --- a/data/osa-13/2-animaatio.md +++ b/data/osa-13/2-animaatio.md @@ -1,24 +1,24 @@ --- path: '/osa-13/2-animaatio' -title: 'Animaatio' +title: 'Animation' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, miten voi toteuttaa animaation -- Osaat tahdistaa ohjelman nopeuden kellon avulla -- Osaat käyttää trigonometriaa animaatiossa +- Vet du hur man skapar en animation med pygame +- Kommer du att kunna använda en klocka för att ställa in hastigheten på ditt program +- Kommer du att kunna använda grundläggande trigonometriska funktioner i dina animationer -Monissa peleissä on tarvetta saada aikaan liikkuvia hahmoja, joten seuraava luonteva askel on opetella animaation tekeminen. Animaatio syntyy, kun kuva piirretään eri kohtiin näytöllä sopivasti ajastettuna. +Många spel har rörliga karaktärer, så ett logiskt nästa steg är att skapa animationer. Vi kan skapa en illusion av rörelse genom att rita samma bild på olika ställen på skärmen och tajma ändringarna på rätt sätt. -## Animaation tekeminen +## Skapa en animation -Seuraava koodi luo animaation, jossa robotti kulkee vasemmalta oikealle ikkunassa: +Följande kod skapar en animation där en robot rör sig från vänster till höger i ett pygame-fönster: ```python import pygame @@ -45,24 +45,24 @@ while True: kello.tick(60) ``` -Ohjelman suoritus näyttää seuraavalta: +När detta exekveras, borde resultatet se ut så här: -Katsotaan taas tarkemmin, mitä ohjelmassa tapahtuu. Jotta kuva pystyy liikkumaan, ohjelmassa täytyy olla tieto sen paikasta. Tämä onnistuu ottamalla käyttöön kaksi muuttujaa, jotka sisältävät kuvan vasemman yläkulman koordinaatit: +Låt oss ta en närmare titt på de instruktioner som är inblandade. Om vi vill spåra bildens rörelse på skärmen måste vi veta var den befinner sig. Därför har vi två variabler för koordinaterna för bildens övre vänstra hörn: ```python x = 0 y = 0 ``` -Tämän lisäksi määritellään kello, jonka avulla pystyy huolehtimaan siitä, että animaation nopeus on sopiva: +Vi har även en klocka, som vi använder för att se till att animationens hastighet är korrekt: ```python kello = pygame.time.Clock() ``` -Pääsilmukan sisällä on koodi, joka piirtää kuvan sen nykyiseen paikkaan: +Huvudloopen ritar bilden på sin aktuella plats vid varje iteration: ```python naytto.fill((0, 0, 0)) @@ -70,29 +70,29 @@ Pääsilmukan sisällä on koodi, joka piirtää kuvan sen nykyiseen paikkaan: pygame.display.flip() ``` -Ensin kutsutaan metodia `fill`, joka tyhjentää ikkunan mustalla värillä. Väri määritellään RGB-muodossa parametrilla `(0, 0, 0)`, mikä tarkoittaa, että värin punainen, vihreä ja sininen komponentti on 0 eli väri on musta. Jokainen komponentti voi olla välillä 0–255. Esimerkiksi `(255, 255, 255)` on valkoinen ja `(255, 0, 0)` on punainen. Verkossa on monia työkaluja, joiden avulla voi tutkia RGB-värejä, kuten [RGB Color Codes Chart](https://www.rapidtables.com/web/color/RGB_Color.html). +Först fyller metoden fill fönstret med svart, precis som tidigare. Färgen skickas som en tupel som innehåller RGB-värdena för färgen. I det här fallet är argumentet `(0, 0, 0)`, vilket innebär att alla tre komponenterna - röd, grön och blå - har värdet 0. Varje komponent kan ha ett värde mellan 0 och 255. Om vi skickar `(255, 255, 255)` som argument får vi alltså ett vitt fönster, och med `(255, 0, 0)` får vi ett rött fönster. RGB-färgkoder utgör ryggraden i digital färgläggning, och det finns många verktyg online för att arbeta med dem, till exempel [RGB Color Codes Chart](https://www.rapidtables.com/web/color/RGB_Color.html). -Tämän jälkeen kuva piirretään tuttuun tapaan metodilla `blit` ja lopuksi ikkunan sisältö päivitetään funktiolla `pygame.display.flip`. +När fönstret har fyllts med färg ritas bilden på den angivna platsen med blit-metoden. Sedan uppdateras innehållet i fönstret med funktionen `pygame.display.flip`. -Silmukan päätteeksi muuttujan `x` arvo kasvaa, minkä ansiosta kuva liikkuu pikselin eteenpäin joka kierroksella: +Slutligen ökas värdet som lagras i x, vilket gör att bilden flyttas en pixel åt höger för varje iteration: ```python x += 1 ``` -Lisäksi silmukan lopussa suoritetaan kellon metodi `tick`: +Klock-metoden `tick` kallas i slutet: ```python kello.tick(60) ``` -Metodi `tick` huolehtii siitä, että animaation nopeus on sopiva: se tahdistaa silmukan niin, että silmukka pyritään suorittamaan 60 kertaa sekunnissa. Toisin sanoen kuva liikkuu sekunnissa 60 pikseliä oikealle. Tämä vastaa suunnilleen pelien yhteydessä käytettävää termiä _FPS_ (_frames per second_). +Metoden `tick` tar hand om hastigheten på animationen. Argumentet 60 anger att loopen ska exekveras `60` gånger per sekund, vilket innebär att bilden förflyttas 60 pixlar åt höger varje sekund. Detta motsvarar ungefär det värde för FPS eller bilder per sekund som används i spel. -Metodi `tick` on hyödyllinen, koska sen avulla animaatio toimii periaatteessa yhtä nopeasti jokaisella koneella. Jos silmukassa ei olisi tällaista ajastusta, pelin nopeus riippuisi siitä, kuinka nopeasti pelaajan kone toimii. +I princip ser `tick`-metoden till att animationen körs med samma hastighet på alla datorer. Om det inte fanns någon sådan timing skulle animationens hastighet bero på datorns hastighet. -## Seinään törmääminen +## Studsa av en vägg -Äskeinen animaatio on muuten hieno, mutta kun robotti etenee ikkunan ulkopuolelle, animaatio jatkuu ja robotti katoaa näkyvistä. Tehdään seuraavaksi ohjelmaan parannus, jonka avulla robotin suunta muuttuu, jos se törmää seinään. +Den föregående animationen var annars utmärkt, men när roboten nådde en vägg fortsatte den bara att försvinna ur syn. Låt oss få roboten att studsa mot väggen. ```python import pygame @@ -115,7 +115,7 @@ while True: naytto.fill((0, 0, 0)) naytto.blit(robo, (x, y)) pygame.display.flip() - + x += nopeus if nopeus > 0 and x+robo.get_width() >= 640: nopeus = -nopeus @@ -125,13 +125,13 @@ while True: kello.tick(60) ``` -Ohjelman suoritus näyttää nyt tältä: +Exekvering av koden ovan borde se ut så här: -Nyt ohjelmassa on uusi muuttuja `nopeus`, joka määrittää robotin liikkumistavan. Positiivinen nopeus tarkoittaa liikkumista oikealle ja negatiivinen nopeus tarkoittaa liikkumista vasemmalle. Tässä tapauksessa kun nopeus on 1, robotti liikkuu oikealle, ja kun nopeus on –1, robotti liikkuu vasemmalle. +Det finns en ny variabel, `hastighet`, som bestämmer rörelseriktningen. Om värdet är över noll sker förflyttningen åt höger och om det är under noll sker förflyttningen åt vänster. I det här fallet rör sig roboten åt höger om värdet är `1`, åt vänster om värdet är `-1`. -Seuraavat rivit huolehtivat, että robotti osaa törmätä seinään: +Följande rader gör att roboten studsar mot sidoväggarna: ```python if nopeus > 0 and x+robo.get_width() >= 640: @@ -140,13 +140,13 @@ Seuraavat rivit huolehtivat, että robotti osaa törmätä seinään: nopeus = -nopeus ``` -Jos nopeus on positiivinen eli robotti liikkuu oikealle ja sen oikea reuna menee ikkunan oikean reunan ulkopuolelle, robotin suunta muuttuu käänteiseksi eli se alkaa liikkua vasemmalle. Vastaavasti jos nopeus on negatiivinen ja robotin vasen reuna menee ikkunan vasemman reunan ulkopuolelle, suunta muuttuu taas käänteiseksi eli robotti alkaa liikkua oikealle. +Om hastigheten är över noll så att roboten rör sig åt höger, och högerkanten på bilden går utanför fönstrets högra kant, vänds riktningen och roboten börjar röra sig åt vänster. På samma sätt, om hastigheten är under noll så att roboten rör sig åt vänster, och bildens vänstra kant når fönstrets vänstra kant, vänds riktningen igen och roboten börjar röra sig åt höger igen. -Tämän koodin ansiosta robotti jatkaa loputtomasti rataa, jossa se liikkuu ensin koko ikkunan verran oikealle, sitten takaisin vasemmalle, sitten taas oikealle, jne. +Detta gör att roboten rör sig på en bana från fönstrets vänstra kant till den högra kanten, och tillbaka till vänster, och sedan till höger igen, upprepat i all oändlighet. -## Pyörivä animaatio +## Rotation -Tehdään vielä animaatio, jossa robotti _pyörii_ ikkunan keskipisteen ympärillä: +Låt oss skapa ytterligare en animation. Den här gången ska roboten rotera i en cirkel runt fönstrets mitt: ```python import pygame @@ -176,20 +176,20 @@ while True: kello.tick(60) ``` -Ohjelman suoritus näyttää tältä: +Exekvering av koden ovan borde se ut så här: -Pyörimisanimaatio saadaan toteutettua trigonometrian avulla: muuttujassa `kulma` on radiaaneina robotin sijainnin kulma suhteessa ikkunan keskipisteeseen. Tästä saadaan laskettua sini- ja kosinifunktioilla robotin sijainti: +Rotation i en relativt exakt cirkel uppnås med hjälp av några grundläggande trigonometriska funktioner. Variabeln `vinkel` innehåller vinkeln för robotens position i förhållande till fönstrets mittpunkt och den horisontella linjen som går genom fönstret. Sinus- och cosinusfunktionerna från Pythons matematikbibliotek används här för att beräkna koordinaterna för robotens position: ```python x = 320+math.cos(kulma)*100-robo.get_width()/2 y = 240+math.sin(kulma)*100-robo.get_height()/2 ``` -Tämä tarkoittaa, että robotin sijainti on ympyrällä, jonka säde on 100. Kosini antaa x-suuntaisen sijainnin ja sini puolestaan y-suuntaisen sijainnin. Jotta animaatio näyttää hyvältä, robotti lisäksi keskitetään niin, että sen keskipiste on ympyrällä. +Roboten roterar runt en cirkel med radien 100 runt fönstrets mittpunkt. Hypotenusan i detta scenario är cirkelns radie. Cosinusfunktionen anger längden på den angränsande sidan i en rätvinklig triangel i förhållande till hypotenusan, vilket innebär att den ger oss platsens `x`-koordinat. Sinusfunktionen ger längden på den motsatta sidan, dvs. `y`-koordinaten. Platsen justeras sedan för bildens storlek, så att cirkelns mittpunkt ligger i fönstrets mittpunkt. -Joka kierroksella muuttujan `kulma` arvo kasvaa 0.01:llä. Koska radiaaneissa täysi ympyrä on 2π eli noin 6.28, robotti pyörii suunnilleen kierroksen verran 10 sekunnissa. +För varje iteration ökar storleken på `vinkel` med 0,01. Eftersom vi använder radianer är en hel cirkel 2π, vilket motsvarar ca 6,28. Det tar cirka 628 iterationer för roboten att gå en hel cirkel, och med 60 iterationer per sekund tar detta drygt 10 sekunder. diff --git a/data/osa-13/3-tapahtumat.md b/data/osa-13/3-tapahtumat.md index ea2f31d0c..4af1091cf 100644 --- a/data/osa-13/3-tapahtumat.md +++ b/data/osa-13/3-tapahtumat.md @@ -1,24 +1,24 @@ --- path: '/osa-13/3-tapahtumat' -title: 'Tapahtumat' +title: 'Händelser' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Olet tutustunut Pygamen tapahtumiin -- Osaat tehdä ohjelman, joka lukee näppäimistön painalluksia -- Osaat tehdä ohjelman, joka lukee hiiren tapahtumia +- Kommer du att vara bekant med pygame händelser +- Kommer du att kunna skriva ett program som reagerar på tangenttryckningar +- Kommer du att kunna skriva ett program som reagerar på mushändelser -Tähän asti olemme toteuttaneet Pygame-ohjelman pääsilmukan niin, että se käy läpi tapahtumat ja tunnistaa tapahtuman `pygame.QUIT`, mutta ei käsittele muita tapahtumia. Nyt on aika tutustua tarkemmin tapahtumien käsittelyyn. +Hittills har våra huvudloopar bara kört förutbestämda animationer och reagerat på händelser av typen `pygame.QUIT`, trots att loopen får en lista över alla händelser från operativsystemet. Låt oss nu ta itu med några andra typer av händelser. -## Tapahtumien käsittely +## Hantering av händelser -Seuraava koodi näyttää, mitä tapahtumia syntyy ohjelman suorituksen aikana: +Det här programmet skriver ut information om alla händelser som skickas från operativsystemet till programmet pygame, medan det körs: ```python import pygame @@ -33,7 +33,7 @@ while True: exit() ``` -Kun ohjelmaa käytetään hetki, se voi tulostaa esimerkiksi seuraavanlaisia tapahtumia: +Låt oss anta att programmet kördes ett tag och att man sedan klickade på avslutningsknappen. Programmet skriver ut följande information: ```x @@ -51,13 +51,13 @@ Kun ohjelmaa käytetään hetki, se voi tulostaa esimerkiksi seuraavanlaisia tap ``` -Tässä ensimmäiset tapahtumat liittyvät hiiren käyttämiseen, seuraavat tapahtumat näppäimistön käyttämiseen ja viimeinen tapahtuma sulkee ohjelman. Jokaisella tapahtumalla on tyyppi ja mahdollisesti lisätietoa, josta voi päätellä esimerkiksi hiiren sijainnin tai painetun näppäimen. +De första händelserna gäller musanvändningen, därefter kommer några händelser från tangentbordet och slutligen stänger den sista händelsen programmet. Varje händelse har åtminstone en typ, men de kan också innehålla annan identifierande information, till exempel var muspekaren befinner sig eller vilken tangent som trycktes in. -Tapahtumia voi etsiä Pygamen dokumentaatiosta mutta usein tehokas tapa löytää sopiva tapahtuma on käyttää yllä olevaa koodia ja tutkia, millainen tapahtuma syntyy, kun ohjelmassa tapahtuu haluttu asia. +Du kan leta efter händelsebeskrivningar i pygame-dokumentationen, men det kan ibland vara enklare att skriva ut händelser med koden ovan och leta efter den händelse som inträffar när något du vill reagera på händer. -## Näppäimistön käsittely +## Tangentbordshändelser -Seuraava ohjelma tunnistaa tapahtumat, joissa käyttäjä painaa oikealle tai vasemmalle nuolinäppäintä. Ohjelma tulostaa testiksi tiedon näppäimen painamisesta. +Detta program kan behandla händelser där användaren trycker på piltangenten antingen till höger eller till vänster på sitt tangentbord. Programmet skriver ut vilken tangent som trycktes in. ```python import pygame @@ -77,9 +77,9 @@ while True: exit() ``` -Tässä vakiot `pygame.K_LEFT` ja `pygame.K_RIGHT` tarkoittavat nuolinäppäimiä vasemmalle ja oikealle. Näppäimistön eri näppäimiä vastaavat vakiot on listattu [Pygamen dokumentaatiossa](https://www.pygame.org/docs/ref/key.html#key-constants-label). +Konstanterna `pygame.K_LEFT` och `pygame.K_RIGHT` avser piltangenterna till vänster och höger. Konstanterna för pygame-tangenterna för de olika tangenterna på ett tangentbord anges i [Pygame dokumentationen](https://www.pygame.org/docs/ref/key.html#key-constants-label). -Esimerkiksi kun käyttäjä painaa ensin kahdesti oikealle, sitten kerran vasemmalle ja lopuksi kerran oikealle, ohjelman tulostus on seuraava: +Om användaren t.ex. trycker på piltangenten till höger två gånger, sedan den vänstra en gång och sedan den högra en gång till, skriver programmet ut ```x oikealle @@ -88,7 +88,7 @@ vasemmalle oikealle ``` -Voimme nyt tehdä ohjelman, jossa käyttäjä pystyy liikuttamaan hahmoa oikealle ja vasemmalle nuolinäppäimillä. Tämä onnistuu seuraavasti: +Vi har nu alla verktyg som behövs för att flytta en karaktär, eller sprite, på skärmen till höger och vänster med piltangenterna. Följande kod kommer att uppnå detta: ```python import pygame @@ -116,13 +116,13 @@ while True: pygame.display.flip() ``` -Ohjelman suoritus voi näyttää seuraavalta: +Beroende på hur du använder piltangenterna kunde programmet köra på följande sätt: -Tässä muuttujat `x` ja `y` sisältävät hahmon sijainnin. Käyttäjä pystyy muuttamaan muuttujaa `x`, ja muuttuja `y` on asetettu niin, että hahmo on ikkunan alalaidassa. Kun käyttäjä painaa vasemmalle tai oikealle nuolinäppäintä, hahmo liikkuu vastaavasti 10 pikseliä oikealle tai vasemmalle. +I koden ovan har vi variablerna `x` och `y` som innehåller sprite-koordinaterna. Variabeln `y` är inställd så att spriten visas längst ned i fönstret. Värdet för `y` ändras inte under hela körningen av programmet. `x`-värdet ökar däremot med 10 när användaren trycker på piltangenten till höger och minskar med 10 när användaren trycker på piltangenten till vänster. -Yllä oleva ohjelma toimii muuten hyvin, mutta pelikokemuksessa on puutteena, että näppäintä pitää painaa uudestaan aina, kun haluaa liikkua askeleen oikealle tai vasemmalle. Olisi parempi, että voi pitää näppäintä pohjassa ja hahmo liikkuu niin kauan, kuin näppäin on pohjassa. Seuraava koodi mahdollistaa tämän: +Programmet fungerar i övrigt ganska bra, men tangenten måste tryckas in igen varje gång vi vill förflytta oss igen. Det skulle vara bättre om rörelsen var kontinuerlig när tangenten hölls nedtryckt. Följande program erbjuder denna funktionalitet: ```python import pygame @@ -168,9 +168,9 @@ while True: kello.tick(60) ``` -Koodissa on nyt muuttujat `oikealle` ja `vasemmalle`, joissa pidetään tietoa siitä, kuuluuko hahmon liikkua tällä hetkellä oikealle tai vasemmalle. Kun käyttäjä painaa alas nuolinäppäimen, vastaava muuttuja saa arvon `True`, ja kun käyttäjä nostaa alas nuolinäppäimen, vastaava muuttuja saa arvon `False`. +Koden innehåller nu variablerna `till_hoger` och `till_vanster`. Dessa innehåller vetskap om huruvida spriten ska röra sig åt höger eller vänster vid ett givet tillfälle. När användaren trycker ner en piltangent blir värdet som lagras i den relevanta variabeln `True`. När tangenten släpps ändras värdet till `False`. -Hahmon liike on tahdistettu kellon avulla niin, että liikkumista tapahtuu 60 kertaa sekunnissa. Jos nuolinäppäin on alhaalla, hahmo liikkuu 2 pikseliä oikealle tai vasemmalle. Tämän seurauksena hahmo liikkuu 120 pikseliä sekunnissa, jos nuolinäppäin on painettuna. +Klockan används för att tidsbestämma spritens rörelser, så att de potentiellt sker 60 gånger per sekund. Om en piltangent trycks ned förflyttas spriten två pixlar åt höger eller vänster. Detta innebär att spriten rör sig 120 pixlar per sekund om tangenten hålls nedtryckt. @@ -196,9 +196,9 @@ Tee ohjelma, jossa kaksi pelaajaa voi ohjata omia robottejaan. Toinen pelaaja k -## Hiiren käsittely +## Händelser med musen -Seuraava koodi tunnistaa tapahtumat, jossa käyttäjä painaa hiiren nappia ikkunan alueella: +Följande kod reagerar på händelser där en musknapp trycks ned medan markören befinner sig inom fönsterområdet: ```python import pygame @@ -215,7 +215,7 @@ while True: exit() ``` -Ohjelman suoritus voi näyttää tältä: +Exekveringen av detta program borde mer eller mindre se ut så här: ```x painoit nappia 1 kohdassa (82, 135) @@ -224,9 +224,9 @@ painoit nappia 1 kohdassa (269, 297) painoit nappia 3 kohdassa (515, 324) ``` -Tässä nappi 1 tarkoittaa hiiren vasenta nappia ja nappi 3 tarkoittaa hiiren oikeaa nappia. +Knapp nummer 1 avser vänster musknapp och knapp nummer 3 avser höger musknapp. -Seuraava ohjelma yhdistää hiiren käsittelyn ja kuvan piirtämisen. Kun käyttäjä painaa hiirellä ikkunan alueella, robotti piirretään hiiren kohtaan. +Nästa program kombinerar hantering av mushändelser och ritning av en bild på skärmen. När användaren trycker på en musknapp medan muspekaren befinner sig inom fönstrets gränser ritas en bild av en robot på den platsen. ```python import pygame @@ -250,11 +250,11 @@ while True: exit() ``` -Ohjelman suoritus voi näyttää tältä: +Exekveringen av programmet borde se ut så här: -Seuraava ohjelma puolestaan toteuttaa animaation, jossa robotti seuraa hiirtä. Robotin sijainti on muuttujissa `robo_x` ja `robo_y`, ja kun hiiri liikkuu, sen sijainti merkitään muuttujiin `kohde_x` ja `kohde_y`. Jos robotti ei ole hiiren kohdalla, se liikkuu sopivaan suuntaan. +Följande program innehåller en animation där robotspriten följer muspekaren. Spritens position lagras i variablerna `robot_x` och `robot_y`. När musen rör sig lagras dess position i variablerna `mal_x` och `mal_y`. Om roboten inte befinner sig på denna plats förflyttar den sig i lämplig riktning. ```python import pygame @@ -296,7 +296,7 @@ while True: kello.tick(60) ``` -Ohjelman suoritus voi näyttää tältä: +Exekveringen av programmet borde se ut så här: diff --git a/data/osa-13/4-lisaa-tekniikoita.md b/data/osa-13/4-lisaa-tekniikoita.md index 6ac5ca0fd..d7916d232 100644 --- a/data/osa-13/4-lisaa-tekniikoita.md +++ b/data/osa-13/4-lisaa-tekniikoita.md @@ -1,30 +1,30 @@ --- path: '/osa-13/4-lisaa-tekniikoita' -title: 'Lisää tekniikoita' +title: 'Fler pygame-tekniker' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, miten ikkunan otsikkoa voi muuttaa -- Osaat piirtää kuvioita Pygamessa -- Osaat piirtää ikkunaan myös tekstiä +- Kommer du att veta hur man titlar pygame-fönstret +- Kommer du att kunna rita former med pygame +- Kommer du att veta hur du visar text i ditt fönster -## Ikkunan otsikko +## Fönstrets titel -Ohjelma näyttää ammattimaisemmalta, jos ikkunan otsikkopalkissa ei lue "pygame window" vaan ohjelman todellinen nimi. Tämä onnistuu näin: +Dina program kommer att se mer professionella ut om fönstertiteln istället för "pygame window" innehåller det faktiska namnet på programmet. Titeln ställs in med funktionen `pygame.display.set_caption`: ```python pygame.display.set_caption("Suuri seikkailu") ``` -## Kuvioiden piirtäminen +## Att rita former -Seuraava ohjelma luo kuvan, jossa on suorakulmio, ympyrä ja viiva: +Följande program ritar en rektangel, en cirkel och en linje på skärmen: ```python import pygame @@ -45,13 +45,13 @@ while True: exit() ``` -Ohjelman tulos näyttää tältä: +Körning av koden ovan borde se ut enligt följande: -## Tekstin piirtäminen +## Att rita text -Tekstin piirtäminen tapahtuu Pygame-kirjastossa niin, että ensin luodaan tekstiä vastaava kuva ja sen jälkeen piirretään kuva näytölle. Seuraava ohjelma esittelee asiaa: +Text i pygame ritas i två steg: först skapar vi en bild som innehåller den önskade texten, och sedan ritas denna bild på skärmen. Det fungerar på följande sätt: ```python import pygame @@ -71,17 +71,17 @@ while True: exit() ``` -Ohjelman suoritus näyttää seuraavalta: +Körning av koden ovan borde se ut enligt följande: -Tässä metodi `pygame.font.SysFont` luo fonttiolion, joka käyttää järjestelmän fonttia Arial kokona 24. Tämän jälkeen olion metodi `render` luo kuvan, jossa lukee teksti "Moikka!" punaisella värillä, ja tämä kuva piirretään ikkunaan. +Här skapar metoden `pygame.font.SysFont` ett typsnittsobjekt, som använder systemtypsnittet Arial i storlek 24. Metoden `render` skapar sedan en bild av den angivna texten i den angivna färgen. Denna bild ritas på fönstret med metoden `blit`, precis som tidigare. -Huomaa, että eri järjestelmissä on saatavilla eri fontit. Jos järjestelmässä ei ole fonttia Arial (mikä tosin on yleinen fontti), yllä oleva koodi käyttää sen sijasta järjestelmän oletusfonttia. Toinen mahdollisuus on käyttää metodia `pygame.font.Font`, jolle annetaan hakemistossa olevan fonttitiedoston nimi. +OBS: olika system kommer att ha olika teckensnitt tillgängliga. Om det system som det här programmet körs på inte har teckensnittet Arial, trots att Arial är ett mycket vanligt teckensnitt som finns på de flesta system, används istället systemets standardteckensnitt. Om du behöver ha ett specifikt teckensnitt tillgängligt för ditt spel kan du inkludera teckensnittsfilen i spelkatalogen och ange dess plats för metoden `pygame.font.Font`. -## Tehtävät +## Övningar -Tässä on pari vaikeampaa tehtävää, joiden avulla voit harjoitella lisää tämän luvun asioita. +Här är några mer avancerade övningar för att öva på det du har lärt dig i denna del av kursmaterialet. @@ -105,5 +105,3 @@ Tehtäväpohjassa on asteroidia varten kuvatiedosto `kivi.png`. Vastaa lopuksi osion loppukyselyyn: - - diff --git a/data/osa-14/1-peliprojekti.md b/data/osa-14/1-peliprojekti.md index b29452403..74bbc7295 100644 --- a/data/osa-14/1-peliprojekti.md +++ b/data/osa-14/1-peliprojekti.md @@ -1,18 +1,18 @@ --- path: '/osa-14/1-peliprojekti' -title: 'Peliprojekti' +title: 'Spelprojekt' hidden: false --- -Tässä osassa tehdään Pygamen avulla hieman laajempi peli, joka on muunnelma perinteisestä Sokoban-pelistä. Pelaaja ohjaa ruudukossa olevaa robottia, jonka tehtävänä on työntää laatikot oikeille paikoille käyttäen mahdollisimman vähän siirtoja. +I den här delen kommer vi att använda pygame för att skapa ett lite större spel. Det är en variant av det klassiska Sokoban spelet, där spelaren flyttar en robot på ett rutnät och skjuter lådor till rätt platser med så få drag som möjligt. -Lopullinen peli tulee näyttämään tältä: +Slutresultatet kommer att se ut så här: -## Pelin pohja +## Spelets karta -Aloitetaan tekemällä pelille pohja, joka piirtää näkyviin pelin aloitustilanteen. Toteutamme pelin luokkaan `Sokoban`, jonka sisällä on pelissä tarvittavat toiminnot. Ensimmäisessä vaiheessa luokan sisältö on seuraava: +Låt oss börja med att rita den karta som används i spelet. Spelet implementeras i klassen `Sokoban`, som kommer att innehålla alla funktioner som krävs för att spela spelet. I detta första steg är innehållet i klassen följande: ```python import pygame @@ -20,10 +20,10 @@ import pygame class Sokoban: def __init__(self): pygame.init() - + self.lataa_kuvat() self.uusi_peli() - + self.korkeus = len(self.kartta) self.leveys = len(self.kartta[0]) self.skaala = self.kuvat[0].get_width() @@ -73,19 +73,19 @@ if __name__ == "__main__": Sokoban() ``` -Tämä saa aikaan ikkunan, jossa on pelin aloitustilanne. Katsotaan seuraavaksi tarkemmin luokassa olevaa koodia. +Om du kör programmet visas ett fönster med spelets inledande tillstånd. Låt oss ta en närmare titt på koden som åstadkommer detta. -## Konstruktori +## Konstruktorn -Luokan konstruktori aloittaa Pygamen käyttämisen, alustaa pelissä tarvittavia muuttujia ja tietorakenteita sekä lopuksi kutsuu metodia, jossa on pelin pääsilmukka. +Klassens konstruktor initierar pygame-modulerna och de väsentliga variabler och datastrukturer som är involverade i spelet. Den anropar också spelets huvudloop-metod. ```python def __init__(self): pygame.init() - + self.lataa_kuvat() self.uusi_peli() - + self.korkeus = len(self.kartta) self.leveys = len(self.kartta[0]) self.skaala = self.kuvat[0].get_width() @@ -99,13 +99,13 @@ Luokan konstruktori aloittaa Pygamen käyttämisen, alustaa pelissä tarvittavia self.silmukka() ``` -Metodi `lataa_kuvat` lataa listaan `kuvat` pelin käyttämät kuvat ja metodi `uusi_peli` luo kaksiulotteisen listan `kartta`, jossa on kuvattu ruudukon sisältö alussa. +Metoden `ladda_bilder` laddar de bilder som används i spelet till en lista med namnet `bilder`. Metoden `nytt_spel` skapar en tvådimensionell lista med namnet karta, som innehåller spelrutnätets tillstånd i början av spelet. -Tämän jälkeen muuttujiin `korkeus` ja `leveys` laitetaan ruudukon korkeus ja leveys ja muuttujaan `skaala` laitetaan yhden ruudun koko. Koska jokainen kuva on neliön muotoinen ja yhtä suuri, ruudun koko saadaan hakemalla ensimmäisen kuvan leveys. Tämän avulla saadaan laskettua näytön korkeus ja leveys, minkä avulla voidaan luoda sopivan kokoinen ikkuna pelille. +Variablerna `hojd` och `bredd` sätts baserat på spelrutnätets dimensioner. Variabeln `skala` innehåller längden på sidan av en kvadrat i rutnätet. Eftersom varje bild är en kvadrat av exakt samma storlek täcks storleken på alla kvadrater av denna enda variabel, och bredden på den första bilden räcker gott och väl för värdet. Samma värde kan användas för att beräkna bredden och höjden på hela rutnätet, vilket gör att vi kan skapa ett fönster av lämplig storlek för att visa spelrutnätet. -## Kuvien lataaminen +## Att ladda bilder -Metodi `lataa_kuvat` lataa pelin tarvitsemat kuvat: +Metoden `ladda_bilder` laddar alla bilder som används i spelet: ```python def lataa_kuvat(self): @@ -114,63 +114,63 @@ Metodi `lataa_kuvat` lataa pelin tarvitsemat kuvat: self.kuvat.append(pygame.image.load(nimi + ".png")) ``` -Pelissä on käytössä seuraavat kuvat: +Spelet använder följande bilder: -### Lattiaruutu +### Golvruta -* Tiedoston nimi `lattia.png` -* Sijainti listalla 0 +* Filnamn: `lattia.png` +* Position i listan: 0 -### Seinäruutu +### Väggruta -* Tiedoston nimi `seina.png` -* Sijainti listalla 1 +* Filnamn: `seina.png` +* Position i listan: 1 -### Kohderuutu +### Målruta -* Tiedoston nimi `kohde.png` -* Sijainti listalla 2 -* Robotin tulee siirtää jokin laatikko tähän ruutuun +* Filnamn: `kohde.png` +* Position i listan: 2 +* Roboten ska flytta en låda till den här rutan -### Laatikko +### Låda -* Tiedoston nimi `laatikko.png` -* Sijainti listalla 3 +* Filnamn: `laatikko.png` +* Position i listan: 3 -### Robotti +### Robot -* Tiedoston nimi `robo.png` -* Sijainti listalla 4 +* Filnamn `robo.png` +* Position i listan: 4 -### Laatikko kohderuudussa +### Låda på målruta -* Tiedoston nimi `valmis.png` -* Sijainti listalla 5 -* Laatikko on saatu siirrettyä kohderuutuun +* Filnamn: `valmis.png` +* Position i listan: 5 +* Lådan har flyttats till målrutan -### Kohderuutu ja robotti +### Målruta och robot -* Tiedoston nimi `kohderobo.png` -* Sijainti listalla 6 -* Robotti voi myös olla kohderuudussa +* Filnamn: `kohderobo.png` +* Position i listan: 6 +* Roboten kan också vara på en tom målruta -## Ruudukon luonti +## Att skapa spelrutan -Metodi `uusi_peli` muodostaa ruudukon aloitustilanteen: +Metoden `nytt_spel` skapar den ursprungliga spelrutan: ```python def uusi_peli(self): @@ -182,13 +182,13 @@ Metodi `uusi_peli` muodostaa ruudukon aloitustilanteen: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] ``` -Metodi luo kaksiulotteisen listan `kartta`, jossa käytetään kuvien tunnuksia samassa järjestyksessä kuin kuvat on ladattu listaan. Tämän avulla pelin muistissa on tieto siitä, mikä on ruudukon tilanne tällä hetkellä. +Metoden skapar en tvådimensionell lista med namnet `karta` som använder de numrerade positionerna för bilderna i listan för att markera vilken bild som ska vara var. På så sätt innehåller spelet ett register över spelrutans tillstånd vid alla tidpunkter. -Huomaa, että jokainen ruudukon luku on alussa välillä 0–4. Missään ruudussa ei ole lukua 5 tai 6, koska mikään laatikko tai robotti ei ole alussa kohderuudussa. +OBS: I början innehåller alla rutor på spelplanen ett nummer mellan 0 och 4. Siffrorna 5 och 6 ingår inte, eftersom det i början inte finns någon låda eller robot på en målruta. -## Pelisilmukka +## Huvudloopen -Pelisilmukka kutsuu joka kierroksella kahta metodia: `tutki_tapahtumat` käy läpi viime kierroksen jälkeen syntyneet tapahtumat ja `piirra_naytto` päivittää näytön sisällön. +Metoden `huvud_loop` är ganska kort. Vid varje iteration anropar den två metoder: `kolla_handelser` går igenom alla händelser som samlats in sedan föregående iteration, och metoden `rita_fonster` uppdaterar innehållet i fönstret. ```python def silmukka(self): @@ -212,8 +212,8 @@ Pelisilmukka kutsuu joka kierroksella kahta metodia: `tutki_tapahtumat` käy lä pygame.display.flip() ``` -Tällä hetkellä ainoa pelin tunnistama tapahtuma on pelin sulkeminen (esimerkiksi pelaaja painaa ikkunassa olevaa raksia). Tässä tilanteessa peli sulkee itsensä kutsumalla `exit`-funktiota. +I det här skedet är den enda händelse som faktiskt hanteras av spelet att stänga spelfönstret, t.ex. med exit-knappen. Spelet avslutas sedan genom att anropa Pythons `exit`-funktion. -Näytön piirtäminen toteutetaan käymällä ruudukon sisältö ja piirtämällä jokaista ruutua vastaava kuva oikeaan paikkaan. +Varje gång metoden `rita_fonster` anropas korsas hela spelrutnätet igenom och den bild som motsvarar varje ruta i rutnätet ritas på rätt plats. -Huomaa, että koordinaatteja x ja y käytetään eri päin eri tilanteissa. Kaksiulotteisen listan indeksoinnissa on luontevaa antaa ensin y ja sitten x, koska ensimmäinen indeksi tarkoittaa riviä ja toinen indeksi tarkoittaa saraketta. Kuitenkin Pygamen metodeissa annetaan ensin x ja y, kuten grafiikassa on yleensä tapana. +OBS: koordinaterna x och y används på två olika sätt i spelet. När man hanterar index i en tvådimensionell lista är det logiskt att ange y-koordinaten först, eftersom y hänvisar till numret på raden medan x är numret på kolumnen. Å andra sidan, när man använder pygame-metoder, skickas x vanligtvis först, vilket det ganska ofta gör när man arbetar med grafik och även i matematiska sammanhang. diff --git a/data/osa-14/2-robo-ja-laatikot.md b/data/osa-14/2-robo-ja-laatikot.md index c9901c919..05df531bc 100644 --- a/data/osa-14/2-robo-ja-laatikot.md +++ b/data/osa-14/2-robo-ja-laatikot.md @@ -1,14 +1,14 @@ --- path: '/osa-14/2-robo-ja-laatikot' -title: 'Robotti ja laatikot' +title: 'Robot och lådor' hidden: false --- -Vaikein asia Sokoban-pelin toteutuksessa on saada robotti liikkumaan niin, että se pystyy työntämään laatikoita halutulla tavalla. Pelin pitää tunnistaa, milloin robotti pystyy siirtymään pelaajan haluamaan suuntiin, sekä käsitellä oikein tilanteet, joissa robotti työntää laatikkoa. Nyt on aika tarttua tähän haasteeseen. +Det svåraste att implementera i ett Sokoban-spel brukar vara att flytta roboten så att den kan skjuta lådor i önskad riktning. Spelet ska kunna avgöra när roboten kan röra sig i en angiven riktning och kunna hantera alla situationer där en låda också ska röra sig. Låt oss ta an den här utmaningen nu. -## Näppäimistön käsittely +## Att hantera viktiga händelser -Pelaaja ohjaa robottia nuolinäppäimillä, joten tapahtumien käsittelyä täytyy laajentaa niin, että se tarkkailee myös näppäimistön tapahtumia: +Spelaren styr roboten med de fyra piltangenterna, så vår händelsehanterare ska också kunna reagera på lämpliga tangenthändelser: ```python def tutki_tapahtumat(self): @@ -27,11 +27,11 @@ Pelaaja ohjaa robottia nuolinäppäimillä, joten tapahtumien käsittelyä täyt exit() ``` -Nyt kun pelaaja painaa nuolinäppäintä, kutsutaan metodia `liiku` sopivilla parametreilla. Ensimmäinen parametri ilmaisee liikkeen määrän pystysuunnassa ja toinen parametri puolestaan ilmaisee liikkeen määrän vaakasuunnassa. +När spelaren nu trycker på en piltangent anropas metoden `flytta` med ett lämpligt par av argument. Det första argumentet innehåller förflyttningen i vertikal riktning, medan det andra innehåller förflyttningen i horisontell riktning. -## Robotin etsiminen +## Sökning av roboten -Pelin täytyy tietää robotin sijainti, jotta sitä pystyy siirtämään oikealla tavalla. Seuraava metodi `etsi_robo` selvittää robotin sijainnin: +Spelet måste veta var roboten befinner sig för att kunna förflytta den på rätt sätt. Låt oss lägga till metoden `hitta_robot` som räknar ut var roboten befinner sig: ```python def etsi_robo(self): @@ -41,13 +41,15 @@ Pelin täytyy tietää robotin sijainti, jotta sitä pystyy siirtämään oikeal return (y, x) ``` -Metodi käy läpi kaikki ruudukon ruudut ja palauttaa ruudun koordinaatit, jos ruudussa on luku 4 (robotti yksinään) tai luku 6 (robotti kohderuudun päällä). +Metoden går igenom alla rutor i rutnätet och returnerar koordinaterna för den ruta som innehåller antingen siffran 4 (roboten på egen hand) eller siffran 6 (roboten på en målruta). -Ideana on, että aina kun käyttäjä painaa nuolinäppäintä, selvitetään ensin robotin sijainti käymällä läpi ruudukon ruudut. Tämä voi tuntua vähän hitaalta, koska vaihtoehtoisesti voisi myös pitää yllä tietoa robotin sijainnista omissa muuttujissa. Tämän toteutuksen etuna on kuitenkin, että robotin sijainti ei ole tallessa kahdessa paikassa (ruudukossa ja erillisissä muuttujissa) vaan vain yhdessä paikassa, eli muistissa oleva pelin tila on yksinkertaisempi. +Tanken är att varje gång spelaren trycker på en piltangent ska robotens position först fastställas genom att gå igenom rutorna i rutnätet. Detta kan tyckas lite långsamt och överflödigt, eftersom vi lika gärna kan hålla robotens plats i en separat variabel eller två. Fördelen med denna sökmetod är att vi inte lagrar robotens position på två olika ställen (i spelrutan och i separata variabler), utan vi behöver bara bry oss om ett ställe (spelrutan), vilket innebär att spelets tillstånd i datorminnet blir enklare att hantera. -## Muutokset ruudukossa +## Förändringar av rutnätet -Metodi `liiku` saa parametreina suunnan, johon pelaaja haluaa robotin liikkuvan, ja metodi joko päivittää ruudukkoa sopivasti tai toteaa, että liikkuminen ei ole mahdollista eikä muuta ruudukon sisältöä. +Vi har redan anropat metoden `flytta` ovan, men vi har inte definierat den än. Låt oss göra det nu. + +Metoden `flytta` tar som argument den riktning som spelaren vill förflytta sig till. Den uppdaterar sedan rutnätet i enlighet med detta, eller fastställer att förflyttningen inte är tillåten och lämnar rutnätet oförändrad. ```python def liiku(self, liike_y, liike_x): @@ -72,9 +74,9 @@ Metodi `liiku` saa parametreina suunnan, johon pelaaja haluaa robotin liikkuvan, self.kartta[robon_uusi_y][robon_uusi_x] += 4 ``` -Metodi on melko monimutkainen, joten katsotaan tarkemmin metodin osia: +Metoden har en hel del olika steg, så låt oss ta en titt på vart och ett i tur och ordning: -### Robotin vanha ja uusi sijainti +### Robotens gamla och nya plats ```python robon_vanha_y, robon_vanha_x = self.etsi_robo() @@ -82,20 +84,20 @@ Metodi on melko monimutkainen, joten katsotaan tarkemmin metodin osia: robon_uusi_x = robon_vanha_x + liike_x ``` -Metodi kutsuu ensin metodia `etsi_robo`, joka selvittää robotin vanhan sijainnin ennen siirtoa. Tämä sijainti tallennetaan muuttujiin `robon_vanha_y` ja `robon_vanha_x`. +Först anropas metoden `hitta_robot` för att ta reda på robotens aktuella position innan förflyttningen. Detta lagras i variablerna `robot_tidigare_y` och `robot_tidigare_x`. -Tämän jälkeen muuttujiin `robon_uusi_y` ja `robon_uusi_x` lasketaan robotin haluttu uusi sijainti. Tämä saadaan laskettua kätevästi, kun tiedossa on vanha sijainti sekä haluttu sijainnin muutos pysty- ja vaakasuunnassa. +Därefter lagras robotens nya position efter den tänkta förflyttningen i variablerna `robot_nya_y` och `robot_nya_x`. De nya koordinaterna kan enkelt beräknas genom att lägga till de värden som skickats som argument till robotens gamla position, eftersom både vertikala och horisontella värden ingår. -### Törmääkö robotti seinään? +### Gick roboten in i en vägg? ```python if self.kartta[robon_uusi_y][robon_uusi_x] == 1: return ``` -Seuraavaksi käsitellään tapaus, jossa pelaaja yrittää ohjata robottia seinään (luku 1 tarkoittaa seinää). Tämä ei ole sallittua, joten tässä tilanteessa ei tapahdu mitään ja metodin suoritus vain loppuu. +`if`-satsen ovan tar hand om situationen där roboten skulle träffa en vägg som ett resultat av förflyttningen. Kom ihåg att 1 var positionen för en väggruta i listan med bilder. Detta är inte tillåtet, så metoden returnerar helt enkelt utan vidare. -### Laatikon siirtyminen +### Flyttning av låda ```python if self.kartta[robon_uusi_y][robon_uusi_x] in [3, 5]: @@ -109,43 +111,43 @@ Seuraavaksi käsitellään tapaus, jossa pelaaja yrittää ohjata robottia sein self.kartta[laatikon_uusi_y][laatikon_uusi_x] += 3 ``` -Jos robotin uudessa sijainnissa on luku 3 (laatikko) tai 5 (laatikko kohderuudussa), robotti työntää laatikkoa liikkuessaan. Tätä varten lasketaan muuttujiin `laatikon_uusi_y` ja `laatikon_uusi_x` laatikon uusi sijainti työntämisen jälkeen. +Om robotens nytänkta position innehåller siffran 3 (en egen låda) eller siffran 5 (en låda i en målruta) försöker roboten flytta lådan till nästa ruta. För detta ändamål behöver vi två nya variabler: `lada_ny_y` och `lada_ny_x`, som innehåller lådans placering efter flytten. -Laatikko ei voi siirtyä, jos uudessa kohdassa on luku 1 (seinäruutu), luku 3 (toinen laatikko) tai luku 5 (toinen laatikko kohderuudussa). Näissä tapauksissa metodi sulkee itsensä eikä tee mitään. +På samma sätt som roboten kan lådan inte flyttas till en väggruta med identifieraren 1. Lådan kan inte heller flyttas till en annan låda eller till en målruta med en låda på. Om detta skulle hända till följd av förflyttningen, återgår metoden helt enkelt utan att göra några ändringar i rutnätet. -Muissa tapauksissa kuitenkin laatikkoa pystyy siirtämään, jolloin laatikon nykyisen ruudun luvusta vähennetään 3 ja uuden ruudun lukuun lisätään 3. Tämä päivittää ruudukkoa oikealla tavalla sekä silloin, kun laatikko on tavallisessa lattiaruudussa tai kohderuudussa. +I alla andra fall kan lådan röra sig. Värdet på lådans nuvarande rutnätsplats minskas med 3 och värdet på dess nya rutnätsplats ökas med 3. På grund av den smarta ordningen på objekten i listan `bilder` fungerar detta korrekt både när det gäller golvrutor och målrutor. -### Robotin siirtyminen +### Förflyttning av roboten ```python self.kartta[robon_vanha_y][robon_vanha_x] -= 4 self.kartta[robon_uusi_y][robon_uusi_x] += 4 ``` -Jos metodin suoritus etenee loppuun asti, myös robotin tulee vielä siirtyä. Tämä toteutetaan samalla tavalla kuin laatikon siirtyminen, paitsi että vähennettävä ja lisättävä arvo on 4. Tässäkin tapauksessa ruudukon sisältö muuttuu oikein tilanteissa, joissa robotti on tavallisessa lattiaruudussa tai kohderuudussa. +Om metoden har nått denna punkt utan att återvända, är det dags att flytta roboten också. Proceduren är densamma som när lådan flyttas, men det värde som dras från och läggs till på de aktuella platserna i rutnätet är 4 den här gången. Detta säkerställer, återigen genom den smarta ordningen av objekten i bildlistan, att slutresultatet i rutnätet blir korrekt både när golv- och målrutor är inblandade i förflyttningen. -## Refaktorointia? +## Omfaktorisering? -Tässä käytetty tapa tallentaa ruudukon tilanne on siinä mielessä kätevä, että yksi ruudukko kuvaa pelin koko tilanteen tiiviissä muodossa ja ruudukkoa on melko helppoa päivittää vähentämällä ja poistamalla sopivasti lukuja. +Att endast använda rutnätet för att lagra spelets tillstånd hela tiden är mycket praktiskt i den meningen att endast en variabel är permanent inblandad i hela processen, och det är relativt enkelt att uppdatera rutnätets tillstånd genom enkla additioner och subtraktioner. -Toteutuksen huonona puolena on kuitenkin, että pelin koodin ymmärtäminen voi olla vaikeaa. Esimerkiksi jos ulkopuolinen koodari näkee seuraavan rivin, se näyttää luultavasti mystiseltä. +Nackdelen är att det kan vara en aning svårt att förstå spelets programkod. Om någon som inte är bekant med den logik som används såg följande kodrad skulle de troligen bli lite förvirrade: ```python if self.kartta[laatikon_uusi_y][laatikon_uusi_x] in [1, 3, 5]: ``` -Tässä on käytetty _taikalukuja_ (_magic numbers_) ruutujen esittämiseen, ja koodin lukijan täytyy tietää, että 1 tarkoittaa seinää, 3 tarkoittaa laatikkoa ja 5 tarkoittaa kohderuudussa olevaa laatikkoa. +Kodsnutten ovan använder magiska siffror för att representera rutorna i rutnätet. Den som läser koden måste veta att 1 betyder vägg, 3 betyder en låda och 5 betyder en låda i en målruta. -Vielä mystisempiä ovat rivit tyyliin +Raderna med de smarta subtraktionerna och adderingarna skulle se ännu mer förvirrande ut: ```python self.kartta[robon_uusi_y][robon_uusi_x] -= 3 ``` -koska nyt laatikkoa tarkoittava luku 3 vähennetään ruudun luvusta. Tämä toimii, koska tämä muuttaa tavallisen laatikon lattiaksi ja kohderuudussa olevan laatikon kohderuuduksi, mutta asian ymmärtäminen vaatii huolellista perehtymistä ruutujen numerointiin. +Siffran 3 betydde en ruta precis som tidigare, men nu subtraheras den från värdet på en ruta i rutnätet. Detta fungerar inom ramen för vårt numreringssystem, eftersom det ändrar en låda (3) till en normal golvruta (0) eller en målruta med en låda (5) till en tom målruta (2), men för att förstå detta krävs kännedom i det numreringssystem som används. -Pelin koodin lukijan työtä voisi helpottaa _refaktoroimalla_ koodia eli muuttamalla koodin rakennetta paremmaksi ja selkeämmäksi. Tässä tapauksessa helppo muutos olisi käyttää lukujen 0–6 sijasta kuvaavampia ruutujen nimiä, mutta tämä ei selittäisi sitä, miksi lukuja voi vähentää ja lisätä ja ruudukko muuttuu oikealla tavalla. +Vi kan göra det enklare för alla som läser koden genom att omfaktorisera vår implementation. Det innebär att vi förbättrar kodens struktur och läsbarhet. Ett sätt att uppnå detta skulle vara att använda namnen på rutorna i stället för siffrorna 0 till 6, även om detta fortfarande inte skulle förklara hur och varför siffror kan adderas och subtraheras samtidigt som rutnätets integritet bibehålls. -Pelin koodin saaminen todella helposti luettavaksi vaatisikin luultavasti paljon suurempaa refaktorointia, kuten ruudukon pysyvän rakenteen tallentamista erillään ja robotin ja laatikoiden sijaintien tallentamista omissa tietorakenteissaan. Toisaalta tämän kääntöpuolena olisi, että koodia voisi tulla paljon lisää ja pelin sisäinen toiminta muuttuisi monimutkaisemmaksi. +För att göra programkoden verkligt tillgänglig skulle det sannolikt krävas mycket mer grundläggande transformativ omfaktorisering. Vi skulle till exempel kunna behålla spelkartans struktur på en plats och lagra robotens och lådornas platser i en separat datastruktur. Nackdelen med detta skulle vara att det sannolikt skulle resultera i mycket mer kod och att spelets interna struktur skulle bli mycket mer komplicerad. -Refaktorointiin ja koodin laatuun liittyviin asioihin tutustutaan lisää tulevilla kursseilla, kuten _Ohjelmistotekniikka_ ja _Ohjelmistotuotanto_. +Omfaktorisering och kodkvalitet är ett ämne för en del efterföljande kurser, t.ex. Software Development Methods och Software Engineering. diff --git a/data/osa-14/3-pelin-viimeistely.md b/data/osa-14/3-pelin-viimeistely.md index 296e7a542..31e1711cc 100644 --- a/data/osa-14/3-pelin-viimeistely.md +++ b/data/osa-14/3-pelin-viimeistely.md @@ -1,16 +1,16 @@ --- path: '/osa-14/3-pelin-viimeistely' -title: 'Pelin viimeistely' +title: 'Färdigställande av spelet' hidden: false --- -Peli on jo hyvässä vaiheessa, joten nyt voimme alkaa viimeistellä pelin toteutusta. Lisäämme peliin laskurin, joka näyttää siirtojen määrän, mahdollisuuden aloittaa uusi peli ja sulkea peli näppäinkomennoilla sekä ilmoituksen, kun pelaaja onnistuu läpäisemään pelin. +Vårt spel är redan ganska fungerande, så det är dags att lägga till några sista detaljer. Vi lägger till en räknare för att visa antalet drag, en möjlighet att starta ett nytt spel och stänga spelet med tangentbordsinmatning samt ett meddelande när spelaren lyckas vinna spelet. -## Siirtolaskuri +## Räknare för mängden drag -Siirtolaskuri näyttää pelin ikkunan alalaidassa, montako siirtoa pelaaja on tehnyt tähän mennessä. Tämän avulla voi yrittää etsiä ratkaisua, jossa on mahdollisimman vähän siirtoja. +Dragräknaren i nedre kanten av spelfönstret visar antalet drag som spelaren har gjort hittills. Detta kan användas för att hitta den lösning som kräver minst antal drag. -Laskurin tekeminen vaatii joitakin muutoksia koodiin. Muutetaan ensin konstruktoria niin, että ikkunassa on tilaa laskurille ja käytettävissä on fontti tekstin piirtämistä varten: +Räknaren kräver några ändringar i koden. Först ändrar vi konstruktorn så att det finns tillräckligt med utrymme för räknaren och att vi har ett lämpligt teckensnitt till vårt förfogande för att rita texten: ```python def __init__(self): @@ -21,7 +21,7 @@ Laskurin tekeminen vaatii joitakin muutoksia koodiin. Muutetaan ensin konstrukto ... ``` -Siirtolaskuri nollataan pelin alussa ja jokainen siirto kasvattaa sitä yhdellä: +Dragräknaren ställs in till noll i början av spelet. Varje drag ökar den med ett: ```python def uusi_peli(self): @@ -36,7 +36,7 @@ Siirtolaskuri nollataan pelin alussa ja jokainen siirto kasvattaa sitä yhdellä ``` -Lisäksi näytön päivityksen yhteydessä näytetään siirtojen määrä laskurin avulla: +Varje gång innehållet i fönstret uppdateras, bör även antalet drag som visas på skärmen uppdateras: ```python def piirra_naytto(self): @@ -46,9 +46,9 @@ Lisäksi näytön päivityksen yhteydessä näytetään siirtojen määrä lasku ... ``` -## Uusi peli ja pelin sulkeminen +## Nytt spel och att avsluta spelet -Lisätään peliin seuraavaksi näppäinkomennot, joiden avulla pelaaja voi aloittaa uuden pelin painamalla F2 sekä sulkea pelin painamalla Esc. Molemmat toiminnot on helppo toteuttaa: +Nu ska vi lägga till tangentbordsinstruktioner för att starta ett nytt spel med F2 och avsluta spelet med Esc. Båda är ganska enkla att implementera: ```python def tutki_tapahtumat(self): @@ -60,7 +60,7 @@ Lisätään peliin seuraavaksi näppäinkomennot, joiden avulla pelaaja voi aloi ... ``` -Lisäksi piirretään ikkunan alalaitaan pelaajalle tiedoksi, että pelissä on tällaiset toiminnot: +Vi bör också lägga till information om den här funktionen så att spelaren kan se den: ```python def piirra_naytto(self): @@ -73,9 +73,9 @@ Lisäksi piirretään ikkunan alalaitaan pelaajalle tiedoksi, että pelissä on ... ``` -## Pelin läpäiseminen +## Vinna spelet -Pelaaja läpäisee pelin, kun jokainen laatikko on jossain kohderuudussa. Tämä voidaan tarkastaa seuraavalla metodilla: +Spelaren har vunnit spelet när varje låda befinner sig i en av målrutorna. Följande metod tar hand om att kontrollera detta: ```python def peli_lapi(self): @@ -86,9 +86,9 @@ Pelaaja läpäisee pelin, kun jokainen laatikko on jossain kohderuudussa. Tämä return True ``` -Metodi käy läpi kaikki ruudut ja jos jossain ruudussa on luku 2 (tyhjä kohderuutu) tai 6 (robotti kohderuudussa), peli ei ole vielä läpäisty ja metodi palauttaa `False`. Jos mitään tällaista ruutua ei ole ruudukossa, peli kuitenkin on läpäisty ja metodi palauttaa `True`. +Metoden går igenom alla rutor i rutnätet. Om någon av rutorna är en 2:a (en tom målruta) eller en 6:a (en robot i en målruta) är spelet ännu inte löst och metoden returnerar `False`. Om det inte finns någon sådan ruta i rutnätet måste alla målrutor vara upptagna av lådor, spelet är löst och metoden returnerar `True`. -Jos pelaaja läpäisee pelin, metodi `piirra_naytto` näyttää asiaan kuuluvan viestin: +Om spelaren löser spelet bör vi visa ett lämpligt meddelande med metoden `rita_fonster`: ```python def piirra_naytto(self): @@ -102,7 +102,7 @@ Jos pelaaja läpäisee pelin, metodi `piirra_naytto` näyttää asiaan kuuluvan ... ``` -Lisäksi metodin `liiku` alkua muutetaan niin, että liikkuminen ei ole enää mahdollista, kun pelaaja on läpäissyt pelin: +För fullständighetens skull ändrar vi också `flytta`-metoden så att spelaren inte längre kan flytta när han eller hon har löst spelet: ```python def liiku(self, liike_y, liike_x): @@ -111,31 +111,31 @@ Lisäksi metodin `liiku` alkua muutetaan niin, että liikkuminen ei ole enää m ... ``` -Tässä tilanteessa kuitenkin pelaaja näkee edelleen ruudukon ja lopullisen pelitilanteen. +Spelaren kan dock fortfarande se rutnätet och det slutliga läget i spelet. -## Vinkki testauksen +## Ett tips för testning -Pelin kehityksen aikana tulee helposti tarve tarkastaa, mitä tapahtuu jossain vaiheessa myöhemmin pelissä. Esimerkiksi tässä pelissä tällainen tapahtuma on pelin läpäiseminen ja siitä tuleva ilmoitus. +När man utvecklar spel händer det ofta att man vill kontrollera vad som händer i någon senare situation i spelet. I det här spelet är till exempel det ögonblick då spelet vinns en sådan situation. -Tällaisen tapahtuman testaaminen voi olla hankalaa, jos esimerkiksi pitää aina läpäistä koko peli ennen kuin tapahtuman näkee. Kuitenkin pelin ohjelmoija voi helposti tehdä koodiin väliaikaisia muutoksia, jotka helpottavat testausta. Esimerkiksi pelin läpäisyä voi testata muuttamalla koodia näin: +Det kan vara svårt att testa att en sådan situation fungerar korrekt, eftersom man normalt sett måste lösa spelet för att komma till den punkten i spelet. Som programmerare kan vi göra några tillfälliga underlättningar i våra spel, för att göra det lättare att testa dem. Vi skulle till exempel kunna lägga till följande för att göra det tillfälligt lättare att lösa spelet: ```python def peli_lapi(self): return True ``` -Nyt metodi palauttaa aina `True` eli peli on läpäisty heti pelin alkaessa. Tämän avulla voi mukavasti varmistaa, että pelin lopussa tuleva ilmoitus näyttää oikealta eikä pelaaja pysty enää siirtymään ruudukossa. Sitten kun pelin päättyminen toimii oikein, metodin voi jälleen palauttaa ennalleen. +Nu returnerar metoden alltid `True`, vilket innebär att spelet är "löst" till att börja med. Detta gör det enkelt att kontrollera att notifieringen i slutet ser bra ut och att spelaren inte längre kan röra sig på rutnätet efter lösningen. När denna funktionalitet är noggrant testad kan vi återkalla ändringarna. -## Peli GitHubiin +## Ditt spel på GitHub? -Peli on nyt valmis ja voit hakea pelin lopullisen koodin ja kuvat GitHubista: +Spelet är nu färdigt. Om du vill ha ett enkelt sätt att leka med koden och bilderna kan du hämta källkoden från GitHub: * [https://github.com/moocfi/sokoban](https://github.com/moocfi/sokoban) -GitHub on hyvä paikka omille ohjelmointiprojekteille: sen avulla projektin koodin ja muut tiedostot saa talteen Git-versionhallintaan ja projektin pystyy helposti jakamaan muille. GitHubia käytetään paljon myöhemmillä tietojenkäsittelytieteen kursseilla. +GitHub är en populär plats för många typer av programmeringsprojekt. Det kan användas för att lagra källkoden och annat material för alla dina egna programmeringsprojekt också, och ditt program kommer då att underhållas genom git-versionskontroll, och det kan enkelt delas med andra. Du kommer att bli mycket bekant med git och GitHub om du fortsätter med andra programmeringskurser på mooc.fi. -## Montako siirtoa tarvitaan? +## Hur många drag krävs? -Vaikka pelin ruudukko on melko pieni, peli ei ole helppo. Ensimmäinen haaste on onnistua läpäisemään peli, ja sen jälkeen haasteena on keksiä ratkaisu, jossa siirtojen määrä on mahdollisimman pieni. Kuinka lyhyen ratkaisun onnistut muodostamaan? +Rutnätet i det här spelet är ganska litet, men spelet är inte så lätt. Den första utmaningen är att helt enkelt klara spelet, men nästa steg är att försöka göra det med så få drag som möjligt. Hur kort är den kortaste vägen till en lösning? -Lyhimmän mahdollisen ratkaisun etsiminen käsin on hyvin vaikeaa, mutta tässäkin voi käyttää apuna ohjelmointia. Kurssilla _Tietorakenteet ja algoritmit_ tutustutaan tekniikoihin, joiden avulla voidaan löytää automaattisesti lyhin mahdollinen ratkaisu peliin. +Att leta efter den kortaste möjliga lösningen är inte alls en lätt uppgift, men det finns beräkningslösningar för detta också. Detta är ett av ämnena i kursen Datastrukturer och algoritmer. diff --git a/data/osa-14/4-oma-peli.md b/data/osa-14/4-oma-peli.md index a67e88386..bc39a81bf 100644 --- a/data/osa-14/4-oma-peli.md +++ b/data/osa-14/4-oma-peli.md @@ -1,85 +1,84 @@ --- path: '/osa-14/4-oma-peli' -title: 'Oma peli' +title: 'Ditt eget spel' hidden: false --- -## Viikon tehtävä +## Den sista programmeringsövningen -Tämän viikon tehtäväsi on tehdä oma pieni peli Pygamen avulla. Peliä varten tehtäväpohjassa on joukko kuvia, joita voit käyttää pelissä. Älä käytä muita kuvia, jotta muutkin pystyvät kokeilemaan peliä koodin perusteella. +Den sista programmeringsövningen på den här kursen är att skapa ett eget litet spel med pygame. Övningsmallen innehåller några bilder som du kan använda. Använd inte andra bilder i ditt spel för då kommer andra inte att kunna testa ditt spel enbart baserat på källkoden. -Saat päättää pelin aiheen itse, ja pelin tulisi olla suunnilleen saman laajuinen kuin tämän viikon esimerkki. Pelin tulisi noudattaa seuraavia vaatimuksia: +Vad spelet handlar om är upp till dig, men det bör vara ungefär lika komplicerat som Sokoban-exemplet i denna del av materialet. Spelet bör innehålla följande funktioner: -* Pelissä on hahmo, jota pelaaja pystyy liikuttamaan -* Pelissä on kerättäviä asioita ja/tai vihollisia -* Pelaajalla on jokin selkeä tavoite -* Pelissä on laskuri, joka näyttää, miten pelaaminen sujuu -* Pelin koodi on jaettu sopivasti funktioihin tämän viikon esimerkin tyylisesti +* Spelet har en sprite som spelaren kan röra sig med på något sätt +* Spelet har några föremål som går att samla på och/eller fiender +* Spelaren måste få en tydlig uppgift i spelet +* Spelet innehåller en räknare som talar om för spelaren hur det går i spelet +* Källkoden för spelet är indelad i funktioner som i Sokoban-exemplet -Kun peli on valmis, lähetä se vertaisarviointiin alla olevan lomakkeen avulla. Tämän jälkeen tutustu vielä kahden muun kurssilaisen peliin ja anna niille mielestäsi sopiva arvosana ja kirjoita lyhyt arvio peleistä. Ota arvioinnissa huomioon sekä pelin kiinnostavuus ja pelattavuus että koodin selkeys. +När spelet är klart kan du skicka in det för granskning av andra spelare med hjälp av nedanstående formulär. Efter detta ska du bekanta dig med två av dina kurskamraters spel, ge spelen ett betyg som du anser lämpligt och skriva en kort recension av spelet. Ta hänsyn till både hur intressant och spelbart spelet var, och hur läsbar programkoden är när du betygsätter och recenserar spelen. -### Pelin lähettäminen +### Skicka in spelet -Pelin lähetys onnistuu lähettämällä oma ohjelmakoodi TMC Paste -ominaisuuden avulla palvelimelle ja liittämällä linkki palautuslomakkeeseen. +Du ska skicka in källkoden till ditt spel via TMC:s Paste-funktion och inkludera länken du får i formuläret nedan. -Klikkaa ensin TMC-painiketta (silmän oikealla puolella oleva painike). Tämä avaa valikon, josta löydät oikean ominaisuuden kirjoittamalla hakukenttään sanan `send`: +Med din lösning öppen i redigeraren klickar du på TMC-menyknappen i Visual Studio Code (bredvid ögonsymbolen). Detta öppnar en meny där du kan leta efter TMC Paste-funktionen genom att skriva in `send`: -Kun klikkaat valintaa `Send Exercise to TMC Paste`, ruudun oikeaan alakulmaan putkahtaa tieto siitä, että ohjelmakoodi on lähetetty palvelimelle: +Välj alternativet `Send Exercise to TMC Paste`, sedan borde det finnas en notifikation i det nedre högra hörnet av fönstret som berättar att källkoden har skickats till TMC-servern: -Saat linkin kopioitua leikepöydälle klikkaamalla painiketta `Open URL` ja valitsemalla aukeavasta ikkunasta valihtoehdon `Copy`. +Du kan kopiera länken genom att klicka på knappen `Open URL` i meddelandet. En popup borde visas och den ska innehålla alternativet `Copy`: -Tämä linkki sinun tulee liittää palautuslomakkeeseen. +Det här är länken som du ska klistra in i formuläret nedan. -## Osan 14 arvioinnista +## Hur del 14 betygsätts -Tämän viikon tavoitteena on saada aikaan toimiva peli. Koska osassa on vain yksi tehtävä, *myös yritys hyväksytään*. Voit siis palauttaa pelisi vaikka se ei toimisikaan haluamallasi tavalla. +Målet med den här delen är att bygga ett litet, fungerande spel. Eftersom den här delen bara innehåller en enda uppgift får du poäng för ansträngning. Skicka in ditt spel även om du inte kan få det att fungera riktigt som du ville. -Jos et saa peliä toimimaan, kirjoita ohjelmakoodin kommentteihin miten pelin (tai jonkin puuttuvan ominaisuuden) olisi tarkoitus toimia. +Om ditt spel inte fungerar som du tänkt dig, bifoga kommentarer i din kod om hur det borde fungera, eller eventuella funktioner som du tycker saknas men inte lyckats implementera. -Kun arvioit (osittain tai kokonaan) toimimatonta peliä, pyri arviossasi mahdollisuuksien mukaan kertomaan mitä pelin valmiiksi saaminen olisi mielestäsi vaatinut. +Om ett spel som du recenserar är ofullständigt eller inte fungerar riktigt som det ska får du gärna kommentera vad du tycker att man kunde ha gjort för att få det att fungera, ifall det är möjligt. -## Peli-ideoita +## Några spelidéer -Voit valita pelin aiheen vapaasti mutta tässä on joitakin ideoita. Pelin tekemisessä on hyötyä myös viikon 13 materiaalista. +Det är helt upp till dig vad ditt spel ska handla om, men här är några idéer som kan hjälpa dig att komma igång. Kom ihåg materialet i del 13; de exemplen kan också hjälpa dig att arbeta med dina idéer. -### Keräilypeli +### Ett samlingsspel -* Pelaaja liikuttaa robottia nuolinäppäimillä -* Ruudulla on kolikko, joka robotin tulee kerätä. Kun robotti saa kolikon, se siirtyy uuteen paikkaan. -* Ruudulla liikkuu myös hirviöitä, joita robotin tulee väistellä. +* Spelaren flyttar roboten med piltangenterna. +* Ett mynt dyker upp på en slumpmässig plats på skärmen. När roboten når fram till myntet flyttas det till en ny plats. +* Det finns också monster på skärmen och roboten måste undvika dem. -### Rahasade +### Myntregn -* Ruudun alareunassa on robotti, jota pelaaja voi liikuttaa vasemmalle tai oikealle -* Taivaalta sataa rahaa, jota robotin tulee kerätä. -* Taivaalta sataa myös hirviöitä, joita robotin tulee väistellä. +* Spelaren flyttar roboten till vänster och höger längs skärmens nedre kant. +* Mynt regnar från himlen. Dessa måste roboten samla in. +* Det regnar också monster från himlen. Roboten måste undvika dessa. -## Vertaisarviointi +## Kollegial granskning -Arvioi peliä esimerkiksi seuraavien kriteerien mukaan: +Du ska bedöma spelet utifrån följande kriterier: -* Miltä peli näyttää? -* Onko peli *pelattava*, ts. onko sen pelaaminen hauskaa ja sujuvaa? -* Onko peli-idea kiinnostava? -* Miten hyvin peliohjelma on kirjoitettu? Onko pelissä hyödynnetty tarvittavissa kohdin järkevästi funktioita ja luokkia? +* Hur ser spelet ut? +* Är spelet spelbart? Är det roligt att spela och lätt att använda? +* Är spelet spelbart? Är det roligt att spela och lätt att använda? +* Hur väl är det programmerat? Är koden läsbar och används klasser och funktioner på rätt sätt? -Hyvään arvioon kuuluu yleensä se, että löydät ohjelmasta sekä hyviä puolia että jotain kehitettävää. +En bra recension brukar peka ut både bra funktioner och några förslag till förbättringar. -## Loppukysely +## Frågeformulär för att avsluta -Vastaa ensin osion loppukyselyyn... +Först ber vi dig svara på en snabb enkät om den här delen av kursen. -...ja sitten vielä koko kurssin loppukyselyyn: +Vänligen svara också på kursens feedbackformulär. Enkätens svar hjälper oss att förbättra kursen. - diff --git a/data/osa-8/1-oliot-ja-metodit.md b/data/osa-8/1-oliot-ja-metodit.md index 1efba1efa..9a11b9da2 100644 --- a/data/osa-8/1-oliot-ja-metodit.md +++ b/data/osa-8/1-oliot-ja-metodit.md @@ -1,26 +1,24 @@ --- path: '/osa-8/1-oliot-ja-metodit' -title: 'Oliot ja metodit' +title: 'Objekt och metoder' hidden: false --- -Tämän osion jälkeen +Efter den här delen -- Tiedät, mitä tarkoitetaan oliolla -- Ymmärrät, mitä tarkoitetaan olioiden itsenäisyydellä -- Osaat muodostaa ja käsitellä olioita +- Kommer du veta vad ett objekt är i programmering +- Kommer du förstå vad som menas med oberoende hos individuella objekt +- Kommer du kunna skapa och komma åt objekt -Tämä on Ohjelmoinnin jatkokurssin ensimmäinen osa, ja kurssilla käytetään VS Codea samaan tapaan kuin Ohjelmoinnin perusteissa. Jos et ole käyttänyt ennen VS Codea, löydät [tästä](https://www.mooc.fi/fi/installation/vscode) ohjeet ympäristön asentamiseen. +Detta är första delen av den Avancerade Kursen inom Programmering. Materialet är designat för att bli använt med Visual Studio Code editeraren, liksom den föregående kursen Introduktion till Programming. Ifall du inte använt Visual Studio Code tidigare, så hittar du installeringsinstruktionerna [här](https://www.mooc.fi/fi/installation/vscode) och en introduktion till programmeringsomgivningen från förra kursen [här](https://programming-24.mooc.fi/part-4/1-vscode). -Jatkokurssi ja peruskurssi ovat TMC:ssä yhtenä kurssina. Jos lopetit juuri peruskurssin tekemisen, jatkokurssin tehtävät löytyvät samasta kurssista. Tämän jatkokurssin ensimmäisen osan numero on **Osa 8**. Mikäli et ole tehnyt tähän jatkokurssin liittyvää peruskurssia, valitse TMC-pluginissa organisaatioksi **MOOC** ja kurssiksi **Ohjelmoinnin MOOC 2024** ja aloita kurssin tekeminen osasta 8. +I introduktion till Programmering kursen så lade vi märke till att det ofta är logiskt att gruppera relaterad data tillsammans i våra program. Ifall vi till exemepl skulle förvara information om en bok skulle det vara logiskt att använda oss av en tuple eller en ordlista för att organisera datan till en enskild datastruktur. -Kuten kurssin ensimmäisen puolikkaan aikana huomattiin, on usein hyödyllistä yhdistää samaan asiaan liittyvät tiedot yhdeksi kokonaisuudeksi. Esimerkiksi kirjaa on kätevä mallintaa vaikkapa tuplen tai sanakirjan avulla, kun kaikki kirjaan liittyvät tiedot voidaan tallentaa samaan rakenteeseen. - -Tuplea käyttämällä esimerkki voisi näyttää tältä: +Lösningen kunde se ut så här när man använder en tuple: ```python nimi = "Nuoruuteni näppäilyt" @@ -34,7 +32,7 @@ kirja = (nimi, kirjailija, vuosi) print(kirja[0]) ``` -Sanakirjassa on tässä yhteydessä se etu, että avaimina voidaan käyttää merkkijonoja kokonaislukujen sijasta. Näin ollen alkioille voidaan antaa niiden sisältöä kuvaavat nimet: +I ett fall som detta är fördelen med att använda en ordlista att vi kan använda strängar istället för indexar som nycklar. Alltså, kan vi ge deskriptiva namn till sakerna som förvaras datastrukturen: ```python nimi = "Nuoruuteni näppäilyt" @@ -48,9 +46,9 @@ kirja = {"nimi": nimi, "kirjailija": kirjailija, "vuosi": vuosi} print(kirja["nimi"]) ``` -Molemmissa tapauksissa tietojen tallentaminen tietorakenteeseen muodostaa _olion_. Olio on itsenäinen kokonaisuus, joka sisältää (tässä tapauksessa) toisiinsa liittyvää tietoa. Itsenäisyys tarkoittaa sitä, että olioon tehdyt muutokset eivät vaikuta muihin olioihin. +I båda fallen så skapar vi ett nytt _objekt_. Inom programmering har termen specifikt betydelsen av en oberoende helhet, i detta fall innehållande några bitar av data som på något vis är relaterade. Att vara oberoende betyder att ändringar till ett objekt inte påverkar andra objekt. -Jos esimerkiksi muodostetaan sanakirjaa käyttäen kaksi kirjaoliota, ensimmäiseen kirjaan tehdyt muutokset eivät vaikuta toiseen kirjaan: +Ifall vi skulle skapa två strukturellt identiska representationer av böcker, användandes ordlistor och identiska nycklar, skulle ändringar som görs till en av dem inte påverka den andra: ```python kirja1 = {"nimi": "Vanhus ja Python", "kirjailija": "Ernest Pythonen", "vuosi": 1952} @@ -76,17 +74,19 @@ Seitsemän Pythonia - + + + +Du kanske kommer ihåg från introduktion till programmering kursen att vilket som helst värde i Python internt är behandlat som ett objekt. Detta betyder att värdet lagrat i en variabel är en _referens till ett objekt_. Själva datan är lagrad inuti ett objekt i datorns minne. Ifall du ger ett värde till en ny variable med kommandot `a = 3`, är värdet som lagras i variabeln _inte_ 3, utan en _referens till ett objekt som innehåller värdet 3_. -Kuten kurssin ensimmäisen puolikkaan aikana kerrottiin, Pythonissa kaikki arvot ovat itse asiassa olioita. Tämä tarkoittaa, että muuttujan arvo on _viittaus olioon_, ja varsinainen tieto on tallennettu olioon. Kun esimerkiksi alustetaan muuttuja `a = 3`, ei muuttujan `a` arvo ole 3 vaan _viittaus olioon, jonka sisältö on arvo 3_. +De flesta andra programmeringsspråk (i varje fall de som stöder objekt-orienterad programmering) inkluderar vissa särskilt definierade _primitiva datatyper_. Dessa inkluderar ofta åtminstone [integer] nummer, flyttals nummer och boleska sanningsvärden. Primitiver är processerade direkt, vilket betyder att de är lagrade direkt i variabler, inte som referenser. Python har inga sådana primitiver, men att jobba med de grundläggade datatyperna i Python är i praktiken väldigt liknande. Objekt av dessa grundläggande datatyper (såsom nummer, boleska värden och strängar) är _oföränderliga_, vilket betyder att de inte kan ändras i minnet. Ifall värdet som lagras i en variabel med en grundläggande datatyp måste ändras så byts hela referensen ut, men själva objektet kvarstår i minnet. -Useimmissa muissa ohjelmointikielissä on olioiden lisäksi ns. perustyyppisiä arvoja (esimerkiksi kokonais- ja liukuluvut sekä totuusarvot), jotka tallennetaan sellaisenaan muuttujiin. Pythonissakin perustyyppiset oliot (kuten vaikkapa luvut, totuusarvot tai merkkijonot) ovat kuitenkin muuttumattomia eli _mutatoitumattomia_. Ohjelmoijan kannalta niiden käyttö ei siis käytännössä eroa perustyyppisistä arvoista. -## Oliot ja metodit +## Objekt och metoder -Olioiden tietosisältöä voidaaan havainnoida ja muuttaa _metodien_ avulla. Metodi on funktio, jonka toiminta kohdistuu annettuun olioon. Metodin erottaa muista funktioista tapa, jolla sitä kutsutaan: ensin kirjoitetaan kohdeolio ja sen perään kutsuttava metodi pisteellä erotettuna. Esimerkiksi sanakirja-olion kaikki arvot voidaan palauttaa metodin `values` avulla: +Datan som lagras i ett objekt kan kommas åt genom olika _metoder_. En metod är en funktion som opererar på ett specifikt objekt som den är kopplad till. Sättet att åtskilja mellan metoder och andra funktioner ligger i hur de är kallade: först skriver man namnet på objektet som avses, sedan en punkt och till sist metodens namn, åtföljt av argument ifall sådana finns. Till exempel returnerar metoden `values` alla värden som är lagrade i ett objekt av typen ordlista eller `dict`: ```python # muodostetaan sanakirjatyyppinen kirjaolio @@ -107,7 +107,7 @@ Ernest Pythonen -Samalla tavalla merkkijonometodit kohdistuvat siihen merkkijonoon, jonka kautta niitä kutsutaan. Esimerkiksi merkkijonon metodeja ovat `count` ja `find`: +På samma sätt opererar en strängmetod på det strängobjekt som den kallas på. Några exempel av strängmetoder är `count` och `find`: ```python nimi = "Keijo Keksitty" @@ -134,7 +134,7 @@ print("Ihan eri jono".find("Keksitty")) -Merkkijonometodit palauttavat arvoja, mutta niiden avulla ei voida muuttaa merkkijonoa. Kuitenkin esimerkiksi lista-olion metodien avulla voidaan muuttaa listan sisältöä: +Strängmetoder returnerar värden, men ändrar inte innehållet av en sträng. Liksom nämnt ovan är strängar i Python oföränderliga. Detta gäller däremot inte alla metoder; Pythonlistor är föränderliga, alltså kan listmetoder ändra innehållet av listan som de kallas på: ```python lista = [1,2,3] diff --git a/data/osa-8/2-luokat-ja-oliot.md b/data/osa-8/2-luokat-ja-oliot.md index 07d9b2f2e..d0d383128 100644 --- a/data/osa-8/2-luokat-ja-oliot.md +++ b/data/osa-8/2-luokat-ja-oliot.md @@ -1,20 +1,20 @@ --- path: '/osa-8/2-luokat-ja-oliot' -title: 'Luokat ja oliot' +title: 'Klasser och objekt' hidden: false --- - + -Tämän osion jälkeen +Efter detta avsnitt -- Tiedät, mitä tarkoitetaan luokalla -- Ymmärrät luokan ja olion yhteyden -- Tiedät, mitä olio-ohjelmointi tarkoittaa +- Vet du vad en klass är +- Kommer du att förstå vad kopplingen mellan en klass och ett objekt är +- Kommer du att veta vad som menas med objektorienterad programmering -Edellisessä osassa käsitellyt esimerkkioliot – listat, tuplet, sanakirjat ja merkkijonot – ovat siinä mielessä erikoistapauksia, että niiden kaikkien muodostamiseen on Pythonissa sisäänrakennettuna oma syntaksinsa: +I föregående avsnitt arbetade vi med listor, tupler, ordlistor och strängar. Dessa är alla ganska speciella fall i Python-programmering. Pythons syntax har en unik, fördefinierad metod för att deklarera ett objekt som tillhör var och en av dessa typer: ```python # Lista luodaan antamalla arvot hakasuluissa @@ -30,7 +30,7 @@ sanakirja = {"yksi": 1, "kaksi:": 2} oma_tuple = (1,2,3) ``` -Muita olioita muodostettaessa kutsutaan erityistä metodia, joka luo olion. Tällaista metodia kutsutaan _konstruktoriksi_. Tarkastellaan esimerkkinä murtolukuolioiden muodostamista Fraction-luokasta: +När någon annan typ av objekt deklareras måste vi anropa en speciell initialiseringsfunktion som kallas konstruktor. Låt oss ta en titt på hur man arbetar med bråk genom Fraction-klassen. ```python # Tuodaan käyttöön luokka Fraction modulista fractions @@ -61,20 +61,20 @@ print(puolikas + kolmasosa) -Esimerkistä huomataan, että konstuktorikutsut poikkeavat aiemmista metodikutsuista. Konstruktorikutsuja ei ole sidottu tiettyyn olioon (mikä on sikäli loogista, että olio muodostetaan kutsumalla konstruktoria). Lisäksi metodin nimi on kirjoitettu isolla alkukirjaimella: `puolikas = Fraction(1,2)`. Pureudutaan tarkemmin olion muodostamisen mekanismiin esittelemällä _luokan_ käsite. +Som du kan se ovan, ser metodkallningar för konstruktorer lite annorlunda ut än de normala metodkallningar som vi har stött på tidigare. För det första är de inte kopplade till något objekt med punktnotationen (eftersom konstruktoranropet behövs för att skapa ett objekt i första hand). Konstruktorsmetoden skrivs också med stor bokstav: `[half = Fraction(1,2)]`. Låt oss titta närmare på hur objekt konstrueras genom att bekanta oss med begreppet klass. -## Luokka on olion käsikirjoitus +## En klass är ritningen till ett objekt -Materiaalissa on jo aiemmin vilahtanut käsite _luokka_. Edellisessä esimerkissä otettiin käyttöön luokka `Fraction` moduulista `fractions`. Uudet oliot muodostettiin kutsumalla luokan `Fraction` _konstruktoria_. +Vi har redan använt termen klass i materialet många gånger. I exemplet ovan importerade vi t.ex. klassen `Fraction` från modulen `fractions`. Nya bråktalsobjekt skapades genom att kalla på konstruktorsmetoden för klassen `Fraction`. -Luokassa määritellään siitä muodostettavien olioiden rakenne ja toiminnallisuus. Luokkaa nimitetään tästä syystä joskus olion käsikirjoitukseksi. Luokassa siis kerrotaan, millaista tietoa olio sisältää, ja määritellään metodit, joiden avulla oliota voidaan käsitellä. _Olio-ohjelmoinnilla_ tarkoitetaan ohjelmointitapaa, jossa ohjelman toiminnallisuus tapahtuu luokkien ja niistä muodostettujen olioiden avulla. +En klassdefinition innehåller strukturen och funktionaliteterna för alla objekt som representerar den. Därför kallas klasser ibland till objektens ritningar. En klassdefinition berättar alltså till oss vilken typ av data ett objekt innehåller och definierar de metoder som kan användas på objektet. Objektorienterad programmering är ett programmeringsparadigm där programmets funktionalitet är knuten till användningen av klasser och objekt som skapas baserat på dessa. -Yhdestä luokasta voidaan muodostaa useita olioita. Niin kuin aiemmin kerrottiin, oliot ovat itsenäisiä - muutokset olioon eivät vaikuta muihin luokasta muodostettuihin olioihin. Jokaisella oliolla on oma tietosisältönsä. Vähän yksinkertaistaen voisi sanoa, että +En klassdefinition kan användas för att skapa flera objekt. Som tidigare nämnts är objekt oberoende av varandra. Ändringar som görs till ett objekt påverkar i allmänhet inte de andra objekten som är samma klass. Varje objekt har sin egen unika uppsättning dataattribut. Det kan vara bra att tänka på denna förenkling av förhållandet mellan klass och objekt: -* luokassa määritellään muuttujat ja -* oliota muodostaessa niille annetaan arvot. +* En klass definierar variablerna +* när ett objekt skapas tilldelas dessa variabler värden -Luodaan esimerkkinä `Fraction`-luokasta olio ja tulostetaan sen osoittaja ja nimittäjä: +Vi kan alltså använda ett objekt av typen `Fraction` för att komma åt täljaren och nämnaren i ett bråktal: ```python from fractions import Fraction @@ -95,9 +95,9 @@ print(luku.denominator) -Luokassa `Fraction` on siis määritelty, että olioilla on muuttujat `numerator` ja `denominator`. Jokaisella oliolla on kuitenkin oma arvonsa näille muuttujille. +Klassdefinitionen för `Fraction` innehåller deklarationer för variablerna `numerator` och `denominator`. Varje objekt som skapas baserat på klassen har sina egna specifika värden som tilldelas dessa variabler. -Samalla tavalla `date`-luokasta muodostetuilla olioilla on kaikilla omat itsenäiset arvonsa vuodelle, kuukaudelle ja päivämäärälle: +På samma sätt innehåller objekt som skapats baserat på klassen `date` sina egna unika värden för datumets år, månad och dag: ```python from datetime import date @@ -117,11 +117,11 @@ print(juhannus.month) -Luokassa `date` on siis määritelty, että luokasta muodostettavilla olioilla on muuttujat `year`, `month` ja `day`. Kun luokasta muodostetaan olio, annetaan muuttujille arvot. Joka oliolla on omat arvonsa muuttujille. +Definitionen av klassen `date` innehåller deklarationer av variablerna `year`, `month` och `day`. När ett nytt datumobjekt skapas baserat på klassen tilldelas dessa variabler värden. Varje objekt har sina egna unika värden som tilldelas dessa variabler. -## Olioita käsittelevät funktiot +## Funktioner som arbetar med objekt -Funktioiden parametrina oleviin olioihin ei liity oikeastaan mitään sen kummempaa. Niitä on jo kurssin aiemmissa osissa nähty runsaasti. Seuraavassa on esimerkki funktiosta, joka tarkastaa, onko sen parametrina oleva `date`-olio viikonloppu: +Att passera ett objekt som ett argument till en funktion borde vara bekant för dig vid det här laget eftersom vi har gjort det redan många gånger i den här kursen. Låt oss ta en titt på följande exempel. Här har vi en funktion som kontrollerar om `date`-objektet som passeras som argument infaller på en helg: ```python def onko_viikonloppu(paiva: date): @@ -129,9 +129,7 @@ def onko_viikonloppu(paiva: date): return viikonpaiva == 6 or viikonpaiva == 7 ``` -Funktio siis käyttää parametrina olevan olion metodia [isoweekday](https://docs.python.org/3/library/datetime.html#datetime.date.isoweekday), joka palauttaa viikonpäivää vastaavan numeron niin, että maanantai on 1, tiistai on 2, jne. - -Funktiota käytetään seuraavasti: +Denna funktion använder metoden [isoweekday](https://docs.python.org/3/library/datetime.html#datetime.date.isoweekday), som definieras i klassdefinitionen för klassen date, och returnerar ett heltalsvärde på det sättet att om det angivna datumet är en måndag returnerar den 1, och om det är en tisdag returnerar den 2, och så vidare. ```python joulu = date(2020, 12, 24) @@ -148,9 +146,9 @@ True -## Metodi vs. olion muuttuja +## Metoder vs variabler -Jos tarkastellaan `date`-oliota, niin huomataan, että sen käsittely poikkeaa hieman riippuen siitä, mitä asiaa olion sisällöstä tarkastellaan: +När du arbetar med ett objekt av typen `date` kanske du märker att det finns en liten skillnad mellan hur variablerna i objektet åtkoms jämfört med hur metoderna som är kopplade till objekten åtkoms: ```python paiva = date(2020, 12, 24) @@ -172,13 +170,13 @@ Kuukausi: 12 -Päiväolion viikonpäivä saadaan siis selville kutsumalla _metodia_ isoweekday: +Veckodagen som datumet infaller på är tillgänglig via metoden isoweekday: ```python viikonpaiva = paiva.isoweekday() ``` -Koska on kyse metodikutsusta, niin metodin nimen perään laitetaan sulut. Jos sulut unohtuvat, on lopputulos outo: +Detta är en metodkallelse, alltså finns det parenteser efter namnet på metoden. Om du lämnar bort parenteserna uppstår det inte något fel, men resultatet blir konstigt: ```python viikonpaiva = paiva.isoweekday @@ -191,13 +189,13 @@ Viikonpäivä: -Päiväolioon liittyvä kuukausi taas on olion muuttuja, ja sen arvo selviää _viittaamalla_ muuttujaan +Månaden av ett date-objekt är en variabel, alltså kan det tillgivna värdet kommas åt med en referens. ```python kuukausi = paiva.month ``` -Nyt siis käytössä _ei ole sulkuja_. Jos tässä tilanteessa yritettäisiin käyttää sulkuja, ohjelma aiheuttaisi virheen: +Lägg märke till att det inte finns parenteser här. Att sätta in parenteser skulle orsaka ett fel: ```python kuukausi = paiva.month() diff --git a/data/osa-8/3-omat-luokat.md b/data/osa-8/3-omat-luokat.md index a42f84605..94ca31ef2 100644 --- a/data/osa-8/3-omat-luokat.md +++ b/data/osa-8/3-omat-luokat.md @@ -1,49 +1,49 @@ --- path: '/osa-8/3-omat-luokat' -title: 'Omat luokat' +title: 'Egna klasser' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, miten omia luokkia määritellään -- Osaat muodostaa itse määritellystä luokasta olion -- Osaat kirjoittaa konstruktorin -- Tiedät, mitä tarkoittaa avainsana `self` -- Tiedät, mitä ovat attribuutit ja miten niitä käytetään +- Vet du hur du definierar dina egna klasser +- Kommer du att kunna skapa objekt baserade på klasser som du själv har definierat +- Vet du hur man skriver en konstruktor +- Är du bekant med parameternamnet `self` +- Vet du vad attribut är och hur de används -Luokka määritellään avainsanan `class` avulla. Syntaksi on +En klass definieras med nyckelordet `class`. Syntaxen ser ut enligt följande: ```python class LuokanNimi: # Luokan toteutus ``` -Luokat nimetään yleensä _camel case_ -käytännöllä niin, että sanat kirjoitetaan yhteen ja jokainen sana alkaa isolla alkukirjaimella. Esimerkiksi seuraavat ovat tämän käytännön mukaisia luokkien nimiä: +Klasser namnges vanligtvis med kamelnotation. Detta innebär att alla ord i klassnamnet skrivs tillsammans, utan mellanslag, och att varje ord börjar med stor bokstav. Följande exempel på klassnamn följer denna konvention: -* `Pankkitili` -* `OhjelmaApuri` -* `KirjastoTietokanta` -* `PythonKurssinArvosanat` +* `Veckodag` +* `Bankkonto` +* `BibliotekDatabas` +* `PythonKursBetyg` -Yhdellä luokalla pyritään mallintamaan jokin sellainen yksittäinen kokonaisuus, jonka sisältämät tiedot liittyvät kiinteästi yhteen. Monimutkaisemmissa ratkaisuissa luokka voi sisältää toisia luokkia (esimerkiksi luokka `Kurssi` voisi sisältää luokan `Osasuoritus` mukaisia olioita). +En enskild klassdefinition bör representera en enskild helhet, vars innehåll bör vara sammanlänkat på något sätt. I mer komplicerade program kan klasser innehålla medlemmar av andra klasser. Till exempel kan klassen `Kurs` innehålla objekt av klasserna `Lektion`, `ÖvningsTillfälle` osv. -Tarkastellaan esimerkkinä yksinkertaista luokkamäärittelyä, josta sisältö vielä puuttuu: +Låt oss ta en titt på ett skelett av en klassdefinition. Funktionerna saknas fortfarande vid denna tidpunkt. ```python class Pankkitili: pass ``` -Koodissa määritellään luokka, jonka nimi on `Pankkitili`. Luokalle ei ole määritelty varsinaista sisältöä, mutta tästä huolimatta luokasta voidaan muodostaa olio. +Kodstycket ovan talar om för Python att vi här definierar en klass med namnet `Bankkonto`. Klassen innehåller ingen funktionalitet ännu, men vi kan fortfarande skapa ett objekt baserat på klassen. -Tarkastellaan ohjelmaa, jossa luokasta muodostetun olion sisälle on määritelty kaksi muuttujaa, `saldo` ja `omistaja`. Olion muuttujia kutsutaan _attribuuteiksi_. Attribuutista käytetään myös nimitystä _oliomuuttuja_. +Låt oss titta på ett program där två variabler läggs till ett `Bankkonto`-objekt: `saldo` och `ägare`. Alla variabler som är kopplade till ett objekt kallas dess attribut, eller mer specifikt, dataattribut, eller ibland instansvariabler. -Kun luokasta luodaan olio, voidaan attribuuttien arvoja käsitellä olion kautta: +De attribut som är kopplade till ett objekt kan nås via objektet: ```python class Pankkitili: @@ -64,7 +64,7 @@ Pekka Python -Attribuutit ovat käytettävissä ainoastaan sen olion kautta, jossa ne on määritelty. Pankkitili-luokasta muodostetuilla olioilla on jokaisella omat arvonsa attribuuteille. Attribuuttien arvot haetaan olioiden kautta, esimerkiksi näin: +Dataattributen är endast tillgängliga via det objekt som de är kopplade till. Varje Bankkonto-objekt som skapas baserat på Bankkonto-klassen har sina egna värden kopplade till dataattributen. Dessa värden kan nås genom att hänvisa till objektet i fråga: ```python tili = Pankkitili() @@ -74,11 +74,12 @@ print(tili.saldo) # Viittaa tilin attribuuttiin saldo print(saldo) # TÄSTÄ TULEE VIRHE, koska oliomuuttuja ei ole mukana! ``` -## Konstruktorin lisääminen +## Att lägga till en konstruktor -Kuten edellisestä esimerkistä huomataan, luokasta voi luoda uuden olion kutsumalla konstruktoria, joka on muotoa `LuokanNimi()`. Yleensä olisi kuitenkin kätevä antaa attribuuteille arvot heti kun olio luodaan – nyt esimerkiksi Pankkitilin omistaja ja saldo asetetaan vasta, kun pankkitiliolio on luotu. +I exemplet ovan såg vi att en ny instans av en klass kan skapas genom att anropa klassens konstruktormetod på följande sätt: `KlassensNamn()`. Ovan kopplade vi sedan dataattribut till objektet separat, men det är ofta bekvämare att skicka dessa initiala värden för attribut direkt när objektet skapas. I exemplet ovan hade vi först ett Bankkonto-objekt utan dessa attribut, och attributen existerade först efter att de uttryckligen hade deklarerats. -Attribuuttien asettamisessa ilman konstruktoria on myös se ongelma, että samasta luokasta luoduilla olioilla voi olla eri attribuutit. Seuraava ohjelmakoodi esimerkiksi antaa virheen, koska oliolle `pirjon_tili` ei ole määritelty attribuuttia `saldo`: + +Att deklarera attribut utanför konstruktorn leder till en situation där olika instanser av samma klass kan ha olika attribut. Följande kod ger ett fel eftersom vi nu har ett annat `Bankkonto-objekt`, `paulas_konto`, som inte innehåller samma attribut: ```python class Pankkitili: @@ -95,11 +96,11 @@ print(pekan_tili.saldo) print(pirjon_tili.saldo) # TÄSTÄ TULEE VIRHE ``` -Sen sijaan että attribuuttien arvot alustettaisiin luokan luomisen jälkeen, on huomattavasti parempi ajatus alustaa arvot samalla, kun luokasta luodaan olio. +Så istället för att deklarera attribut efter att varje instans av klassen har skapats, är det oftast en bättre idé att initialisera attributens värden när klasskonstruktorn anropas. Eftersom klassdefinitionen Bankkonto för närvarande bara är ett skelett, antas konstruktormetoden implicit av Python-tolkaren, men det är möjligt att definiera egna konstruktormetoder, och det är precis vad vi kommer att göra nu. -Konstruktori kirjoitetaan luokan sisään metodina `__init__` yleensä heti luokan alkuun. +En konstruktormetod är en metoddeklaration med det speciella namnet `__init__`, som vanligtvis inkluderas i början av en klassdefinition. -Tarkastellaan `Pankkitili`-luokkaa, johon on lisätty konstruktori: +Låt oss ta en titt på en `Bankkonto`-klass med en konstruktormetod tillagd: ```python class Pankkitili: @@ -110,19 +111,19 @@ class Pankkitili: self.omistaja = omistaja ``` -Konstruktorin nimi on aina `__init__`. Huomaa, että nimessä sanan `init` molemmilla puolilla on _kaksi alaviivaa_. +Namnet på konstruktorsmetoden är alltid `__init__`. Lägg märke till de två understrecken på båda sidorna av ordet `init`. -Konstruktorin ensimmäinen parametri on nimeltään `self`. Tämä viittaa olioon, jota käsitellään. Asetuslause +Den första parametern i en konstruktorsdefinition heter alltid `self`. Detta refererar till själva objektet och är nödvändigt för att deklarera alla attribut som är knutna till objektet. Tilldelningen `self.saldo = saldo` -asettaa parametrina annetun saldon luotavan olion saldoksi. On tärkeä huomata, että tässä yhteydessä muuttuja `self.saldo` on eri muuttuja kuin muuttuja `saldo`: +tilldelar objektets saldoattribut den balans som mottagits som argument. Det är vanligt att använda samma variabelnamn för parametrarna och dataattributen som definieras i en konstruktor, men variabelnamnen `self.saldo` och `saldo` ovan hänvisar till två olika variabler: -* Muuttuja `self.saldo` viittaa olion attribuuttiin. Jokaisella Pankkitili-luokan oliolla on oma saldonsa. +* Variabeln `self.saldo` är ett attribut för objektet. Varje Bankkonto-objekt har sitt eget saldo. -* Muuttuja `saldo` on konstruktorimetodin `__init__` parametri, jolle annetaan arvo, kun metodia kutsutaan (eli kun halutaan luoda uusi olio luokasta). +* Variabeln `saldo` är en parameter i konstruktorsmetoden `__init__`. Dess värde sätts till det värde som skickas som argument till metoden när konstruktorn kallas (dvs. när en ny insctance av klassen skapas). -Nyt kun konstruktorille on määritelty parametrit, voidaan attribuuttien arvot antaa oliota luotaessa: +Nu när vi har definierat parametrarna för konstruktorsmetoden kan vi skicka de önskade initiala värdena för dataattributen som argument när ett nytt objekt skapas: ```python class Pankkitili: @@ -148,9 +149,9 @@ print(pirjon_tili.saldo) -Esimerkistä huomataan, että olioiden luominen helpottuu, kun arvot voidaan antaa heti oliota muodostaessa. Samalla tämä varmistaa, että arvojen antaminen ei unohdu, ja ohjaa käyttäjää antamaan arvot attribuuteille. +Det är nu mycket enklare att arbeta med Bankkonto-objekten, eftersom värdena kan skickas när objektet skapas, och de två separata instanserna kan hanteras på ett mer förutsägbart och enhetligt sätt. Att deklarera dataattribut i konstruktorn säkerställer också att attributen verkligen deklareras, och att de önskade initiala värdena alltid ges av programmeraren som använder klassen. -Attribuuttien arvoja voi edelleen muuttaa myöhemmin ohjelmassa, vaikka alkuarvo olisikin annettu konstruktorissa: +Det är fortfarande möjligt att ändra de initiala värdena för dataattributen senare i programmet: ```python class Pankkitili: @@ -180,7 +181,7 @@ print(pekan_tili.saldo) -Tarkastellaan vielä toista esimerkkiä luokasta ja olioista. Kirjoitetaan luokka, joka mallintaa yhtä lottokierrosta: +Låt oss titta på ett annat exempel på klasser och objekt. Vi ska skriva en klass som modellerar en enstaka dragning av lotterinummer: ```python from datetime import date @@ -218,7 +219,7 @@ for numero in kierros1.numerot: -Attribuutit voivat olla siis minkä tahansa tyyppisiä – esimerkiksi edellisessä esimerkissä jokaiseen olioon tallennetaan lista ja päivämäärä. +Som du kan se ovan kan attributerna vara av vilken sort som helst. Här har varje LotteriDragnings objekt attributer av typerna `list` och `date`. @@ -267,9 +268,9 @@ Kirjoita jokaiselle luokalle myös konstruktori, jossa attribuutit annetaan siin -## Omien luokkien olioiden käyttö +## Användning av objekt från egengjorda klasser -Omasta luokasta muodostetut oliot käyttäytyvät esimerkiksi funktioiden parametrina ja paluuarvona samalla tavalla kuin muutkin oliot. Voisimme esimerkiksi tehdä pari apufunktiota tilien käsittelyyn: +Objekt som bildas från dina egna klassdefinitioner skiljer sig inte från andra Python-objekt. De kan skickas som argument och returnera värden precis som alla andra objekt. Vi kan till exempel skriva hjälpfunktioner för att arbeta med bankkonton: ```python # funktio luo uuden tiliolion ja palauttaa sen diff --git a/data/osa-8/4-metodit-omissa-luokissa.md b/data/osa-8/4-metodit-omissa-luokissa.md index 18ae4cae4..65b18ca17 100644 --- a/data/osa-8/4-metodit-omissa-luokissa.md +++ b/data/osa-8/4-metodit-omissa-luokissa.md @@ -1,20 +1,20 @@ --- path: '/osa-8/4-metodit-omissa-luokissa' -title: 'Metodit omissa luokissa' +title: 'Metoder i egna klasser' hidden: false --- - + -Tämän osion jälkeen: +Efter den här delen: -- Tiedät, miten metodit toimivat luokissa -- Osaat kirjoittaa metodeita omiin luokkiin -- Ymmärrät, mitä tarkoitetaan kapseloinnilla ja asiakkaalla olio-ohjelmoinnissa +- Vet du hur klassmetoder fungerar +- Kan du skriva nya metoder i dina egna klasser +- Kommer du att förstå begreppen inkapsling och klient inom objektorienterad programmering -Vain attribuutteja sisältävät luokat eivät käytännössä eroa juurikaan sanakirjoista. Seuraavassa esimerkissä on esitetty pankkitiliä mallintava rakenne sekä oman luokan että sanakirjan avulla toteutettuna: +Klasser som endast innehåller dataattribut skiljer sig inte så mycket från ordlistor. Nedan kan du se två sätt att modellera ett bankkonto, först med en klassdefinition och sedan med hjälp av en ordlista. ```python # Esimerkki omaa luokkaa käyttäen @@ -34,15 +34,15 @@ pekan_tili = Pankkitili("12345-678", "Pekka Python", 1500.0, 0.015) pekan_tili = {"tilinumero": "12345-678", "omistaja": "Pekka Python", "saldo": 1500.0, "vuosikorko": 0.0} ``` -Sanakirjaa käyttäen rakenteen toteutus on huomattavasti suoraviivaisempi ja koodi on lyhyempi. Luokan hyötynä tässä tapauksessa on, että se määrittelee rakenteen "tiukemmin", jolloin kaikki luokasta muodostetut oliot ovat rakenteeltaan samanlaisia. Luokka on lisäksi nimetty: oliota muodostaessa viitataan `Pankkitili`-luokkaan ja olion tyyppi on `Pankkitili` eikä sanakirja. +Med en ordlista är implementeringen mycket kortare och enklare. Med en klass är strukturen däremot mer "hårt bunden", så vi kan förvänta oss att alla `Bankkonto`-objekt är strukturellt lika. Dessutom är en klass också namngiven. Klassen `Bankkonto` refereras till när ett nytt bankkonto skapas, och objektets typ är `Bankkonto`, inte dict. -Luokilla on lisäksi etuna, että niihin voidaan lisätä attribuuttien lisäksi myös toiminnallisuutta. Yksi olio-ohjelmoinnin periaatteista onkin, että olioon on yhdistetty sekä tallennettavat tiedot että operaatiot, joilla tietoa voidaan käsitellä. +En annan stor fördel med klasser är att de förutom data även kan innehålla funktionalitet. En av de vägledande principerna för objektorienterad programmering är att ett objekt används för att komma åt både den data som är kopplad till ett objekt och funktionaliteten för att bearbeta denna data. -## Metodit luokissa +## Metoder i klasser -Metodi tarkoittaa luokkaan sidottua aliohjelmaa. Yleensä metodin toiminta kohdistuu vain yhteen olioon. Metodi kirjoitetaan luokan sisälle, ja se voi käsitellä attribuutteja kuten mitä tahansa muuttujia. +En metod är ett underprogram eller en funktion som är knuten till en specifik klass. Vanligtvis påverkar en metod bara ett enda objekt. En metod definieras inom klassdefinitionen och den kan komma åt klassens dataattribut precis som vilken annan variabel som helst. -Katsotaan esimerkkinä `Pankkitili`-luokan metodia, joka lisää koron pankkitilille: +Låt oss fortsätta med klassen `Bankkonto` som introducerades ovan. Nedan har vi en ny metod som lägger till ränta på kontot: ```python class Pankkitili: @@ -69,9 +69,9 @@ print(pekan_tili.saldo) -Metodi `lisaa_korko` kertoo olion saldon vuosikorkoprosentilla ja lisää tuloksen nykyiseen saldoon. Metodin toiminta kohdistuu siihen olioon, jonka kautta sitä kutsutaan. +Metoden `tillsätt_ränta` multiplicerar saldot på kontot med den årliga ränteprocenten och lägger sedan till resultatet till det aktuella saldot. Metoden verkar bara på det objekt som den anropas på. -Katsotaan vielä toinen esimerkki, jossa luokasta on muodostettu useampi olio: +Låt oss se hur detta fungerar när vi har skapat flera instanser av klassen: ```python # Luokka Pankkitili on määritelty edellisessä esimerkissä @@ -98,13 +98,13 @@ print(paulin_tili.saldo) -Korko lisätään vain siihen tiliin, jonka kautta metodia kutsutaan. Esimerkistä huomataan, että Pekalle ja Pirjolle lisätään eri korkoprosentit ja Paulin tilin saldo ei muutu ollenkaan, koska olion `paulin_tili` kautta ei kutsuta metodia `lisaa_korko`. +Som du kan se ovan läggs den årliga räntan endast till på de konton som metoden anropas på. Eftersom den årliga räntan är olika för Peters och Paulas konton, blir resultatet olika för dessa två konton. Saldot på Pippas konto ändras inte, eftersom metoden `tillsätt_ränta` inte anropas på objektet `pippas_konto`. -## Kapselointi +## Inkapsling -Olio-ohjelmoinnin yhteydessä puhutaan usein olioiden _asiakkaista_. Asiakkaalla (client) tarkoitetaan koodin osaa, joka muodostaa olion ja käyttää sen palveluita kutsumalla metodeita. Kun olion tietosisältöä käsitellään vain olion tarjoamien metodien avulla, voidaan varmistua siitä, että olion _sisäinen eheys_ säilyy. Käytännössä tämä tarkoittaa esimerkiksi sitä, että `Pankkitili`-luokassa tarjotaan metodi, jolla tililtä nostetaan rahaa, sen sijaan, että asiakas käsittelisi suoraan attribuuttia `saldo`. Tässä metodissa voidaan sitten esimerkiksi varmistaa, ettei tililtä nosteta katetta enempää rahaa. +Inom objektorienterad programmering dyker ordet klient upp då och då. Det används för att hänvisa till ett kodavsnitt som skapar ett objekt och använder den tjänst som tillges av dess metoder. När data som finns i ett objekt endast används genom de metoder som det tillges, garanteras objektets interna integritet. I praktiken innebär detta att t.ex. en `Bankkonto`-klass erbjuder metoder för att hantera `saldo`-attributet, så att saldot aldrig nås direkt av klienten. Dessa metoder kan sedan verifiera att saldot till exempel inte tillåts gå under noll. -Esimerkiksi: +Ett exempel på hur detta skulle fungera: ```python class Pankkitili: @@ -149,9 +149,9 @@ Nosto ei onnistunut, rahaa ei ole tarpeeksi. -Olion sisäisen eheyden säilyttämistä ja sopivien metodien tarjoamista asiakkaalle kutsutaan _kapseloinniksi_. Tämä tarkoittaa, että olion toteutus piilotetaan asiakkaalta ja olio tarjoaa ulkopuolelle metodit, joiden avulla tietoja voi käsitellä. +Att bibehålla objektets interna integritet och erbjuda lämpliga metoder för att säkerställa detta kallas inkapsling. Tanken är att objektets inre arbete är dolt för klienten, men objektet erbjuder metoder som kan användas för att komma åt den data som lagras i objektet. -Pelkkä metodin lisäys ei kuitenkaan piilota attribuuttia: vaikka luokkaan `Pankkitili` onkin lisätty metodi `nosto` rahan nostamiseksi, asiakas voi edelleen muokata `saldo`-attribuutin arvoa suoraan: +Att lägga till en metod innebär inte att attributet automatiskt döljs. Även om klassdefinitionen `Bankkonto` innehåller metoden `uttag` för att ta ut pengar, kan klientkoden fortfarande komma åt och ändra attributet `saldo` direkt: ```python pekan_tili = Pankkitili("12345-678", "Pekka Python", 1500.0, 0.015) @@ -168,7 +168,7 @@ else: print("Saldo nyt:", pekan_tili.saldo) ``` -Ongelma voidaan ainakin osittain ratkaista piilottamalla attribuutit asiakkaalta. Käytännön toteutukseen palataan tarkemmin ensi viikolla. +Det är möjligt att dölja dataattributen från klientkoden, vilket kan bidra till att lösa detta problem. Vi återkommer till detta ämne i nästa del. @@ -280,7 +280,7 @@ arvo: 55 -Tarkastellaan vielä esimerkkiä luokasta, joka mallintaa pelaajan ennätystulosta. Luokkaan on kirjoitettu erilliset metodit, joilla voidaan tarkastaa, ovatko annetut parametrit sopivia. Metodeja kutsutaan heti konstruktorissa. Näin varmistetaan luotavan olion sisäinen eheys. +Som avslutning på detta avsnitt tittar vi på en klass som modellerar en spelares personliga bästa. Klassdefinitionen innehåller separata valideringsmetoder som kontrollerar att de argument som skickas är giltiga. Metoderna anropas redan i konstruktorn. Detta säkerställer att det skapade objektet är sunt internt. ```python from datetime import date @@ -341,13 +341,13 @@ Piia -Esimerkistä huomataan, että myös olion omiin metodeihin pitää viitata `self`-määreen avulla, kun niitä kutsutaan konstruktorista. Luokkiin voidaan kirjoittaa myös _staattisia metodeita_ eli metodeja, joita voidaan kutsua ilman, että luokasta muodostetaan oliota. Tähän palataan kuitenkin tarkemmin ensi viikolla. +I exemplet ovan anropades även hjälpmetoderna via parameternamnet `self` när de användes i konstruktorn. Det är också möjligt att inkludera /statiska/ metoddefinitioner i klassdefinitioner. Dessa är metoder som kan anropas utan att det någonsin skapas en instans av klassen. Vi återkommer till detta i nästa del. -Määrettä `self` käytetään kuitenkin vain silloin, kun viitataan _olion piirteisiin_ (eli metodeihin tai olion attribuutteihin). Olion metodeissa voidaan käyttää myös paikallisia muuttujia. Tämä on suositeltavaa, jos muuttujaan ei ole tarvetta viitata metodin ulkopuolella. +Parameternamnet `self` används endast när man hänvisar till /objektets egenskaper som en instans av klassen/. Det gäller både dataattributen och de metoder som är knutna till ett objekt. För att göra terminologin mer förvirrande kallas dataattributen och metoderna tillsammans ibland helt enkelt för objektets attribut, vilket är anledningen till att vi i detta material ofta har angett dataattribut när vi menar de variabler som definieras inom klassen. Det är här terminologin hos vissa Python-programmerare skiljer sig något från den terminologi som mer allmänt används inom objektorienterad programmering, där attribut vanligtvis bara hänvisar till dataattributen hos ett objekt. -Paikallinen muuttuja määritellään ilman `self`-määrettä - eli samoin kuin esimerkiksi kaikki muuttujat kurssin ensimmäisellä puoliskolla. +Det är också möjligt att skapa lokala variabler inom metoddefinitioner utan att hänvisa till `self`. Detta bör du göra om det inte finns något behov av att komma åt variablerna utanför metoden. Lokala variabler inom metoder har inga speciella nyckelord; de används precis som alla vanliga variabler som du har stött på hittills. . -Esimerkiksi +Så här skulle det till exempel fungera: ```python class Bonuskortti: diff --git a/data/osa-8/5-lisaa-esimerkkeja.md b/data/osa-8/5-lisaa-esimerkkeja.md index 18b03d403..b14ad8ffc 100644 --- a/data/osa-8/5-lisaa-esimerkkeja.md +++ b/data/osa-8/5-lisaa-esimerkkeja.md @@ -1,22 +1,22 @@ --- path: '/osa-8/5-lisaa-esimerkkeja' -title: 'Lisää esimerkkejä' +title: 'Fler exempel' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Osaat luoda aiempaa monipuolisempia luokkia -- Osaat määritellä luokkaan metodin `__str__` +- Kommer du att kunna skapa mer mångsidiga klasser +- Vet du hur du lägger till en `__str__`-metod i dina klassdefinitioner -## Esimerkki 1: Luokka Suorakulmio +## Exempel 1: klassen Rektangel -Tarkastellaan seuraavaksi luokkaa, joka mallintaa suorakulmiota kaksiulotteisessa koordinaatistossa: +Låt oss ta en titt på en klass som modellerar en rektangel i ett tvådimensionellt rum: ```python class Suorakulmio: @@ -39,13 +39,13 @@ class Suorakulmio: self.oikea_alakulma = (kulma[0]+x_muutos, kulma[1]+y_muutos) ``` -Kun suorakulmio luodaan, konstruktorille annetaan kaksi tuplea: vasemman yläkulman ja oikean alakulman sijainti (x- ja y-koordinaatit). Konstruktori laskee tämän perusteella suorakulmion leveyden ja korkeuden. +En ny `Rektangel` skapas med två tupler som argument. Dessa tupler innehåller x- och y-koordinaterna för det övre vänstra hörnet och det nedre högra hörnet. Konstruktören beräknar rektangelns höjd och bredd baserat på dessa värden. -Metodit `pinta_ala` ja `piiri` laskevat suorakulmion pinta-alan ja piirin korkeuden ja leveyden perusteella. Metodi `siirra` puolestaan siirtää suorakulmiota koordinaatistossa annetun verran x- ja y-suunnissa. +Metoderna `area` och `omkrets` beräknar rektangelns area och omkrets baserat på höjd och bredd. Metoden `flytta` flyttar rektangeln med de x- och y-värden som anges som argument. -Huomaa, että suorakulmio esitetään koordinaatistossa, jossa x-koordinaatit kasvavat vasemmalta oikealle ja y-koordinaatit kasvavat ylhäältä alaspäin. Tämä on usein käytetty koordinaatisto ohjelmoinnissa, koska on luontevaa esittää tietokoneen näyttö niin, että vasemman yläkulman x- ja y-koordinaatti on 0. +Rektangeln representeras i ett koordinatsystem där x-koordinaterna ökar från vänster till höger och y-koordinaterna ökar från topp till botten. Detta är ett vanligt sätt att hantera koordinater i programmering eftersom det ofta är enklare och mer naturligt att betrakta datorskärmens övre vänstra hörn som den punkt där x och y är lika med noll. -Seuraava koodi testaa luokkaa: +Följande program testar klassen `Rektangel`: ```python suorakulmio = Suorakulmio((1, 1), (4, 3)) @@ -74,16 +74,16 @@ print(suorakulmio.oikea_alakulma) -## Olion tulostaminen +## Skriva ut ett objekt -Kun omasta luokasta luotu olio tulostetaan sellaisenaan `print`-komennolla, lopputulos ei ole kovin selkeä: +När du har ett objekt som skapats från en klass som du själv definierat, är standardreaktionen på att anropa instruktionen `print` med objektet som argument inte särskilt informativt: ```python suorakulmio = Suorakulmio((1, 1), (4, 3)) print(suorakulmio) ``` -Ohjelma tulostaa jotain seuraavankaltaista: +Utskriften borde se ut någorlunda så här: @@ -91,9 +91,9 @@ Ohjelma tulostaa jotain seuraavankaltaista: -Järkevämpi tulostus saadaan lisäämällä luokkaan metodi `__str__`, joka palauttaa ymmärrettävän kuvauksen olion tilasta merkkijonona. Kun tämä metodi on määritelty, metodin palauttama kuvaus oliosta tulee näkyviin `print`-komennossa. +Vi vill självklart ha mer kontroll över vad som skrivs ut. Det enklaste sättet att göra detta är att lägga till en speciell `__str__`-metod i klassdefinitionen. Dess syfte är att returnera en ögonblicksbild av objektets tillstånd i strängformat. Om klassdefinitionen innehåller en `__str__`-metod är det värde som returneras av metoden, det som skrivs ut när instruktionen `print` körs. -Lisätään luokkaan `Suorakulmio` metodi `__str__`: +Så låt oss lägga till en `__str__`-metoddefinition i vår `Rektangel`-klass: ```python class Suorakulmio: @@ -105,7 +105,7 @@ class Suorakulmio: return f"suorakulmio {self.vasen_ylakulma} ... {self.oikea_alakulma}" ``` -Nyt `print`-komento tuottaa luettavan lopputuloksen: +Nu borde `print` instruktionen producera nånting mer användarvänligt: ```python suorakulmio = Suorakulmio((1, 1), (4, 3)) @@ -118,7 +118,7 @@ suorakulmio (1, 1) ... (4, 3) -Metodia `__str__` kutsutaan yleisemmin silloin, kun oliosta muodostetaan merkkijonokuvaus `str`-funktiolla. Seuraava koodi esittelee asiaa: +Metoden `__str__` används kanske oftare för att formulera en strängrepresentation av objektet med `str`-funktionen, som i följande program: ```python suorakulmio = Suorakulmio((1, 1), (4, 3)) @@ -133,7 +133,7 @@ suorakulmio (1, 1) ... (4, 3) -Metodin `__str__` lisäksi olioon voidaan määritellä samantapainen metodi `__repr__`, joka antaa teknisen kuvauksen olion tilasta. Tutustumme tähän metodiin tarkemmin myöhemmin. +Det finns många fler speciella understrukna metoder som kan definieras för klasser. En metod som liknar `__str__`-metoden är `__repr__`-metoden. Dess syfte är att ge en teknisk representation av objektets tillstånd. Vi kommer att stöta på denna metod senare. @@ -391,9 +391,9 @@ Matti: Kortilla on rahaa 72.8 euroa -## Esimerkki 2: Tehtävälista +## Exempel 2: Uppgiftslista -Seuraava luokka `Tehtavalista` toteuttaa tehtävälistan: +Följande klass `UppgiftsLista` modellerar en lista med uppgifter: ```python class Tehtavalista: @@ -417,11 +417,11 @@ class Tehtavalista: self.tehtavat = [] ``` -Metodi `lisaa` lisää listalle uuden tehtävän tietyllä prioriteetilla ja metodi `hae_seuraava` poistaa ja palauttaa listan suurimman prioriteetin tehtävän. Lisäksi metodi `yhteensa` antaa listan tehtävien yhteismäärän ja metodi `tyhjenna` tyhjentää listan. +Metoden `tillägg_uppgift` lägger till en ny uppgift på listan. Varje uppgift har också en prioritet, som används för att sortera uppgifterna. Metoden `hämta_nästa` tar bort och returnerar den uppgift som har högst prioritet i listan. Metoden `mängden_uppgifter` finns också, som returnerar antalet uppgifter i listan, och slutligen metoden `rensa_uppgifter`, som rensar uppgiftslistan. -Tehtäviä säilytetään sisäisesti listassa, jossa on tuplena kunkin tehtävän prioriteetti ja nimi. Prioriteetti tallennetaan ensin, jolloin tärkein tehtävä on listan lopussa listan järjestämisen jälkeen. Tämän ansiosta tehtävän saa haettua ja poistettua listalta kätevästi `pop`-metodilla. +Inom objektet lagras uppgifterna i en lista. Varje uppgift består av en tupel som innehåller uppgiftens prioritet och dess namn. Prioritetsvärdet lagras först, så att den uppgift som har högst prioritet hamnar sist i listan då den sorteras. Därför kan vi sedan helt enkelt använda `pop`-metoden för att hämta och ta bort det högst prioriterade objektet. -Seuraava koodi esittelee luokan käyttämistä: +Ta en titt på följande program med uppgiftslistan i handling: ```python lista = Tehtavalista() @@ -555,4 +555,3 @@ Huomaa, että yllä oleva koodi ja testit olettavat, että luokassa on attribuut Vastaa lopuksi osion loppukyselyyn: - diff --git a/data/osa-9/1-oliot-ja-viitteet.md b/data/osa-9/1-oliot-ja-viitteet.md index 1b6fb1abd..f310cbc7b 100644 --- a/data/osa-9/1-oliot-ja-viitteet.md +++ b/data/osa-9/1-oliot-ja-viitteet.md @@ -1,19 +1,19 @@ --- path: '/osa-9/1-oliot-ja-viittaukset' -title: 'Oliot ja viittaukset' +title: 'Objekt och referenser' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, miten olioita voi tallentaa tietorakenteisiin -- Tiedät, miten olioiden välitys parametrina toimii +- Kommer du att kunna använda olika datastrukturer för att hantera objekt +- Vet du hur objekt kan bli passerade som argument -Pythonissa kaikki arvot ovat olioita ja myös omista luokista luotuja olioita voi käsitellä kuin mitä tahansa muitakin olioita. Esimerkiksi olioita voidaan tallentaa listaan: +Varje värde i Python är ett objekt. Alla objekt som du skapar baserat på en klass som du själv har definierat fungerar exakt på samma sätt som alla "vanliga" Python-objekt. Objekt kan till exempel lagras i en lista: ```python from datetime import date @@ -119,7 +119,7 @@ Koesuoritus (suorittaja: Petriina, pisteet: 17) -Listaan ei tarkkaan ottaen tallenneta olioita vaan _viittauksia olioihin_. Niinpä sama olio voi esiintyä listassa useaan kertaan ja samaan olioon voidaan viitata useaan kertaan listassa ja sen ulkopuolella. Esimerkiksi näin: +Du kanske minns att listor inte innehåller några objekt i sig själva. De innehåller referenser till objekt. Exakt samma objekt kan förekomma flera gånger i en och samma lista, och det kan refereras till flera gånger i listan eller utanför den. Låt oss ta en titt på ett exempel: ```python class Tuote: @@ -139,7 +139,7 @@ if __name__ == "__main__": -Jos samaan olioon on useampi kuin yksi viittaus, on lopputuloksen kannalta yhdentekevää, mitä viittauksista käytetään: +Om det finns mer än en referens till samma objekt spelar det ingen roll vilken av referenserna som används: ```python class Koira: @@ -187,9 +187,9 @@ Fifi -Listan kohdissa 0 ja 1 on viittaus samaan olioon, joten olion sisältöä voidaan muuttaa kumman tahansa viittauksen kautta. Listan kohdassa 2 on kuitenkin viittaus toiseen olioon, minkä vuoksi tämän olion muuttaminen ei muuta muita. +Referenserna på index 0 och 1 i listan hänvisar till samma objekt. Var och en av referenserna kan användas för att komma åt objektet. Referensen på index 2 hänvisar till ett annat objekt, men med till synes samma innehåll. Om innehållet i det senare objektet ändras påverkas inte det andra. -Operaattorilla `is` voidaan tutkia, onko kyseessä täysin sama olio, ja operaattorilla `==` voidaan tutkia, onko kyseessä saman sisältöinen olio. Seuraava koodi havainnollistaa asiaa: +Operatorn `is` används för att kontrollera om de två referenserna hänvisar till exakt samma objekt, medan operatorn `==` talar om för dig om innehållet i objekten är detsamma. Följande exempel gör förhoppningsvis skillnaden tydlig: ```python @@ -220,7 +220,7 @@ True -Omista luokista muodostettuja olioita voidaan myös tallentaa esimerkiksi sanakirjaan ja muihin tietorakenteisiin: +Alla Python-objekt kan också lagras i en ordlista eller någon annan datastruktur. Detta gäller även objekt som är av en klass som du själv har definierat. ```python class Opiskelija: @@ -235,16 +235,16 @@ if __name__ == "__main__": opiskelijat["54321"] = Opiskelija("Outi Opiskelija", 67) ``` -[Visualisaattori](http://www.pythontutor.com/visualize.html#mode=edit) osaa havainnollistaa nämäkin asiat hienosti: +[Visualiseringsverktyget](http://www.pythontutor.com/visualize.html#mode=edit) kan hjälpa dig att förstå exemplet ovan: -## Selfillä vai ilman? +## Self eller inget self? -Tässä vaiheessa kurssia `self`-määre saattaa vaikuttaa vielä hämärältä. Käytetään siis hetki sen pohtimiseen, milloin selfiä tulee käyttää, ja milloin sitä kannattaa olla käyttämättä. +Hittills har vi bara snuddat vid ytan när det gäller att använda parameternamnet `self`. Låt oss titta närmare på när det bör eller inte bör användas. -Tarkastellaan esimerkkinä yksinkertaista luokkaa, jonka avulla joukosta sanoja on mahdollista muodostaa sanasto: +Nedan har vi en enkel klass som låter oss skapa ett vocabulary-objekt som innehåller några ord: ```python class Sanasto: @@ -278,11 +278,11 @@ python -Luokka tallentaa sanalistan oliomuuttujaan `self.sanat`. Tässä tapauksessa `self` tarvitaan ehdottomasti sekä luokan konstruktorissa että luokan muissa metodeissa tähän muuttujaan viitatessa, koska muuten sama lista ei ole kaikkien olion metodien käytettävissä. +Listan med ord lagras i ett attribut med namnet `self.ord`. I det här fallet är parameternamnet `self` obligatoriskt både i klassens konstruktormetod och i alla andra metoder som använder variabeln. Om `self` utelämnas kommer de olika metoderna inte att få tillgång till samma lista med ord. -Lisätään luokalle metodi `pisin_sana(self)` joka selvittää nimensä mukaisesti sanaston pisimmän sanan (tai yhden niistä). +Låt oss lägga till en ny metod i vår klassdefinition. Metoden `längsta_ordet(self)` returnerar (ett av) de längsta orden i vokabulären. -Tehtävän voisi toteuttaa vaikkapa seuraavasti, mutta näemme kohta miksei se ole kovin hyvä idea: +Följande är ett sätt att utföra denna uppgift, men vi kommer snart att se att det inte är ett särskilt bra sätt: ```python class Sanasto: @@ -304,7 +304,7 @@ class Sanasto: return self.pisin ``` -Metodi siis käyttää kahta apumuuttujaa, jotka on määritelty käyttäen `self`-määrettä. Jos vielä halutaan hämmentää ohjelmakoodia lukevaa, apumuuttujat voisi lisäksi nimetä kryptisemmin, esim. `apu` ja `apu2`: +Den här metoden använder två hjälpvariabler som deklareras med parameternamnet `self`. Kom ihåg att namnen på variablerna inte spelar någon roll i funktionell mening, så dessa variabler kan också namnges mer förvirrande som till exempel `hjälpare` och `hjälpare2`. Koden börjar se lite kryptisk ut: ```python class Sanasto: @@ -326,11 +326,11 @@ class Sanasto: return self.apu ``` -Kun muuttujan määrittely tehdään `self`-määreen avulla, liitetään muuttuja olion attribuutiksi, eli muuttuja tulee olemaan edelleen olemassa myös metodin suorituksen päätyttyä. Tämä on aivan tarpeetonta, koska kyseisiä apumuuttujia on tarkoitus käyttää vain metodissa `pisin_sana(self)`. Apumuuttujien määrittely `self`-määreen avulla on siis varsin huono idea. +När en variabel deklareras med parameternamnet `self` blir den ett attribut till objektet. Detta innebär att variabeln kommer att existera så länge objektet existerar. Specifikt kommer variabeln att fortsätta existera även efter att metoden som deklarerar den har avslutat sin exekvering (engelska “Execution”). I exemplet ovan är detta helt onödigt, eftersom hjälpvariablerna endast är avsedda att användas inom metoden `longest_word(self)`. Så att deklarera hjälpvariabler med parameternamnet `self` är inte en särskilt bra idé här. -Paitsi turhaa, apumuuttujien liittäminen `self`-määreellä olion attribuuteiksi on myös riskialtista, varsinkin epämääräisesti nimettyjen apumuuttujien tapauksessa. Jos samaa apumuuttujaa `self.apu` käytetään monessa eri metodissa mutta täysin eri tarkoituksiin, voivat seuraukset olla arvaamattomat ja koodissa voi ilmetä hankalasti löydettäviä bugeja. +Förutom att variabler kan existera efter sitt "utgångsdatum" kan användning av `self` för att skapa nya attribut där de inte är nödvändiga orsaka svåra buggar i din kod. Särskilt generiskt namngivna attribut som `self.hjalpare`, som sedan används i flera olika metoder, kan orsaka oväntade beteenden som är svåra att spåra. -Ongelma voi tulla esiin erityisesti silloin jos apumuuttujan alkuarvo annetaan jossain muualla, esimerkiksi konstruktorissa: +Om t.ex. en hjälpvariabel deklareras som ett attribut och tilldelas ett ursprungligt värde i konstruktorn, men variabeln sedan används i ett orelaterat sammanhang i en annan metod, blir resultatet ofta oförutsägbart: ```python class Sanasto: @@ -354,9 +354,9 @@ class Sanasto: return self.apu ``` -Toisaalta uusien olion attribuuttien määrittely _muualla_ kuin konstruktorissa on sikäli vaarallista, että tällöin olion attribuutit riippuvat siitä, mitä metodeja on suoritettu. Kaikilla saman luokan avulla luoduilla olioilla ei välttämättä ole samoja attribuutteja, mistä seuraa helposti bugeja. +Man skulle kunna tro att detta skulle lösas genom att bara deklarera attributen där de används, utanför konstruktorn, men detta resulterar i en situation där de attribut som är tillgängliga via ett objekt är beroende av vilka metoder som har utförts. I föregående del såg vi att fördelen med att deklarera attribut i konstruktorn är att alla instanser av klassen då kommer att ha exakt samma attribut. Om så inte är fallet kan det lätt leda till fel om man använder olika instanser av klassen. -Siispä oikea tapa määritellä yhdessä metodissa käytettävät apumuuttujat on tehdä se _ilman_ `self`-määrettä: +Sammanfattningsvis, om du behöver hjälpvariabler för användning inom en enda metod, är det korrekta sättet att göra det utan `self`. För att göra din kod lättare att förstå, använd också informativa variabelnamn: ```python class Sanasto: @@ -378,13 +378,13 @@ class Sanasto: return pisin ``` -Tällaisessa toteutuksessa apumuuttujat ovat olemassa ainoastaan metodin suorituksen aikana, ja niissä olevat arvot eivät pääse aiheuttamaan komplikaatioita muussa koodissa. +I implementeringen ovan är hjälpvariablerna endast tillgängliga när metoden utförs. De värden som lagras i dem kan inte orsaka komplikationer i andra delar av programmet. -## Oliot funktioiden parametrina +## Objekt som argument till funktioner -Omista luokista luodut oliot ovat yleensä muuttuvia eli mutatoituvia, joten niiden toiminta parametrina välitettäessä muistuttaa esimerkiksi listoista tuttua tapaa: funktio, jolle olio välitetään parametrina, voi muuttaa kyseistä oliota. +De objekt som skapas baserat på våra egna klasser är vanligtvis mutabla. Du kanske kommer ihåg att till exempel Python-listor är föränderliga: när de passeras som argument till funktioner kan deras innehåll ändras som ett resultat av exekveringen. -Tarkastellaan yksinkertaista esimerkkiä, jossa funktiolle välitetään `Opiskelija`-luokasta luotu olio. Funktion sisällä muutetaan opiskelijan nimi, ja muutos näkyy myös pääohjelmassa, koska molemmissa tilanteissa viitataan samaan olioon. +Låt oss titta på ett enkelt exempel där en funktion får en referens till ett objekt av typen `Student` som sitt argument. Funktionen ändrar sedan namnet på studenten. Både funktionen och huvudfunktionen som anropar den har åtkomst till samma objekt, så ändringen syns även i huvudfunktionen. ```python class Opiskelija: @@ -414,7 +414,7 @@ Olli Opiskelija (12345) -Olion voi myös luoda funktion sisällä. Mikäli funktio palauttaa viittauksen olioon, on muodostettu olio käytettävissä myös pääohjelmassa: +Det är också möjligt att skapa objekt inom funktioner. Om en funktion returnerar en referens till det nyskapade objektet är det också åtkomligt inom huvudfunktionen: ```python from random import randint, choice @@ -452,6 +452,7 @@ if __name__ == "__main__": for opiskelija in opiskelijat: print(opiskelija) ``` +Om du kör ovanstående kan det resultera i följande utskrift (OBS: eftersom slumpen är inblandad kommer resultaten sannolikt att bli annorlunda om du testar koden själv). @@ -463,9 +464,9 @@ Minna Pythonen (86211) -## Oliot metodien parametrina +## Objekt som argument till metoder -Oliot toimivat normaaliin tapaan myös _metodien_ parametrina. Tarkastellaan seuraavaa esimerkkiä: +På liknande sätt kan objekt fungera som argument till metoder. Låt oss ta en titt på ett exempel från en nöjespark: ```python class Henkilo: @@ -490,7 +491,7 @@ class Huvipuistolaite: return f"{self.nimi} ({self.kavijoita} kävijää)" ``` -Huvipuistolaitteen metodi `ota_kyytiin` saa nyt parametrina luokan `Henkilo` olion. Jos kävijä on riittävän pitkä, metodi päästää hänet laitteeseen ja lisää kävijöiden määrää. Seuraavassa esimerkkisuoritus: +Attraktionen innehåller en metod `motta_besökare`, som tar ett objekt av typen `Person` som argument. Om besökaren är tillräckligt lång släpps denne ombord och antalet besökare ökas. Klasserna kan testas på följande sätt: ```python hurjakuru = Huvipuistolaite("Hurjakuru", 120) @@ -826,9 +827,9 @@ Maukkaita lounaita myyty 1 -## Saman luokan oliot metodien parametrina +## En instans av samma klass som argument till en metod -Tarkastellaan jälleen kerran yhtä versiota luokasta `Henkilo`: +Nedan har vi ytterligare en version av klassen `Person`: ```python class Henkilo: @@ -837,7 +838,7 @@ class Henkilo: self.syntynyt = syntynyt ``` -Oletetaan että olemme tekemässä ohjelmaa, joka vertailee henkilöiden ikiä. Voisimme tehdä tarkoitusta varten erillisen funktion: +Låt oss anta att vi vill skriva ett program som jämför åldern på objekt av typen Person. Vi kan skriva en separat funktion för detta ändamål: ```python def vanhempi_kuin(henkilo1: Henkilo, henkilo2: Henkilo): @@ -868,7 +869,7 @@ Grace Hopper ei ole vanhempi kuin Blaise Pascal -Olio-ohjelmoinnin henkeen kuuluu kuitenkin sijoittaa oliota käsittelevät "funktiot" luokan metodeiksi. Voisimmekin tehdä henkilölle metodin, jonka avulla henkilön ikää voidaan verrata _toiseen_ henkilöön: +En av principerna för objektorienterad programmering är att all funktionalitet som hanterar objekt av en viss typ ska inkluderas i klassdefinitionen, som metoder. I stället för en funktion kan vi alltså skriva en metod som gör det möjligt att jämföra åldern på ett Person-objekt med ett annat Person-objekt: ```python class Henkilo: @@ -884,9 +885,9 @@ class Henkilo: return False ``` -Nyt siis olio itse on `self` ja `toinen` on henkilöolio, joka toimii vertailukohtana. +Här kallas det objekt som metoden anropas på för `self`, medan det andra Person-objektet kallas för `annan`. -Huomaa, miten metodin kutsuminen eroaa funktion kutsumisesta: +Kom ihåg att anrop av en metod skiljer sig från anrop av en funktion. En metod är kopplad till ett objekt med punktnotationen: ```python muhammad = Henkilo("Muhammad ibn Musa al-Khwarizmi", 780) @@ -904,11 +905,11 @@ else: print(f"{grace.nimi} ei ole vanhempi kuin {pascal.nimi}") ``` -Pisteen vasemmalla puolella on siis verrattava henkilö, eli olio, johon metodin suorituksessa viittaa muuttuja `self`. Metodin parametrina taas on vertailukohta, eli metodin suorituksessa muuttujan `toinen` viittaama olio. +Till vänster om punkten finns själva objektet, som kallas `self` i metoddefinitionen. Inom parentes står argumentet till metoden, vilket är det objekt som kallas `annan`. -Ohjelman tulostus on sama kuin edellisessä funktiota käyttäneessä esimerkissä. +Utskriften från programmet är exakt densamma som med funktionsimplementeringen ovan. -Huomaa, että if-else-rakenne metodissa `vanhempi_kuin` on oikeastaan turha, sillä vertailun arvona on suoraan haluamamme totuusarvo. Voimme siis yksinkertaistaa metodia seuraavasti: +Till sist, en ganska kosmetisk punkt: `if...else`-strukturen i metoden `aldre_an` är i stort sett onödig. Värdet på det booleska uttrycket i villkoret är redan exakt samma sanningsvärde som returneras. Metoden kan alltså förenklas: ```python class Henkilo: @@ -921,7 +922,7 @@ class Henkilo: return self.syntynyt < toinen.syntynyt: ``` -Edellisestä esimerkistä kannattaa huomata se, että kun metodi saa parametrikseen toisen saman luokan olion, tulee tyyppivihje antaa hipsuissa, eli seuraava koodi aiheuttaisi virheen: +Liksom det framkommer av kommentarerna i exemplen ovan, så måste typhintet omslutas av citattecken ifall parametern i en metoddefinition är av samma typ som klassen själv. Om citattecknen utelämnas uppstår ett fel, vilket du kommer att se om du försöker med följande: ```python class Henkilo: diff --git a/data/osa-9/2-oliot-attribuuttina.md b/data/osa-9/2-oliot-attribuuttina.md index 101a36369..be6f3ce94 100644 --- a/data/osa-9/2-oliot-attribuuttina.md +++ b/data/osa-9/2-oliot-attribuuttina.md @@ -1,23 +1,23 @@ --- path: '/osa-9/2-oliot-attribuuttina' -title: 'Oliot attribuuttina' +title: 'Objekt som attribut ' hidden: False --- - + -Tämän osion jälkeen +Efter den här delen -- Osaat tallentaa olioita toisten olioiden sisään -- Tiedät, mitä tarkoittaa `None` +- Vet du hur man använder objekt som attribut i andra objekt +- Kommer du vara bekant med nyckelordet `None` -Aikaisemmin nähtiin esimerkkejä luokista, joissa attribuutteina oli käytetty esimerkiksi listoja. Samalla tavalla myös omista luokista luotuja olioita voi käyttää toisten olioiden attribuutteina. Seuraavissa esimerkeissä on määritelty luokat `Kurssi`, `Opiskelija` ja `Opintosuoritus`. Opintosuorituksessa hyödynnetään kahta ensimmäistä luokkaa. Luokkien sisäinen toteutus on lyhyt, jotta esimerkki toisi esille oleellisen. +Vi har redan sett exempel på klasser som har listor som attribut. Eftersom det alltså inte finns något som hindrar oss från att inkludera mutabla objekt som attribut i våra klasser, kan vi lika gärna använda instanser av våra egna klasser som attribut i andra klasser som vi själva har definierat. I följande exempel kommer vi att definiera klasserna `Kurs`, `Student` och `AvslutadKurs`. En avslutad kurs använder sig av de två första klasserna. Klassdefinitionerna är mycket korta och enkla för att vi bättre ska kunna koncentrera oss på tekniken att använda instanser av våra egna klasser som attribut. -Esimerkissä jokainen luokka on kirjoitettu omaan tiedostoonsa. +Vi kommer att anta att varje klass definieras i en separat fil. -Esitellään aluksi luokka `Kurssi`, joka on määritelty tiedostossa `kurssi.py`: +Först definierar vi klassen `Kurs` i en fil med namnet `kurs.py`: ```python class Kurssi: @@ -27,7 +27,7 @@ class Kurssi: self.opintopisteet = opintopisteet ``` -Luokka `Opiskelija` mallintaa yhtä opiskelijaa. Luokka on määritelty tiedostossa `opiskelija.py`: +Till näst, klassen `Student` i en fil med namnet `student.py`: ```python class Opiskelija: @@ -37,7 +37,7 @@ class Opiskelija: self.opintopisteet = opintopisteet ``` -Luokka `Opintosuoritus` hyödyntää luokkia `Kurssi` ja `Opiskelija` suorituksen tallentamiseen. Huomaa, että luokat tuodaan mukaan `import`-lauseella: +Slutligen är klassen `AvslutadKurs` definierad i en fil med namned `avslutadkurs.py`. Eftersom den använder de två andra klasserna, måste de importeras innan de kan användas: ```python from kurssi import Kurssi @@ -50,7 +50,7 @@ class Opintosuoritus: self.arvosana = arvosana ``` -Esimerkki opintosuoritusten lisäämisestä listaan: +Här är ett exempel av en huvudfunktion som lägger till några avslutade kurser i en lista: ```python from opintosuoritus import Opintosuoritus @@ -86,15 +86,15 @@ Tiina -Tarkastellaan lähemmin riviä `print(suoritus.opiskelija.nimi)`: +Vad exakt händer med alla prickar på raden `print(kurs.student.namn)`? -* `suoritus` on luokan `Opintosuoritus` mukainen olio -* Niinpä muuttuja `opiskelija` viittaa suoritukseen tallennettuun `Opiskelija`-olioon -* `Opiskelija`-luokan muuttuja `nimi` sisältää opiskelijan nimen +* `kurs` är en instans av klassen `AvslutadKurs` +* `student` refererar till ett attribut i objektet `AvslutadKurs`, som är ett objekt av typen `Student` +* attributnamnet i `Student`-objektet innehåller namnet på studenten -## Milloin import tarvitaan? +## När är en import nödvändig? -Edellisessä esimerkissä käytetään muutamassa kohdassa `import`:ia: +I exemplen ovan förekommer en `import`-sats ganska många gånger: ```python from opintosuoritus import Opintosuoritus @@ -104,7 +104,7 @@ from opiskelija import Opiskelija # koodi ``` -Importia tarvitaan vain jos tiedostossa käytetään jossain muualla määriteltyä koodia. Näin on esimerkiksi kun käytetään jotain Pythonin valmista kalustoa, esim. matemaattisia operaatiota tarjoavaa moduulia `math`: +En importsats är bara nödvändig när man använder kod som är definierad någonstans utanför den aktuella filen (eller Python-tolksessionen). Detta inkluderar situationer där vi vill använda något som är definierat i Pythons standardbibliotek. Modulen `math` innehåller till exempel vissa matematiska operationer: ```python import math @@ -113,13 +113,11 @@ x = 10 print(f"luvun {x} {neliöjuuri math.sqrt(x)}") ``` -Edellisessä tehtävässä oletettiin, että luokat on määritelty omissa tiedostoissaan. Esimerkki toteaa mm. - _Esitellään aluksi luokka Kurssi, joka on määritelty tiedostossa kurssi.py_ -ja importin tarve siis johtuu tästä. +I exemplet ovan antog vi att de tre klasserna definierades i var sin fil och att huvudfunktionen kördes från ytterligare en fil. Det var därför `import`-satserna var nödvändiga. -Jos kaikki koodi sijoitetaan samaan tiedostoon, kuten kaikissa kurssin ohjelmointitehtävissä ohjeistetaan, **et tarvitse** `import`:ia luomiesi luokkien käytöön. +Om all programkod skrivs i samma fil, vilket de flesta övningarna i den här kursen rekommenderar, behöver du **inte** `import`-satser för att använda de klasser du har definierat. -Jos siis päädyt kirjottamaan kurssilla seuraavanlaista koodia +Ifall du märker dig själv skriva något i stil med ```python from henkilo import Henkilo @@ -127,7 +125,7 @@ from henkilo import Henkilo # koodi ``` -ratkaisusi on todennäköisesti väärä! Lisää importin käytöstä [osan 7](/osa-7/1-moduulit/) materiaalissa. +är det sannolikt att du förstått nånting felaktigt. Ifall du behöver en uppfriskare så introducerades `import`deklarationen för första gången i [del 7](/osa-7/1-moduulit/) av kursmaterialet. @@ -152,11 +150,11 @@ Leevi, kaverina Hulda, joka on sekarotuinen koira -## Olion attribuuttina lista olioita +## En lista med objekt som attribut till ett objekt -Äskeisissä esimerkeissä oliolla oli attribuuttina yksittäinen toisen luokan olio, esim. henkilöllä attribuuttina lemmikki ja opintosuorituksella attribuuttina kurssi. +I exemplen ovan använde vi enstaka instanser av andra klasser som attribut: en Person har ett enda Husdjur som attribut, och en AvslutadKurs har en Student och en Kurs som attribut. -Olio-ohjelmoinnissa törmätään kutenkin usein tilanteeseen, jossa oliolla on attribuuttina _joukko_ toisen luokan olioita. Eräs tälläinen tilanne kuvaa joukkueen ja sen pelaajien välistä yhteyttä: +I objektorienterad programmering är det ofta så att vi vill ha en samling objekt som attribut. Till exempel följer relationen mellan ett idrottslag och dess spelare detta mönster: ```python class Pelaaja: @@ -184,7 +182,7 @@ class Joukkue: print("Pelaajien maalimäärät", maalit) ``` -Käyttöesimerkki: +Ett exempel på hur vår klass fungerar: ```python kupa = Joukkue("Kumpulan pallo") @@ -258,11 +256,11 @@ print(pakkaus.yhteispaino()) -## None eli viite ei mihinkään +## None: en referens till ingenting -Pythonissa muuttujat viittaavat aina johonkin olioon. On kuitenkin tilanteita, joissa haluaisimme määrittää arvon, joka ei viittaa mihinkään. Arvoa `None` käytetään esittämään tyhjää viittausta. +I Python-programmering refererar alla initialiserade variabler till ett objekt. Det finns dock oundvikligen situationer där vi måste referera till något som inte existerar, utan att orsaka fel. Nyckelordet `None` representerar just ett sådant "tomt" objekt. -Jos esimerkiksi luokkaan joukkue lisättäisiin metodi, joka etsii joukkueen pelaajan, saattaisi olla luontevaa esittää paluuarvolla `None` tilanne, jossa pelaajaa ei löydy: +Låt oss fortsätta från exemplet med lag och spelare ovan och anta att vi vill lägga till en metod för att söka efter spelare i laget med hjälp av spelarens namn. Om ingen sådan spelare hittas kan det vara vettigt att returnera `None`: ```python class Pelaaja: @@ -288,7 +286,7 @@ class Joukkue: return None ``` -Käyttöesimerkki: +Låt oss testa vår funktion: ```python kupa = Joukkue("Kumpulan pallo") @@ -309,7 +307,7 @@ None -`None`-arvojen kanssa pitää olla tarkkana. On hyvin tyypillistä, että ohjelmassa kutsutaan jotain metodia oliolle (tai pyydetään attribuutin arvoa oliolta), joka onkin `None`: +Var dock försiktig med `None`. Det kan ibland orsaka mer problem än det löser. Det är ett vanligt programmeringsfel att försöka komma åt en metod eller ett attribut via en referens som utvärderas till `None`: ```python kupa = Joukkue("Kumpulan pallo") @@ -319,7 +317,7 @@ pelaaja = kupa.etsi("Jukkis") print(f"Jukkiksen maalimäärä {pelaaja.maalit}") ``` -Jos näin tehdään, ohjelma päättyy virheeseen: +Exekverande av ovanstående skulle orsaka ett fel: @@ -329,7 +327,7 @@ AttributeError: 'NoneType' object has no attribute 'maalit' -`None`-arvojen varalta onkin syytä tehdä tarkistus, ennen kuin riskialtista koodia kutsutaan: +Det är en god idé att kontrollera om det finns `None` innan du försöker komma åt några attribut eller metoder för returvärden: ```python kupa = Joukkue("Kumpulan pallo") @@ -484,4 +482,3 @@ class Huone: ``` - diff --git a/data/osa-9/3-kapselointi.md b/data/osa-9/3-kapselointi.md index dd2ee0f11..d39816b01 100644 --- a/data/osa-9/3-kapselointi.md +++ b/data/osa-9/3-kapselointi.md @@ -1,27 +1,27 @@ --- path: '/osa-9/3-kapselointi' -title: 'Kapselointi' +title: 'Inkapsling' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, mitä tarkoitetaan kapseloinnilla -- Osaat muodostaa piilotetun attribuutin -- Osaat kirjoittaa attribuutille asetus- ja havainnointimetodit +- Vet du vad inkapsling innebär +- Kommer du att kunna skapa privata attribut +- Vet du hur du skapar gettar och sättare för dina attribut -Olio-ohjelmoinnissa asiakkaalla tarkoitetaan luokkaa tai siitä muodostettuja olioita käyttävää ohjelmaa. Luokka tarjoaa asiakkaalle _palveluja_, joiden avulla asiakas voi käyttää olioita. Päämääränä on, että +I objektorienterad programmering avser termen klient ett program som använder en klass eller instanser av en klass. En klass erbjuder klienten tjänster genom vilka klienten kan komma åt de objekt som skapats baserat på klassen. Målen här är att -1) asiakkaan kannalta luokan ja olioiden käyttö on mahdollisimman yksinkertaista ja -2) olion _sisäinen eheys_ säilyy joka tilanteessa. +1) användningen av en klass och/eller objekt är så enkel som möjligt ur klientens synvinkel +2) integriteten för varje objekt bevaras hela tiden -Sisäisellä eheydellä tarkoitetaan, että olion _tila_ (eli käytännössä olion attribuuttien arvot) pysyy koko ajan hyväksyttävänä. Virheellinen tila olisi esimerkiksi sellainen, jossa päivämäärää esittävälle oliolle kuukauden numero on 13 tai opiskelijaa esittävällä oliolla opintopistemäärä on negatiivinen luku. +Ett objekts integritet innebär att objektets tillstånd alltid förblir acceptabelt. I praktiken innebär detta att värdena på objektets attribut alltid är acceptabla. Ett objekt som representerar ett datum ska till exempel aldrig ha 13 som värde för månaden, ett objekt som representerar en student ska aldrig ha ett negativt tal som värde för uppnådda studiepoäng och så vidare. -Tarkastellaan esimerkkinä luokkaa Opiskelija: +Låt oss ta en titt på en klass som heter Student: ```python class Opiskelija: @@ -35,7 +35,7 @@ class Opiskelija: self.opintopisteet += opintopisteet ``` -`Opiskelija`-olio tarjoaa asiakkaalle metodin `lisaa_suoritus`, jolla opintopisteitä voidaan lisätä. Metodi varmistaa, että lisättävä opintopisteiden määrä on positiivinen. Esimerkiksi seuraava koodi lisää kolme suoritusta: +`Student`objektet erbjuder sina klienter metoden `tillägg_poäng`, som gör det möjligt för klienten att lägga till ett angivet antal studiepoäng till studentens totala antal. Metoden säkerställer att värdet som skickas som argument är över noll. Följande kod lägger till studiepoäng vid tre tillfällen: ```python oskari = Opiskelija("Oskari Opiskelija", "12345") @@ -52,7 +52,7 @@ Opintopisteet: 20 -Asiakas pystyy kuitenkin muuttamaan opintopistemäärää myös suoraan viittaamalla attribuuttiin `opintopisteet`. Näin olio voi päätyä virheelliseen tilaan, jossa se ei ole enää sisäisesti eheä: +Trots metoddefinitionen är det fortfarande möjligt att komma åt attributet `studie_poäng` direkt. Detta kunde resultera i ett felaktigt tillstånd där objektets integritet går förlorad: ```python oskari = Opiskelija("Oskari Opiskelija", "12345") @@ -66,9 +66,9 @@ Opintopisteet: -100 -## Kapselointi +## Inkapsling -Luokka voi piilottaa attribuutit asiakkailta. Pythonissa tämä tapahtuu lisäämällä attribuuttimuuttujan nimen alkuun kaksi alaviivaa `__`: +Ett vanligt inslag i objektorienterade programmeringsspråk är att klasserna kan dölja sina attribut för eventuella kunder. Dolda attribut kallas vanligtvis privata. I Python uppnås denna sekretess genom att lägga till två understreck `__` i början av attributnamnet: ```python class Pankkikortti: @@ -78,7 +78,7 @@ class Pankkikortti: self.nimi = nimi ``` -Piilotettu attribuutti ei näy asiakkaalle, vaan siihen viittaaminen aiheutta virheilmoituksen. Niinpä nimen voi tulostaa ja sitä voi muuttaa: +Ett privat attribut är inte direkt synligt för klienten. Försök att referera till det orsakar ett fel. I exemplet ovan är attributet namn lätt att komma åt och ändra: ```python kortti = Pankkikortti("123456","Reijo Rahakas") @@ -94,7 +94,7 @@ Reijo Rutiköyhä -Mutta jos kortin numeroa yritetään tulostaa, seuraa virheilmoitus: +Ifall man provar få en utskrift av kortnumret så orsakar det däremot ett fel: ```python kortti = Pankkikortti("123456","Reijo Rahakas") @@ -107,9 +107,9 @@ AttributeError: 'Pankkikortti' object has no attribute '__numero' -Tietojen piilottamista asiakkaalta kutsutaan _kapseloinniksi_. Nimensä mukaisesti attribuutti siis "suljetaan kapseliin" ja asiakkaalle tarjotaan sopiva rajapinta, jonka kautta tietoa voi käsitellä. +Att dölja attribut från klienter kallas inkapsling. Som namnet antyder är attributet "slutet inne i en kapsel". Klienten erbjuds sedan ett lämpligt gränssnitt (engelska: interface) för att komma åt och bearbeta den data som finns lagrad i objektet. -Laajennetaan pankkikorttiesimerkkiä niin, että kortilla on piilotettu attribuutti saldo ja tämän käsittelyyn tarkoitetut julkiset metodit, joiden avulla asiakas voi hallita saldoa: +Låt oss lägga till ett annat inkapslat attribut: saldot på kreditkortet. Den här gången lägger vi också till offentligt synliga metoder som gör det möjligt för klienten att komma åt och ändra saldot: ```python class Pankkikortti: @@ -151,7 +151,7 @@ print(kortti.hae_saldo()) -Saldoa ei voi suoraan muuttaa, koska attribuutti on piilotettu, mutta sitä voi muuttaa metodeilla `lisaa_rahaa` ja `kayta_rahaa` ja sen voi hakea metodilla `hae_saldo`. Metodeihin voidaan sijoittaa sopivia tarkastuksia, joilla varmistetaan, että olion sisäinen eheys säilyy: esimerkiksi rahaa ei voi käyttää enempää kuin kortilla on saldoa jäljellä. +Saldot kan inte ändras direkt eftersom attributet är privat, men vi har inkluderat metoderna `tillsatt_pengar` och `ta_ut_pengar` för att ändra värdet. Metoden `returnera_saldo` returnerar det värde som lagrats i saldo. Metoderna innehåller några rudimentära kontroller för att bibehålla objektets integritet: till exempel kan kortet inte överdras. @@ -196,9 +196,15 @@ Auto: ajettu 60 km, bensaa 60 litraa -## Asetus- ja havainnointimetodit +## En kort notis om privata attribut, Python och objektorienterad programmering -Python tarjoaa myös suoraviivaisemman syntaksin attribuuttien havainnoimiselle ja asettamiselle. Tarkastellaan ensin esimerkkinä yksinkertaista luokkaa `Lompakko`, jossa ainoa attribuutti `rahaa` on suojattu asiakkailta: +Det finns sätt att kringgå understryknings `__`-notationen för att dölja attribut, som du kan stöta på om du söker efter material online. Inget Python-attribut är verkligen privat, och det är avsiktligt från skaparna av Pythons. Å andra sidan förväntas en Python-programmerare i allmänhet respektera de riktlinjer för synlighet som anges i klasser, och det krävs en särskild ansträngning för att komma runt dessa. I andra objektorienterade programmeringsspråk, till exempel Java, är privata variabler ofta verkligen dolda, och det är bäst om du tänker på privata Python-variabler som sådana också. + +## Getter och sättare + +I objektorienterad programmering kallas metoder som är avsedda för att komma åt och ändra attribut vanligtvis för getter och sättare (eng: setters). Inte alla Python-programmerare använder termerna "getter" och "sättare", men konceptet med egenskaper som beskrivs nedan är mycket liknande, vilket är varför vi kommer att använda den allmänt accepterade objektorienterade programmeringsterminologin här. + +Ovan skapade vi några offentliga metoder för att komma åt privata attribut, men det finns ett enklare, "pythoniskt" sätt att komma åt attribut. Låt oss ta en titt på en enkel klass som heter `Plånbok` med ett enda privat attribut `pengar`: ```python class Lompakko: @@ -206,7 +212,7 @@ class Lompakko: self.__rahaa = 0 ``` -Luokkaan voidaan lisätä havainnointi- ja asetusmetodit, joilla asiakas voi hallita rahamäärää: +Vi kan tillägga getter och sättar metoder för att komma åt det privata attributet genom att använda `@property` dekoratorn: ```python class Lompakko: @@ -225,9 +231,9 @@ class Lompakko: self.__rahaa = rahaa ``` -Luokalle siis määritellään ensin havainnointimetodi, joka palauttaa rahamäärän, ja sitten asetusmetodi, joka asettaa rahamäärän ja varmistaa, että uusi rahamäärä ei ole negatiivinen. +Först definierar vi en getter-metod som returnerar den summa pengar som för närvarande finns i plånboken. Sedan definierar vi en sättar-metod som sätter ett nytt värde för money-attributet och samtidigt ser till att det nya värdet inte är negativt. -Kutsuminen tapahtuu esimerkiksi näin: +De nya metoderna kan användas på följande sätt: ```python lompsa = Lompakko() @@ -248,11 +254,11 @@ print(lompsa.rahaa) -Asiakkaan kannalta metodien kutsuminen muistuttaa attribuuttien kutsumista, koska kutsussa ei käytetä sulkuja vaan voi kirjoittaa esimerkiksi`lompsa.rahaa = 50`. Tarkoituksena onkin piilottaa (eli kapseloida) sisäinen toteutus ja tarjota asiakkaalle vaivaton tapa muokata olion tietoja. +För klienten är det ingen skillnad att använda dessa nya metoder jämfört med att direkt komma åt ett attribut. Parenteser är inte nödvändiga, utan det är helt acceptabelt att ange `plånbok.pengar = 50`, som om vi helt enkelt tilldelar ett värde till en variabel. Syftet var faktiskt att dölja (dvs. kapsla in) den interna implementeringen av attributet och samtidigt erbjuda ett enkelt sätt att komma åt och ändra den data som lagras i objektet. -Edellisessä esimerkissä on kuitenkin yksi pieni vika: asiakas ei saa mitään viestiä siitä, että negatiivisen rahasumman asettaminen ei toimi. Kun arvo on selvästi virheellinen, hyvä tapa viestiä tästä on heittää poikkeus. Tässä tapauksessa oikea poikkeus voisi olla `ValueError`, joka kertoo että arvo on väärä. +I det föregående exemplet finns dock ett litet problem: klienten meddelas inte om att det inte går att ange ett negativt värde för attributet pengar. När ett värde som anges är uppenbart felaktigt är det vanligtvis en bra idé att skapa ett undantag och på så sätt informera klienten. I det här fallet bör undantaget förmodligen vara av typen `ValueError` för att visa att det angivna värdet var oacceptabelt. -Korjattu versio luokasta ja testikoodi: +Här har vi en förbättrad version av klassen, tillsammans med lite kod för att testa den: ```python class Lompakko: @@ -284,7 +290,7 @@ ValueError: Rahasumma ei saa olla negatiivinen -Huomaa, että havainnointimetodi eli `@property`-dekoraattori pitää esitellä luokassa ennen asetusmetodia, muuten seuraa virhe. Tämä johtuu siitä, että `@property`-dekoraattori määrittelee käytettävän "asetusattribuutin" nimen (edellisessä esimerkiksi `rahaa`), ja asetusmetodi `.setter` liittää siihen uuden toiminnallisuuden. +OBS: getter-metoden, dvs `@property-dekoratorn`, måste introduceras före sättar-metoden i koden, annars blir det fel när klassen exekveras. Detta beror på att `@property-dekoratorn` definierar namnet på det "attribut" som erbjuds till klienten. Sättar-metoden, som läggs till med `.setter`, lägger helt enkelt till en ny funktionalitet till den. @@ -319,7 +325,7 @@ Jos et muista miten poikkeus tuotetaan, kertaa -Katsotaan vielä esimerkki luokasta, jolla on kaksi suojattua attribuuttia ja molemmille havainnointi- ja asetusmetodit: +Följande exempel har en klass med två privata attribut, tillsammans med getter och sättare för båda. Prova programmet med olika värden som skickas som argument: ```python class Pelaaja: @@ -370,7 +376,9 @@ Paula Palloilija -Kolmantena esimerkkinä tarkastellaan luokkaa, joka mallintaa päiväkirjaa. Huomaa, että omistajalla on asetus- ja havainnointimetodit, mutta merkintöjen lisäys on toteutettu "perinteisillä" metodeilla. Tämä siksi, että asiakkaalle ei ole haluttu tarjota suoraan pääsyä tietorakenteeseen, johon merkinnät tallennetaan. Kapseloinnista on tässä sekin hyöty, että sisäistä toteutusta voidaan myöhemmin muuttaa (esim. vaihtamalla lista vaikka sanakirjaksi) ilman, että asiakkaan täytyy muuttaa omaa koodiaan. +Som avslutning på detta avsnitt ska vi titta på en klass som modellerar en enkel dagbok. Alla attribut är privata, men de hanteras genom olika gränssnitt: dagbokens ägare har getter- och sättar-metoder, men dagboksposterna behandlas med "traditionella" metoder. I det här fallet är det vettigt att neka klienten all tillgång till dagbokens interna datastruktur. Endast de offentliga metoderna är direkt synliga för klienten. + +Inkapsling säkerställer också att den interna implementeringen av klassen kan ändras när som helst, förutsatt att det offentliga gränssnittet förblir intakt. Klienten behöver inte veta eller bry sig om huruvida den interna datastrukturen är baserad på listor, ordlistor eller något helt annat. ```python class Paivakirja: diff --git a/data/osa-9/4-metodien-nakyvyys.md b/data/osa-9/4-metodien-nakyvyys.md index 678dd7ff5..b25927e69 100644 --- a/data/osa-9/4-metodien-nakyvyys.md +++ b/data/osa-9/4-metodien-nakyvyys.md @@ -1,23 +1,23 @@ --- path: '/osa-9/4-metodien-nakyvyys' -title: 'Metodien näkyvyys' +title: 'Metodernas räckvidd' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tiedät, miten metodin näkyvyys määritellään Pythonissa -- Osaat kirjoittaa piilotettuja metodeita +- Vet du hur du kan begränsa synligheten för en metod i Python +- Kommer du att kunna skriva privata metoder -Luokassa olevien metodien näkyvyyteen voidaan vaikuttaa samalla tavalla kuin attribuuttien näkyvyyteen. Jos metodin nimi alkaa kahdella alaviivalla `__`, metodi ei ole näkyvissä asiakkaille. +De metoder som definieras inom en klass kan döljas på exakt samma sätt som attributen i föregående avsnitt. Om metoden börjar med två understreck `__` är den inte direkt åtkomlig för klienten. -Käytännössä mekanismia käytetään hiukan eri tavalla: piilotettujen attribuuttien käyttöä varten kirjoitetaan usein julkiset havainnointi- ja asetusmetodit. Piilotettu metodi on kuitenkin yleensä tarkoitettu vain luokan sisäiseen käyttöön, apumetodiksi asiakkaalta piilotettujen operaatioiden toteuttamiseksi. +Tekniken är alltså densamma för både metoder och attribut, men användningsfallen är oftast lite annorlunda. Privata attribut kommer ofta tillsammans med getter- och sättar-metoder för att kontrollera åtkomsten till dem. Privata metoder å andra sidan är vanligtvis endast avsedda för internt bruk, som hjälpmetoder för processer som klienten inte behöver känna till. -Piilotettua metodia voidaan kutsua luokan sisällä normaalisti, mutta kutsuttaessa pitää muistaa `self`-aluke. Tarkastellaan esimerkkinä sähköpostin vastaanottajaa mallintavaa luokkaa `Vastaanottaja`, jossa yksityistä apumetodia käytetään tarkistamaan sähköpostiosoitteen oikeellisuus: +En privat metod kan användas inom klassen precis som vilken annan metod som helst, men man måste naturligtvis komma ihåg att inkludera `self`-prefixet. Följande är en enkel klass som representerar mottagaren av e-postbrev. Den innehåller en privat hjälpmetod för att kontrollera att e-postadressen är i ett giltigt format: ```python class Vastaanottaja: @@ -33,7 +33,7 @@ class Vastaanottaja: return len(sposti) > 5 and "." in sposti and "@" in sposti ``` -Jos asiakas yrittää kutsua metodia, seuraa virhe: +Försök att kalla den privata metoden direkt orsakar ett fel: ```python pertti = Vastaanottaja("Pertti Keinonen", "pertti@example.com") @@ -46,7 +46,7 @@ AttributeError: 'Vastaanottaja' object has no attribute '__tarkasta_sposti' -Samaa apumetodia kannattaa kutsua myös sähköpostia asettaessa - lisätään siis luokkaan esimerkin vuoksi havainnointi- ja asetusmetodit sähköpostille: +Inom klassen kan metoden användas på normalt sätt, och det är vettigt att använda den även för att ange ett nytt värde för adressen. Låt oss lägga till getter- och sätter-metoder för e-postadressen: ```python class Vastaanottaja: @@ -73,7 +73,7 @@ class Vastaanottaja: raise ValueError("Sähköposti ei kelpaa") ``` -Tarkastellaan sitten toista esimerkkiä. Luokka `Korttipakka` mallintaa nimensä mukaisesti 52 kortin korttipakkaa. Apumetodi `__alusta_pakka` luo uuden sekoitetun pakan oliota luotaessa. Vastaava alustus voitaisiin toki tehdä myös metodissa `__init__`, mutta erillisen apumetodin käyttö tekee koodista siistimpää ja mahdollistaa alustusmetodin kutsumisen myös muualta luokasta tarvittaessa. +I följande exempel är klassen `Kortlek` en modell för en kortlek med 52 kort. Den innehåller hjälpmetoden `__återställ_kortlek`, som skapar en ny blandad kortlek. Den privata metoden anropas för närvarande endast i konstruktörsmetoden, så implementationen skulle kunna placeras direkt i konstruktören. Att använda en separat metod gör dock koden mer lättläst och gör det också möjligt att komma åt funktionaliteten senare i andra metoder om det behövs. ```python from random import shuffle @@ -100,7 +100,7 @@ class Korttipakka: return kasi ``` -Seuraava koodi testaa luokkaa: +Låt oss testa klassen: ```python korttipakka = Korttipakka() @@ -110,7 +110,7 @@ kasi2 = korttipakka.jaa(5) print(kasi2) ``` -Ohjelma tulostaa esimerkiksi +Eftersom händerna är slumpmässigt genererade, är följande endast ett exempel av det som kunde utskrivas: @@ -119,7 +119,7 @@ Ohjelma tulostaa esimerkiksi -Piilotettuja metodeja tarvitaan yleensä harvemmin kuin piilotettuja attribuutteja. Metodi kannattaa piilottaa, jos asiakas ei tarvitse siihen suoraa pääsyä, ja varsinkin silloin, jos on todennäköistä, että asiakas voisi sotkea olion sisäisen eheyden metodia kutsumalla. +Privata metoder är i allmänhet mindre vanliga än privata attribut. En tumregel är att en metod ska döljas när klienten inte har något behov av att direkt komma åt den. Detta är särskilt fallet när det är möjligt att klienten kan påverka objektets integritet negativt genom att anropa metoden. @@ -156,4 +156,3 @@ print(tili.saldo) - diff --git a/data/osa-9/5-staattiset-piirteet.md b/data/osa-9/5-staattiset-piirteet.md index 7ae763091..f08b6fc90 100644 --- a/data/osa-9/5-staattiset-piirteet.md +++ b/data/osa-9/5-staattiset-piirteet.md @@ -1,28 +1,28 @@ --- path: '/osa-9/5-staattiset-piirteet' -title: 'Staattiset piirteet' +title: 'Klassattribut' hidden: false --- - + -Tämän osion jälkeen: +Efter den här delen -- Ymmärrät käsitteet luokkamuuttuja ja luokkametodi -- Tiedät miten staattiset piirteet eroavat olioiden piirteistä -- Osaat lisätä staattisia piirteitä omiin luokkiin +- Är du bekant med begreppen klassvariabel och klassmetod +- Vet du hur statiska egenskaper skiljer sig från egenskaper hos instanser +- Kommer du att kunna lägga till statiska egenskaper i dina egna klasser -Olio-ohjelmoinnissa puhutaan _piirteistä_. Näillä tarkoitetaan olion ominaisuuksia: luokan sisälle kirjoitettuja metodeja ja luokassa määriteltyjä muuttujia. +Objektens egenskaper är ett centralt begrepp inom objektorienterad programmering. Termen egenskaper (eng. traits) omfattar de metoder och variabler som definieras i klassdefinitionen. -Tähän mennessä olemme käsitelleen _olioiden piirteitä_ eli oliometodeita ja attribuutteja. Olio-ohjelmointiin kuuluvat kuitenkin myös _luokan piirteet_, joita kutsutaan usein myös _staattisiksi piirteiksi_. Myös käsitettä _luokkamuuttuja_ käytetään. +Hittills har vi mest behandlat egenskaper hos objekt. Dessa inkluderar de metoder och attribut som är tillgängliga i alla instanser av en klass. Faktum är att klasser i sig också kan ha egenskaper, som ibland kallas statiska egenskaper, eller mer specifikt klassvariabler och klassmetoder. -## Luokkamuuttujat +## Klassvariabler -Kuten on aiemmin opittu, jokaisella oliolla on omat itsenäiset arvonsa attribuuteille. Attribuuttien lisäksi luokassa voidaan määritellä _luokkamuuttujia_ eli staattisia muuttujia. Luokkamuuttujalla tarkoitetaan muuttujaa, jota käytetään luokan kautta eikä luokasta muodostettujen olioiden kautta. Luokkamuuttujalla on yksi yhteinen arvo riippumatta siitä, montako oliota luokasta muodostetaan. +Varje instans av en klass har sina egna specifika värden för varje attribut som definieras i klassen, som vi har sett i exemplen i de tidigare avsnitten. Men vad händer om vi vill ha data som delas mellan de olika instanserna? Här kommer klassvariablerna in i bilden, även kallade statiska variabler. En klassvariabel är en variabel som nås via själva klassen, inte via de instanser som skapas baserat på klassen. Vid varje given tidpunkt under exekveringen av programmet har en klassvariabel ett enda värde, oavsett hur många instanser av klassen som skapas. -Luokkamuuttujan määrittely eroaa attribuutista siinä, että se määritellään ilman `self`-aluketta. Jos luokkamuuttujaa halutaan käyttää koko luokassa ja mahdollisesti luokan ulkopuoleltakin, se tulee määritellä metodien ulkopuolella. +En klassvariabel deklareras utan `self`-prefixet och vanligtvis utanför varje metoddefinition eftersom den ska kunna nås var som helst inom klassen eller till och med utanför klassen. ```python class Korkotili: @@ -43,9 +43,9 @@ class Korkotili: return self.__saldo ``` -Koska yleiskorko on määritelty luokassa eikä metodin sisällä eikä sen alustuksessa ole käytetty `self`-aluketta, se on luokkamuuttuja. +Eftersom variabeln `allman_ranta` definieras inom klassen men utanför alla metoddefinitioner och inte använder prefixet `self`, är det en klassvariabel. -Luokkamuuttujaan viitataan luokan nimen avulla, esimerkiksi näin: +En klassvariabel nås via klassens namn, till exempel så här: ```python # Yleiskorko on olioista riippumaton @@ -64,9 +64,9 @@ Yleiskorko on 0.03 -Luokkamuuttujiin viitataan siis luokan nimen avulla, esimerkiksi `Korkotili.yleiskorko`, ja oliomuuttujiin eli attribuutteihin olion nimen avulla `tili.saldo`. Oliomuuttujiin voi luonnollisesti viitata vasta, kun luokasta on muodostettu olio. +Klassvariablerna nås alltså via klassens namn, till exempel `Sparkonto.allman_ranta`, medan instansvariablerna nås via objektvariabelns namn, till exempel `konto.saldo`. En instansvariabel existerar naturligtvis bara när en instans av klassen har skapats, men en klassvariabel är tillgänglig överallt och vid varje tidpunkt då klassen i sig är tillgänglig. -Luokkamuuttujaa on kätevä käyttää, kun halutaan tallentaa arvoja, jotka on jaettu kaikkien olioiden kesken. Edellisessä esimerkissä oletetaan, että kaikilla pankkitileillä on sama yleiskorkoprosentti, jonka lisäksi tilille voidaan erikseen määrittää oma korkoprosenttinsa. Yleiskorkokin voi muuttua, mutta muutos vaikuttaa kaikkiin luokasta muodostettuihin olioihin: +Klassvariabler är användbara när det finns behov av värden som delas av alla instanser av klassen. I exemplet ovan antog vi att den totala räntan för alla sparkonton består av två komponenter: den allmänna räntan delas av alla konton, men varje konto har också sin egen ränta i en instansvariabel. Den allmänna räntan kan också förändras, men förändringen kommer då att påverka alla instanser av klassen lika mycket. ```python class Korkotili: @@ -118,9 +118,9 @@ Yleiskorko: 0.1 -Kun yleiskorko nousee, kaikkien luokasta määriteltyjen tilien kokonaiskorko nousee. Huomaa, että kokonaiskorko on määritelty havainnointimetodiksi, vaikkei vastaavaa attribuuttia olekaan suoraan määritelty. Metodi palauttaa tilin koron ja yleiskoron summan. +När den allmänna räntan ändras, ändras den totala räntan för alla instanser av klassen. Som du kan se ovan är det möjligt att lägga till en getter-metod med `@property`-dekoratorn även om det inte finns ett attribut med samma namn i klassen. Denna metod returnerar summan av den allmänna räntan och den kontospecifika räntan. -Tarkastellaan vielä toista esimerkkiä. Luokassa `Puhelinnumero` on maatunnukset tallennettuna sanakirjaan. Lista maatunnuksista on yhteinen kaikille luokasta luoduille puhelinnumero-olioille, koska maatunnus saman maan puhelinnumeroille on aina sama. +Låt oss ta en titt på ett annat exempel. Klassen `TelefonNummer` används för att definiera ett enda telefonnummer, men den innehåller också några landskoder i en ordbok. Denna ordbok är en klassvariabel och delas därför av alla instanser av klassen, eftersom landskoden för telefonnummer från ett och samma land alltid är densamma. ```python class Puhelinnumero: @@ -148,9 +148,9 @@ print(paulan_nro.puhelinnumero) -Kun puhelinnumero-olio luodaan, tallennetaan nimen ja numeron lisäksi maa. Kun numero haetaan havainnointimetodilla, haetaan numeron eteen maatunnus luokkamuuttujasta olion attribuuttiin tallennetun maatiedon avulla. +Varje TelefonNummer-objekt innehåller namnet på ägaren, själva numret och det land där telefonnumret finns. När attributet som innehåller telefonnumret nås med getter-metoden hämtas lämplig landskod från klassvariabelns ordbok baserat på landattributet, och resultatet prefixeras till numret. -Esimerkkiluokka on toiminnallisuudeltaan melko vajavainen. Katsotaan vielä, miltä näyttäisi parempi toteutus, jossa on havainnointi- ja asetusmetodit eri attribuuteille: +Implementeringsexemplet ovan är inte särskilt funktionellt i övrigt. I följande exempel har vi lagt till getter och sättare för alla attribut: ```python class Puhelinnumero: @@ -239,15 +239,15 @@ Muuta toiminnallisuutta ei tarvitse toteuttaa. -## Luokkametodit +## Klassmetoder -Luokkametodi eli staattinen metodi on luokassa oleva metodi, jota ei ole sidottu mihinkään luokasta muodostettuun olioon. Niinpä luokkametodia voi kutsua ilman, että luokasta muodostetaan oliota. +En klassmetod, även kallad statisk metod, är en metod som inte är knuten till någon enskild instans av klassen. En klassmetod kan anropas utan att några instanser av klassen skapas. -Luokkametodit ovat yleensä työkalumetodeja, jotka liittyvät jotenkin luokkaan mutta joita on tarkoituksenmukaista kutsua ilman olion muodostamista. Luokkametodit ovat yleensä julkisia, jolloin niitä voidaan kutsua sekä luokan ulkopuolelta että luokan ja siitä muodostettujen olioiden sisältä. +Klassmetoder är vanligen verktyg som har något att göra med klassens syfte, men som är fristående i den meningen att det inte ska vara nödvändigt att skapa instanser av klassen för att kunna anropa dem. Klassmetoder är vanligtvis offentliga, så att de kan anropas både utanför klassen och inom klassen, inklusive inom instanser av klassen. -Luokkametodi merkitään annotaatiolla `@classmethod` ja sen ensimmäinen parametri on aina `cls`. Tunnistetta `cls` käytetään samaan tapaan kuin tunnistetta `self`, mutta erotuksena on, että `cls` viittaa luokkaan ja `self` viittaa olioon. Kummallekaan parametrille ei anneta kutsuessa arvoa, vaan Python tekee sen automaattisesti. +En klassmetod definieras med annotationen `@classmethod`. Den första parametern är alltid `cls`. Variabelnamnet `cls` liknar `self`-parametern. Skillnaden är att `cls` pekar på klassen medan `self` pekar på en instans av klassen. Ingen av parametrarna ingår i argumentlistan när funktionen anropas, utan Python fyller i lämpligt värde automatiskt. -Esimerkiksi luokassa `Rekisteriote` voisi olla staattinen metodi, jolla voidaan tarkistaa, onko annettu rekisteritunnus oikeamuotoinen. Metodi on staattinen, jotta tunnuksen voi tarkastaa myös ilman, että luodaan uutta oliota luokasta: +I följande exempel har vi en klass som modellerar fordonsregistreringar. Klassen `Registration` innehåller en statisk metod för att kontrollera om en registreringsskylt är giltig. Metoden är en statisk klassmetod eftersom det är användbart att kunna kontrollera om en registreringsskylt är giltig redan innan ett enda Registration-objekt har skapats: ```python class Rekisteriote: @@ -305,7 +305,7 @@ Tämä on validi tunnus! -Rekisteriotteen oikeellisuuden voi tarkistaa kutsumalla metodia (esimerkiksi `Rekisteriote.rekisteritunnus_kelpaa("xyz-789"))`) ilman, että muodostaa luokasta oliota. Samaa metodia kutsutaan myös uutta oliota muodostaessa luokan konstruktorista. Huomaa kuitenkin, että myös tässä kutsussa viitataan metodiin luokan nimen avulla eikä `self`-tunnisteella! +Giltigheten för en registreringsskylt kan kontrolleras även utan att skapa en enda instans av klassen, t.ex. med `Registration.nummer_plat_giltig("xyz-789")`. Samma metod anropas i klassens konstruktor. OBS: även inom konstruktören är denna metod åtkomlig via klassens namn, inte `self`! diff --git a/data/osa-9/6-lisaa-esimerkkeja.md b/data/osa-9/6-lisaa-esimerkkeja.md index 36f0e2236..3b06c22ee 100644 --- a/data/osa-9/6-lisaa-esimerkkeja.md +++ b/data/osa-9/6-lisaa-esimerkkeja.md @@ -1,19 +1,19 @@ --- path: '/osa-9/6-lisaa-esimerkkeja' -title: 'Lisää esimerkkejä' +title: 'Fler exempel med klasser' hidden: false --- - + -Tämän osion jälkeen +Efter den här delen -- Tunnet lisää esimerkkejä luokista ja olioista -- Osaat käyttää parametrien oletusarvoja metodeissa +- Är du bekant med några fler exempel på klasser och objekt +- Kan du använda standardvärden för parametrar i dina metoder -Tarkastellaan seuraavaksi esimerkkiä, joka muodostuu kahdesta luokasta. Luokka `Piste` mallintaa yhtä pistettä kaksiulotteisessa koordinaatistossa ja luokka `Jana` kahden pisteen välistä janaa. Luokkien toiminta on kommentoitu koodiin. +Följande exempel består av två klasser. Klassen `Punkt` är en modell för en punkt i ett tvådimensionellt rum. Klassen `Linje` är en modell för ett linjesegment mellan två punkter. Koden nedan är kommenterad, läs gärna kommentarerna för att förstå hur klasserna fungerar. ```python import math @@ -99,15 +99,15 @@ print(jana) -## Parametrien oletusarvot +## Standardvärden för parametrar -Pythonissa mille tahansa parametrille voidaan asettaa oletusarvo. Oletusarvoja voidaan käyttää sekä funktioiden että metodien parametreissa. +I Python-programmering kan du i allmänhet ange ett standardvärde för alla parametrar. Standardvärden kan användas i både funktioner och metoder. -Jos parametrille on annettu oletusarvo, sille ei ole pakko antaa arvoa kutsuttaessa. Jos arvo annetaan, se syrjäyttää oletusarvon, ja jos arvoa ei anneta, käytetään oletusarvoa. +Om en parameter har ett standardvärde behöver du inte inkludera ett värde som ett argument när du anropar funktionen. Om ett argument anges ignoreras standardvärdet. Om inte, används standardvärdet. -Oletusarvot ovat usein hyödyllisiä konstruktoreissa: jos on oletettavaa, ettei tiettyä tietoa ole aina olemassa oliota luodessa, on parempi antaa sille vakioarvo konstruktorissa kuin antaa tämä asiakkaan huoleksi. Tämä on asiakkaalle helpompaa ja myös ylläpitää olion sisäistä eheyttä, kun voidaan esimerkiksi olla varmoja, että "tyhjä" arvo on aina samanlainen (muuten se voisi olla esimerkiksi merkkijono `""`, arvo `None` tai merkkijono `"ei asetettu"`). +Default-värden används ofta i konstruktörer. Om man kan förvänta sig att all information inte är tillgänglig när ett objekt skapas är det bättre att inkludera ett standardvärde i definitionen av konstruktörsmetoden än att tvinga klienten att ta hand om problemet. Detta gör det enklare att använda klassen ur klientens synvinkel, men det säkerställer också objektets integritet. Med ett fastställt standardvärde kan vi t.ex. vara säkra på att ett "tomt" värde alltid är detsamma, såvida inte klienten specifikt vill ange något annat. Om ett standardvärde inte anges är det upp till kunden att tillhandahålla ett "tomt" värde. Det kan t.ex. vara en tom sträng `""`, det speciella tomma objektet `None` eller strängen `"inte angivet"`. -Tarkastellaan esimerkkinä luokkaa, joka mallintaa opiskelijaa. Pakollisia kenttiä luodessa ovat opiskelijanumero ja nimi ja näistä opiskelijanumeroa ei pysty myöhemmin muuttamaan. Opintopisteet ja muistiinpanot voi halutessaan antaa oliota luodessa, mutta niille on myös asetettu oletusarvot. Luokan toiminta on kommentoitu suoraan ohjelmakoodin yhteyteen. +Låt oss ta en titt på ännu en klass som representerar en student. När ett nytt Student-objekt skapas måste klienten ange ett namn och ett studentnummer. Studentnumret är privat och ska inte ändras i efterhand. Dessutom har ett Student-objekt attribut för studiepoäng och anteckningar, vilka har standardvärden som anges i konstruktorn. Nya värden kan skickas som argument till konstruktören, men de kan också utelämnas så att standardvärdena används istället. Titta gärna på kommentarerna i koden för att bättre förstå vad varje metod gör. ```python class Opiskelija: @@ -203,9 +203,9 @@ Opiskelija Onerva Opiskelija (98765): -Huomaa, että attribuutille opiskelijanumero ei ole määritelty asetusmetodia, koska ideana on, että opiskelijanumero ei voi muuttua. +OBS: Det finns ingen sättar-metod för attributet `student_nummer` eftersom det inte är meningen att studentnumret ska ändras. -Parametrien oletusarvojen käyttöön liittyy kuitenkin eräs huomattavan iso "mutta" joka ilmenee seuraavasti esimerkistä: +Det finns en ganska betydande hake när man använder standardvärden för parametrar. Följande exempel som modellerar ännu en typ av student kommer att belysa detta mer: ```python class Opiskelija: @@ -235,7 +235,7 @@ print(opiskelija2.tehdyt_kurssit) -Huomataan siis, että kurssisuorituksen lisääminen Ollille muuttaa myös Outin kurssisuorituksia. Ilmiö johtuu siitä, että Python uudelleenkäyttää oletusarvoa. Yllä oleva tapa luoda opiskelijat vastaa siis seuraavaa koodia: +Om du lägger till slutförda kurser i Sallys lista läggs dessa kurser också till i Sassys lista. Faktum är att dessa två är exakt samma lista, eftersom Python återanvänder referensen som lagras i standardvärdet. Att skapa de två nya Student-objekten i exemplet ovan är likvärdigt med följande: ```python kurssit = [] @@ -243,7 +243,7 @@ opiskelija1 = Opiskelija("Olli Opiskelija", kurssit) opiskelija2 = Opiskelija("Outi Opiskelija", kurssit) ``` -Tästä johtuen parametrin oletusarvona ei koskaan tulisi käyttää monimutkaisempia tietorakenteita kuten listoja. Korjattu versio luokan `Opiskelija` konstruktorista on seuraava: +Standardvärdena för parametrar bör aldrig vara instanser av mer komplicerade, föränderliga datastrukturer, t.ex. listor. Problemet kan kringgås genom att göra följande ändringar i konstruktorn för klassen `Student`: ```python class Opiskelija: @@ -276,9 +276,9 @@ print(opiskelija2.tehdyt_kurssit) -## Loppuhuipennus +## Den stora finalen -Vaikka seuraava tehtävä on tässä luvussa, et tarvitse tehtävän ratkaisemiseen mitään muuta kun luvussa [Oliot attribuuttina](/osa-9/2-oliot-attribuuttina) esiteltyjä tekniikoita. Tehtävä on käytännössä hyvin samanlainen kuin tuon luvun tehtävät [lahjapakkaus](/osa-9/2-oliot-attribuuttina#programming-exercise-lahjapakkaus) ja [huoneen lyhin](/osa-9/2-oliot-attribuuttina#programming-exercise-huoneen-lyhin). +Fastän följande övning avslutar den här delen av materialet, så har de tekniker som krävs för att lösa den redan behandlats i avsnittet som heter [Objekt som attribut](/osa-9/2-oliot-attribuuttina). Du behöver inte använda `@property`-dekoratorn eller standardvärden för parametrar i den här övningen. Den här övningen är mycket lik övningarna [en presentask](/osa-9/2-oliot-attribuuttina#programming-exercise-lahjapakkaus) och [den kortaste personen i rummet](/osa-9/2-oliot-attribuuttina#programming-exercise-huoneen-lyhin). @@ -531,4 +531,3 @@ Tiiliskivi (4 kg) Vastaa lopuksi osion loppukyselyyn: -