Luku 3. Listankäsittelyä. 3.1 Listat



Samankaltaiset tiedostot
Tämän vuoksi kannattaa ottaa käytännöksi aina kirjoittaa uuden funktion tyyppi näkyviin, ennen kuin alkaa sen määritemää kirjoittamaan.

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Tyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

TIEA341 Funktio-ohjelmointi 1, kevät 2008

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Laajennetaan vielä Ydin-Haskellia ymmärtämään vakiomäärittelyt. Määrittely on muotoa

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Abstraktit tietotyypit. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Jatkeet. TIES341 Funktio ohjelmointi 2 Kevät 2006

Ohjelmoinnin peruskurssi Y1

5.5 Jäsenninkombinaattoreista

TIEA341 Funktio-ohjelmointi 1, kevät 2008

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Ohjelmoinnin perusteet Y Python

Listarakenne (ArrayList-luokka)

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Matriisit ja vektorit Matriisin käsite Matriisialgebra. Olkoon A = , B = Laske A + B, , 1 3 3

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Tyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet. TIES341 Funktio-ohjelmointi 2 Kevät 2006

Ohjelmoinnin perusteet Y Python

811312A Tietorakenteet ja algoritmit II Perustietorakenteet

18. Abstraktit tietotyypit 18.1

Algoritmit 1. Luento 3 Ti Timo Männikkö

Laiska laskenta, korekursio ja äärettömyys. TIEA341 Funktio ohjelmointi Syksy 2005

Sisällys. 18. Abstraktit tietotyypit. Johdanto. Johdanto

Taas laskin. TIES341 Funktio ohjelmointi 2 Kevät 2006

Ohjelmoinnin peruskurssien laaja oppimäärä

Algoritmit 1. Demot Timo Männikkö

Tietueet. Tietueiden määrittely

Java-kielen perusteet

JavaScript alkeet Esimerkkikoodeja moniste 2 ( Metropolia)

JFO: Johdatus funktionaaliseen ohjelmointiin

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Java-kielen perusteet

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Ohjelmoinnin peruskurssien laaja oppimäärä

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

Algoritmit 2. Luento 7 Ti Timo Männikkö

Esimerkki: Laskin (alkua) TIEA341 Funktio ohjelmointi 1 Syksy 2005

Tieto- ja tallennusrakenteet

Ohjelmoinnin perusteet Y Python

Funktionimien kuormitus. TIES341 Funktio ohjelmointi 2 Kevät 2006

Algoritmit 1. Luento 5 Ti Timo Männikkö

Tietojenkäsittelyteorian alkeet, osa 2

Algoritmit 1. Luento 4 Ke Timo Männikkö

2.4 Normaalimuoto, pohja ja laskentajärjestys 2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

8.5 Takarekursiosta. Sanoimme luvun 8.3 foldl -esimerkissämme että

PHP tehtävä 3 Atte Pekarinen TIKT13A

Koe ma 1.3 klo salissa A111, koeaika kuten tavallista 2h 30min

Muuttujien roolit Kiintoarvo cin >> r;

Ohjelmoinnin perusteet Y Python

Tietotyypit ja operaattorit

ALGORITMIT 1 DEMOVASTAUKSET KEVÄT 2012

Algoritmit 2. Luento 14 Ke Timo Männikkö

Demo 7 ( ) Antti-Juhani Kaijanaho. 9. joulukuuta 2005

Algoritmit 1. Luento 12 Ke Timo Männikkö

Ohjelmoinnin peruskurssien laaja oppimäärä

Luku 4. Tietorakenteet funktio-ohjelmoinnissa. 4.1 Äärelliset kuvaukset

Harjoitus 1 -- Ratkaisut

Geneeriset tyypit. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

ITKP102 Ohjelmointi 1 (6 op)

Tietorakenteet ja algoritmit

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

A TIETORAKENTEET JA ALGORITMIT

Turingin koneet määritteli sen laitelähtöisesti: Laskenta etenee suorittamalla yksinkertaisia käskyjä siten kuin sen ohjelma

Ohjelmoinnin peruskurssien laaja oppimäärä

7/20: Paketti kasassa ensimmäistä kertaa

Racket ohjelmointia osa 2. Tiina Partanen Lielahden koulu 2014

Ydin-Haskell Tiivismoniste

MATEMATIIKAN LATOMINEN LA T EXILLA, OSA 1

Ohjelmoinnin perusteet Y Python

Tämä tarina on Fibonaccin lukujen ongelman alkuperäinen muotoilu.

SQL-perusteet, SELECT-, INSERT-, CREATE-lauseet

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Algoritmit 1. Luento 12 Ti Timo Männikkö

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

ITKP102 Ohjelmointi 1 (6 op)

Algoritmit 1. Demot Timo Männikkö

List-luokan soveltamista. Listaan lisääminen Listan läpikäynti Listasta etsiminen Listan sisällön muuttaminen Listasta poistaminen Listan kopioiminen

VINKKI: Katso Kentät Muistioon -painikkeella, mikä on taulukon nimen oikea kirjoitusasu.

Ohjelmointi 1 Taulukot ja merkkijonot

Ohjelmointiharjoituksia Arduino-ympäristössä

A TIETORAKENTEET JA ALGORITMIT

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

Ohjelmoinnin jatkokurssi, kurssikoe

etunimi, sukunimi ja opiskelijanumero ja näillä

Ohjelmoinnin perusteet Y Python

FUNKTIONAALIANALYYSIN PERUSKURSSI Johdanto

fix e e (fix e). fix = λf.(λx.f (x x)) (λx.f (x x)) (9)

1 Erilaisia tapoja järjestää

Ohjelmoinnin perusteet Y Python

Luento 5. Timo Savola. 28. huhtikuuta 2006

Transkriptio:

Luku 3 Listankäsittelyä Funktio-ohjelmoinnin tärkein yksittäinen tietorakenne on lista. Listankäsittely on paitsi käytännöllisesti oleellinen aihe, se myös valaisee funktio-ohjelmoinnin ideaa. 3.1 Listat Lista on algebrallinen tietotyyppi, jolla on yleisyytensä vuoksi erityinen syntaksi. Tyyppiä α olevien alkioiden listan tyyppi on [α]. Tyypillä on kaksi koostinta: [] edustaa tyhjää listaa ja (:) :: α [α] [α] lisää olemassaolevan listan eteen uuden alkion. Koostinoperaattori (:) assosioi oikealle, joten 2 : 3 : 4 : 5 : [] on sama kuin 2 : (3 : (4 : (5 : []))). Äärellinen lista voidaan kirjoittaa paitsi koostimillaan myös luettelemalla sen alkiot hakasulkeiden sisällä pilkuilla ne toisistaan erottaen: [3, 9, 2, 7, 5] tarkoittaa 3 : 9 : 2 : 7 : 5 : []. Lista on persistentti tietorakenne. Tämä tarkoittaa, että listaa sinänsä ei voi muuttaa, vaan listan muuttaminen oikeasti tuottaa uuden listan. Näin alkuperäinen lista on yhtä lailla käytettävissä kuin uusikin. Muutettu ja alkuperäinen lista jakavat yhteisen loppuosan: jos muutos on uuden alkion lisääminen, muutettu lista koostuu oikeasti uudesta alkiosta ja alkuperäisestä listasta; jos muutos on vasemmanpuolimmaisen listan poistaminen, tilanne on sama kuin lisäämisessä, mutta alkuperäinen ja muutettu lista vaihtavat rooleja. Kaikki muut listaoperaatiot ovat näiden kahden operaation yhdistelmiä. Listojen yhtäsuuruus määritellään niin, että listat ovat yhtäsuuret silloin ja vain silloin kun niissä on samat alkiot (saman alkion mahdolliset kaksoiskappaleet mukaanlukien) samassa järjestyksessä: instance Eq a => Eq [a] where [] == [] = True 31

32 LUKU 3. LISTANKÄSITTELYÄ [] == (_:_) = False (_:_) == [] = False (x:xs) == (y:ys) = x == y && xs == ys Tämä määrittely tarkoittaa, että listojen yhtäsuuruusvertailu on mahdollista vain, jos alkiotyypille on yhtäsuuruusvertailu määritelty. Tosin yhdessä erityistapauksessa tätä ei tarvita, nimittäin listan tyhjyyden tarkastamisessa: null :: [α] Bool null [] = True null (_ : _) = False Funktio null kuuluu Haskellin varuskirjastoon. Kahden listan yhdistäminen (concatenation) on hyödyllinen perusoperaatio. Haskellin varuskirjastossa se on määritelty operaattoriksi (++): (++) :: [α] [α] [α] [] ++ ys = ys (x : xs) ++ ys = x : (xs ++ ys) Tämä määritelmä toimii vasemman argumentin hahmonsovituksella. Koska molemmat hahmot ovat koostinhahmoja, ei sovi niistä mihinkään. Tämän vuoksi ++ ys =. Oikeaa argumenttia sovitetaan sen sijaan koko ajan muuttujaan, joten xs ++ = pitää paikkansa vain, jos xs =. Esimerkiksi GHCi käyttäytyy näin 1 : Prelude> let bot = bot Prelude> [2,3,4]++bot [2,3,4*** Exception: <<loop>> Lista [2, 3, 4] ++ on siis osittaislista (partial list) 2 : 3 : 4 :. GHCi tunnistaa noin yksinkertaisesti määritellyn bot:n ja antaa siitä virheilmoituksen. Yhdistämisoperaattorin sukulaisfunktio on varuskirjastoon concat, joka yhdistää listaan kuuluvat listat yhteen: concat :: [[α]] [α] concat [] = [] concat (x : xs) = x ++ concat xs 1. GHCi:lle voi antaa vakiomäärittelyjä, funktiosidontoja ja hahmosidontoja let-avainsanan jälkeen.

3.1. LISTAT 33 Esimerkki 19 Yhdistelläänpä merkkijonolistoja ja merkkijonoja: Prelude> concat [["Kissa", "Koira"], [], ["Hiiri"], ["Koira", "Hiiri", "Kissa"]] ["Kissa","Koira","Hiiri","Koira","Hiiri","Kissa"] Prelude> concat ["Kissa", "Koira", "Hiiri", "Koira", "Hiiri", "Kissa"] "KissaKoiraHiiriKoiraHiiriKissa" Haskellin varuskirjastoon kuuluu myös funktio reverse, joka olisi helpointa määritellä näin: reverse :: [α] [α] reverse [] = [] reverse (x : xs) = reverse xs ++[x] Valitettavasti tämä on tehoton määritelmä; sen asymptoottinen aikavaativuus on O(n 2 ), missä n on parametrilistan pituus. Tämä johtuu siitä, että listan loppuun lisääminen on O(n)-operaatio, ja tämä tehdään n kertaa. Fiksumpi määritelmä on seuraava: reverse :: [α] [α] reverse = let f a [] = a f a (x : xs) = f (x : a) xs in f [] Tämän asymptoottinen aikavaativuus on O(n), sillä kaikki käytetyt perusoperaatiot ovat vakioaikaisia ja lista käydään läpi kerran. Tämä määritelmä on lisäksi häntärekursiivinen (tail-recursive), eli siinä rekursiivinen kutsu on viimeinen asia, mitä funktio tekee ennen palaamistaan. Häntärekursion tekee käytännölliseksi se, että se on oikeastaan rekursioksi naamioitu silmukka, ja hyvä (funktiokielen) kääntäjä käsittelee sitä sellaisena. Häntärekursio tarvitsee näin vain vakiomäärän pinoa, eikä häntärekursiivinen funktio siis voi aiheuttaa pinon ylivuotoa. Listan pituuden laskee varuskirjaston funktio length 2 : length :: [α] Int length [] = 0 length (_ : xs) = 1 + length xs 2. Tyyppi Int on lukualueeltaan rajattu kokonaislukutyyppi. Se on toteutettavissa suoraan konesanoilla ja konekielen laskukäskyillä, joten se on rajoittamattoman lukualueen kokonaislukutyyppiä Integer tehokkaampi.

34 LUKU 3. LISTANKÄSITTELYÄ Listan pituuden voi laskea, vaikka listan alkioina olisikin yksi tai useampi. Listan alkioilla ei siis ole varsinaisesti väliä: length [, ] = length ( : : []) = 2. Sen sijaan osittaislistoilla ei ole pituutta: length (2 : 3 : 4 : ) =. Listatyypin koostimet jo sinänsä jakavat listat kahteen osaan, päähän (head) ja häntään (tail). Pää on listan ensimmäinen alkio ja häntä on listan loppuosa. Varuskirjastossa on funktiot näiden hakemiseksi toisinaan tällaiset funktiot ovat kätevämpiä kuin saman asian hoitavat hahmonsovitukset casella tai vastaavalla: head :: [α] α head (x : _) = x tail :: [α] [α] head (_ : xs) = xs Nämä operaatiot ovat luonnollisestikin vakioaikaisia. Sen sijaan niiden peilioperaatiot last = head reverse ja init = reverse tail reverse ovat asymptoottiselta aikavaativuudeltaan lineaarisia operaatioita. Huomautus 3 Edellä käytetty pisteoperaattori ( ) on funktioidenyhdistämisoperaattori, jota matemaattisessa analyysissä tavataan merkitä :llä. Määritellään siis ( ) :: (β γ) (α β) (α γ) (f g) x = f (g x) Koneelle ( ) kirjoitetaan pisteenä: (.). Funktio-ohjelmoinnissa suositaan edellisten kaltaisia määritelmiä, joissa funktioiden parametrit eivät näy lainkaan vaan funktiot rakennetaan muista funktioista käyttämällä kombinaattoreita (combinators) eli funktioita, jotka yhdistelevät toisia funktioita tuottaen ulos uusia funktioita. Pisteoperaattori ( ) on esimerkki yksinkertaisesta kombinaattorista. Ohjelmointityyliä, jossa funktiot määritellään toisten funktioiden avulla niin, että funktioiden varsinaisia parametreja ei määritelmässä esiinny, sanotaan pisteettömäksi ( pointless tai point-free) tyyliksi. Tässä pisteettömyyden piste viittaa ajatukseen, jossa funktiot kuvaavat lähtöavaruuden pisteitä maaliavaruuden pisteiksi. Huomautus 4 Edellä annettu initin määritelmä on sikäli huono, että se käyttäytyy huonosti osittaislistoilla. Tämä on korjattavissa määrittelemällä se uu-

3.2. LISTA-AIHEISISTA TODISTUKSISTA 35 destaan pisteittäisesti: init :: [α] [α] init (x : []) = [] init (x : _) = x : init xs Varuskirjastoon kuuluu myös funktiot take ja drop, jotka ottavat tai tiputtavat pois, funktiosta riippuen, parametrina annetun määrän alkioita annetun listan alusta: take :: Int [α] [α] take _ [] = [] take n xs n < 0 = error "Negatiivinen taken parametri" n == 0 = [] otherwise = x : take (n 1)xs Lopuksi vielä esitellään varuskirjastoon kuuluva listan indeksointioperaattori: (!!) :: [α] Int α (x : xs)!! n n < 0 = error "Negatiivinen (!!):n parametri" n == 0 = x otherwise = xs!! n 3.2 Lista-aiheisista todistuksista Toisinaan on hyvä voida todistaa, että tietyllä funktiolla on tietty ominaisuus, ihan luotettavuuden parantamisen kannalta. Esimerkiksi voisi olla hyvä todistaa yhtälö (xs ++ ys) ++ zs = xs ++(ys ++ zs). Tällaiset todistukset etenevät parhaiten induktiolla. Listan yli tapahtuvassa induktiossa on kolme askelta; todistaakseen, että kaikilla listoilla on jokin ominaisuus, pitää todistaa seuraavat asiat: Perustapaus P( ) Todista, että ko. ominaisuus on (listatyyppisellä) pohjalla. Perustapaus P([]) Todista, että ko. ominaisuus on tyhjällä listalla. Induktioaskel Todista, että jos ko. ominaisuus on mielivaltaisella listalla xs niin silloin kaikilla (tyypiltään sopivilla) alkioilla x ko. ominaisuus on listalla x : xs.