Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 5.2: Olioita kaikkialla
Tästä sivusta:
Pääkysymyksiä: Mitä merkkijono-olioilla voi tehdä? Miten jäsennän vaikkapa tähtikatalogin sisältämiä merkkijonoja? Ai, merkkijonot ovat olioita — mitä kaikkia muita olioita on?
Mitä käsitellään? Useaa aiemmin käsiteltyä tietotyyppiä olio-ohjelmoinnin näkökulmasta.
Mitä tehdään? Luetaan ja tehdään pieniä tehtäviä.
Suuntaa antava työläysarvio:? Kaksi tai kolme tuntia.
Pistearvo: A60.
Oheismoduulit: Miscellaneous, Stars.
Muuta: Eräissä kohdissa on kaiuttimista tai kuulokkeista hyötyä. Pakolliset ne eivät ole.
Johdanto
Luvussa 2.1 otimme asiaksemme opetella olio-ohjelmointia, ja oletkin sittemmin luonut
monia luokkia ja olioita sekä käyttänyt valmiina annettuja. Samalla on osoittautunut,
että eräät jo tutuiksi tulleet Scalan perustyökalut ovat myös olioita, joilla on
hyödyllisiä metodeita: tällaisia ovat erimerkiksi vektorit ja puskurit (luku 4.2) ja
Option
it (luku 4.3).
Scala on huomattavan puhdasoppinen olio-ohjelmointikieli siinä mielessä, että Scalassa kaikki arvot ovat olioita ja noiden arvojen tyyppeihin liitetyt toiminnot metodeita.
Miten niin? Emmekö ole käyttäneet "irrallisia" funktioita kuten max
ja min
ja
laatineet sellaisia itsekin? Eiväthän kai ne olleet mitään olioiden metodeita? Ja eikö
olioiden lisäksi ole näitä Int
-arvoja ja Boolean
eja ja muita vastaavia, joilla on
operaattoreita eikä metodeita?
Katsotaan.
Luvut olioina
Int
-tietotyyppi edustaa kokonaislukuarvoja. Myös tämä Scalan valmis tietotyyppi on itse
asiassa luokka ja yksittäiset kokonaisluvut Int
-olioita, vaikka asiaa ei ole kurssilla
toistaiseksi näin muotoiltukaan.
On totta, ettei Int
ole aivan tavallinen luokka. Se on siinä mielessä erikoinen,
ettei siitä tarvitse luoda ilmentymiä erillisellä käskyllä, vaan uusia Int
-arvoja saa
ohjelmassa käyttöön yksinkertaisesti käyttämällä literaalimerkintää. Int
-luokka on
määritelty Scalan ydinpakkaukseen scala
, joka on kielen erottamaton osa. Joihinkin
tämän pakkauksen luokkiin liittyy "erikoisuuksia" kuten literaalimerkinnät.
Tästä huolimatta Int
on esimerkiksi siinä mielessä ihan tavallinen luokka, että se
määrittelee joukon metodeita, joita voi Int
-olioille kutsua. Alla on esimerkkejä.
val luku = 17luku: Int = 17 luku.toBinaryStringres0: String = 10001
Tässä kutsuttiin toBinaryString
-metodia, joka palauttaa kyseisen luvun kuvauksen
binaarilukuna eli kaksikantaisessa lukujärjestelmässä. Esimerkiksi kymmenkantainen
luku 17 on kaksikantaisena 10001.
Myös lukuliteraalille voi kutsua metodia:
123.toBinaryStringres1: String = 1111011
to
-metodi
Int
-olioilla on metodi to
, joka palauttaa luettelon luvuista kohdeolion (alla: 2
)
ja parametriolion (alla: 6
) väliltä.
val luvutKakkosestaKuutoseen = 2.to(6)luvutKakkosestaKuutoseen: Range.Inclusive = Range 2 to 6 luvutKakkosestaKuutoseen.toVectorres2: Vector[Int] = Vector(2, 3, 4, 5, 6)
Esimerkin Range
kattaa luvut 2, 3, 4, 5 ja 6
kasvavassa järjestyksessä, mikä näkyy erityisen
selvästi kun kopioimme sen sisällön vektoriin.
(Vektori vie enemmän muistia kuin Range
, koska se
varastoi jokaisen alkion erikseen kun taas Range
varastoi tiedon ensimmäisestä ja viimeisestä luvusta.)
to
-metodista vähän lisää alempana. Löydämme sille ja sen palautusarvoille kunnollista
hyötykäyttöä luvussa 5.6.
toDouble
-metodi
Tässä esimerkki kätevästä ja usein käytetystä Int
-olioiden metodista, joka palauttaa
kokonaislukua vastaavan Double
-arvon.
val jaettava = 100jaettava: Int = 100 val jakaja = 7jakaja: Int = 7 val kokonaislukutulos = jaettava / jakajakokonaislukutulos: Int = 14 val tarkempi = jaettava.toDouble / jakajatarkempi: Double = 14.285714285714286 val tulosDoubleksi = (jaettava / jakaja).toDoubletulosDoubleksi: Double = 14.0
Saisit toki samat tulokset ilman toDouble
-metodiakin: esim. 1.0 * jaettava / jakaja
.
Kuitenkin toDouble
-metodin käyttäminen ilmaisee suoremmin, mitä halutaan.
abs
, min
jne.
Int
-olioille on myös määritelty eräitä tuttuja matemaattisia toimintoja metodeina.
Esimerkiksi itseisarvon tai kahdesta luvusta pienemmän voi selvittää tähänkin tapaan:
-100.absres3: Int = 100 val luku = 17luku: Int = 17 luku.min(100)res4: Int = 17
Kokoelmien indekseistä
Kokoelmien indeksointiin on muitakin käytäntöjä, mutta useimmissa nykyaikaisissa ohjelmointikielissä indeksit alkavat nollasta, koska tästä on joitakin käytännön etuja. Wikipediassa on aiheesta artikkeli Zero-based numbering.
Should indices start at 0 or 1?My compromise of 0.5 was rejected without, I thought, proper consideration.
Operaattorit metodeina
Luvuilla on siis metodeitakin, mutta entä operaattorit? Olio-ohjelmoinnissahan olioita olisi tarkoitus käyttää "lähettämällä niille viestejä" eli kutsumalla niiden metodeita.
Int
-luokka on kuvattu Scala API -dokumentaatiossa tässä osoitteessa:
Dokumentaatiota selaamalla löytyvät muun muassa yllä mainitut metodit toDouble
, to
ja abs
. Samasta luettelosta löytyy koko liuta metodien kuvauksia, joissa metodien
niminä on tutunnäköisiä symboleita: !=
, *
, +
jne. (Muuta tuosta dokumentaatiosta
ei ole välttämätöntä ymmärtää tässä vaiheessa.)
Kokeillaan:
val luku = 10luku: Int = 10 luku.+(5)res5: Int = 15 (1).+(1)res6: Int = 2 10.!=(luku)res7: Boolean = false
Aritmeettiset laskutoimitukset ja vertailutkin on määritelty
metodeiksi Int
-luokkaan. Niinpä Int
-oliota voi pyytää niitä
suorittamaan tähän tapaan. Tulokset ovat tutut, mutta käskyjen
kirjoitustapa aiemmin käytettyä kömpelömpi.
Onko siis erikseen olemassa operaattori +
, jota käytetään esimerkiksi lausekkeessa
luku + toinen
, sekä metodi +
, jota käytetään lausekkeessa luku.+(toinen)
?
Ei, vaan kyse on samasta metodikutsusta kahdella eri tavalla kirjoitettuna. Scala-kieli
nimittäin sallii pisteen ja sulkeiden jättämisen pois metodikutsusta silloin, kun metodi
ei ota useampaa kuin yhden parametrin. Ilmaisut luku + toinen
ja luku.+(toinen)
tarkoittavat siis täsmälleen samaa asiaa. Operaattorit ovatkin metodeita!
Scalassa itse asiassa ei edes virallisesti ole operaattorin käsitettä. (Monissa muissa kielissä on.) Silti on usein kätevää ajatella tiettyjä Scala-metodeita — kuten juuri lukujen aritmeettisia metodeita — operaattoreina ja käyttää niitä ilman välimerkkejä.
Pistenotaatio vs. operaattorinotaatio
Voimme siis käyttää esimerkiksi metodia +
ilman pistettä ja parametrisulkeita.
Sama toimii myös muille yksiparametrisille metodeille. Esimerkiksi tutussa
Category
-luokassamme voisi yhtä hyvin olla kumpi tahansa seuraavista lausekkeista:
newExperience.chooseBetter(this.fave)
newExperience chooseBetter this.fave
Ensimmäistä kirjoitustapaa sanotaan joskus pistenotaatioksi (dot notation) ja jälkimmäistä operaattorinotaatioksi (operator notation tai infix operator notation).
Molemmilla notaatioilla on puolensa, ja valinta niiden välillä on osin makuasia.
Tässä kurssimateriaalissa operaattorinotaatiota käytetään erittäin säästeliäästi,
koska pistenotaatio toimii johdonmukaisesti kaikilla parametrimäärillä sekä koska
pistenotaatiossa kohdeolio ja parametrilausekkeet korostuvat selkeämmin ja notaatio tukee
siksi ohjelmoinnin peruskäsitteiden oppimista paremmin. Operaattorinotaatiota käytämme
lähinnä aritmetiikka-, vertailu- ja logiikkaoperaattorien yhteydessä sekä tietyissä
muissa yhteyksissä, joissa se on poikkeuksellisen luontevaa; esimerkiksi Int
-olion
to
-metodin kutsu lienee useimpien mielestä luontevampaa kirjoittaa 1 to 10
kuin
1.to(10)
.
Saat itse käyttää omissa ohjelmissasi kumpaa tapaa vain. Suosittelemme varsinkin ohjelmoinnin aloittelijoille kurssin käyttämää tapaa. Joka tapauksessa molemmat notaatiot pitää tuntea, jotta pystyy lukemaan muiden kirjoittamia Scala-ohjelmia. Kurssin ulkopuolisessa Scala-ohjelmoinnissa operaattorinotaation käyttö on yleisempää kuin kurssilla.
Totuusarvojen ja puskurien "operaattoreita"
Logiikkaoperaattorit (luku 5.1) ovat Boolean
-olioiden metodeita.
Puskurien "operaattorit" +=
ja -=
ovat metodeja nekin. Niitä voi kutsua operaattori-
tai pistenotaatiolla, joista edellinen lienee tässä tapauksessa kaikkien mielestä
mukavampi.
val puskuri = Buffer("eka", "toka", "kolmas")res8: Buffer[String] = ArrayBuffer(eka, toka, kolmas) puskuri += "neljäs"puskuri.+=("viides")
GoodStuff revisited
Aiemmissa luvuissa olet nähnyt esityksiä GoodStuff-ohjelman toiminnasta, joissa oliot viestivät keskenään. Esityksissä ei esimerkiksi puskureita tai lukuja näytetty olioina.
Kuten on käynyt ilmi, myös ne ovat olioita. Esimerkiksi
Experience
-olio kutsuu Int
-olion >
-metodia selvittääkseen
kumpi kahdesta kokonaisluvusta on suurempi (mikä määrää, kumpi
kokemuksista on parempi). Olio-ohjelman suoritus siis muodostuu
näiltäkin osin olioiden välisestä viestinnästä.
String
-oliot ja niiden metodit
Minkäänlaisena yllätyksenä ei enää tule se, että String
on luokka, merkkijonot sen
ilmentymiä ja merkkijono-operaattorit metodeita.
"kissa".+("kala")res9: String = kissakala
Vektori sisältää jonkin tyyppisiä alkioita kukin omalla indeksillään. Merkkijono sisältää
merkkejä kukin omalla indeksillään. Käsitteet ovat toisilleen sukua, ja on ymmärrettävää,
että merkkijonoille on määritelty monia samankaltaisia metodeita kuin vektoreille ja
puskureille. Tästä on monia esimerkkejä alla. Toisaalta merkkijonoilla on ihan omiakin,
nimenomaan merkkien käsittelyyn liittyviä metodeitaan. Seuraavat esimerkit ja pikkutehtävät
tutustuttavat eräisiin String
-metodeihin.
Merkkijonojen käsittely on ohjelmissa yleistä, ja monista alla esitellyistä metodeista on hyötyä tulevissa kurssin vaiheissa ja vielä tässä samassa luvussakin. Tarkoitus ei taaskaan ole opetella metodien yksityiskohtia ulkoa, koska riittävän usein tarvitut metodit jäävät kyllä harjoituksen myötä mieleen. Tärkeämpää on saada yleiskuva siitä, millaista kalustoa on tarjolla.
Merkkijonon pituus eli length
length
-metodi palauttaa merkkijonon pituuden eli sen sisältämien merkkien lukumäärän.
"kissa".lengthres10: Int = 5
Merkkijonosta luku: toInt
ja toDouble
Laaditaan kokeeksi pikkuohjelma, joka pyytää käyttäjää syöttämään luvun ja raportoi sitten syötteen numeroiden määrän sekä erään laskutoimituksen tuloksen. Ohjelman pitäisi toimia tekstikonsolissa näin:
Please enter an integer: 2022 Your number is 4 digits long. Multiplying it by its length gives 8088.
Ensimmäinen versio koodista on tässä:
import io.StdIn.*
@main def inputTest() =
val input = readLine("Please enter an integer: ")
val digits = input.length
println(s"Your number is $digits digits long.")
val multiplied = input * digits
println(s"Multiplying it by its length gives $multiplied.")
end inputTest
Tuo toteutus "toimii" näiden kahden esimerkkiajon tapaan:
Please enter an integer: 2022 Your number is 4 digits long. Multiplying it by its length gives 2022202220222022.
Please enter an integer: laama Your number is 5 digits long. Multiplying it by its length gives laamalaamalaamalaamalaama.
Käsittelemme syötettä merkkijonona, mikä onkin tarpeen, jos haluamme kutsua length
-metodia.
Merkkijonon kertominen luvulla ei kuitenkaan tuota haluttua tulosta. Tyydyttävää ei ole
sekään, että ohjelmamme hyväksyy minkä vain tekstinpätkän muka luvuksi.
Tässä tapauksessa — kuten lukemattomissa muissakin reaalimaailman ohjelmissa —
haluamme tulkita numeromerkkejä sisältävän merkkijonon lukuna, jotta sillä voidaan
laskea. Haluamme siis esimerkiksi muodostaa merkit '1'
, '0'
ja '5'
sisältävästä
String
-arvosta Int
-arvon 105
.
Suoraviivaisin ratkaisu on käyttää metodia toInt
(tai toDouble
):
val tekstiJossaLuku = "105"tekstiJossaLuku: String = 105 tekstiJossaLuku.toIntres11: Int = 105 tekstiJossaLuku.toDoubleres12: Double = 105.0
Sovelletaan ohjelmaamme:
import io.StdIn.*
@main def inputTest() =
val input = readLine("Please enter an integer: ")
val digits = input.length
println(s"Your number is $digits digits long.")
val multiplied = input.toInt * digits
println(s"Multiplying it by its length gives $multiplied.")
end inputTest
Ainoa muutos edelliseen on, että nyt tulkitsemme numeromerkit luvuksi kertolaskua varten.
Turvallisemmin: toIntOption
ja toDoubleOption
Äskeisessä ohjelmassa ongelmaksi jäi, että tieto voi puuttua käyttäjän virhesyötteen vuoksi. Vaikka pienissä kokeiluohjelmissa ei tuollaisilla erikoistilanteilla välttämättä olekaan väliä, muissa ohjelmissa usein on.
Puuttuvaa tietoa voi käsitellä mm. Option
-tyypin avulla. Yksi helppo tapa tehdä sellainen
on käyttää toInt
-metodin sijaan toIntOption
-metodia; on myös toDoubleOption
ja
muitakin vastaavia.
"100".toIntOptionres13: Option[Int] = Some(100) "sata".toIntOptionres14: Option[Int] = None "100.99".toDoubleOptionres15: Option[Double] = Some(100.99)
toIntOption
-tehtävä
Ota esiin äskeinen inputTest
-ohjelma Miscellaneous-moduulin pakkauksesta o1.inputs
.
Pelkkä toInt
-kutsun korvaaminen toIntOption
-kutsulla ei saa koodia toimimaan.
Päinvastoin: noin muokattua koodia ei voi edes ajaa. (Miksei?)
On tarpeen käsitellä eri tavoin tapaukset, joissa käyttäjän syöte on kelvollinen ja
kelvoton. Muokkaa ohjelmaa niin, että se toimii näiden kahden ajoesimerkin mukaisesti.
(Vinkki: käsittele tapaukset match
illä.)
Please enter an integer: 105 Your number is 3 digits long. Multiplying it by its length gives 315.
Please enter an integer: sataviis That is not a valid input. Sorry!
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tyhjät pois: trim
-metodi
trim
-metodi on myös usein kätevä jostakin ulkoisesta lähteestä luettua dataa
käsiteltäessä. Se tuottaa merkkijonon, jossa välilyönnit ja vastaavat tyhjeet
(whitespace) on jätetty pois alusta ja lopusta:
var teksti = " moi vaan "teksti: String = " moi vaan " println("Teksti on: " + teksti + ".")Teksti on: moi vaan . println("Teksti on: " + teksti.trim + ".")Teksti on: moi vaan.
REPL korostaa merkkijonon alussa ja lopussa olevaa tyhjää lainausmerkein. Nämä lainausmerkit eivät siis kuitenkaan ole osa itse merkkijonoa.
Metodi on hyödyksi esimerkiksi näppäimistösyötettä siistiessä. Sovellusten käyttäjät kun joskus kirjoittavat turhia tyhjiä merkkejä antamiinsa syötteisiin.
Jos tyhjiä merkkejä ei alussa tai lopussa ole, trim
palauttaa koskemattoman merkkijonon:
teksti = "puudeli"teksti: String = puudeli println("Teksti on: " + teksti.trim + ".")Teksti on: puudeli.
Voimme sanoa, että trim
-metodi "poistaa merkkijonosta tyhjää". Tarkalleen ottaen siis
se ei kuitenkaan muuta alkuperäistä merkkijonoa, vaan tuottaa uuden merkkijonon,
jossa on muuten samat merkit kuin alkuperäisessä, paitsi että alun ja lopun tyhjät on
jätetty pois. Kaikki merkkijonojen metodit ovat vaikutuksettomia, myös trim
. Jokainen
String
-olio on täysin muuttumaton.
Merkin poimiminen: apply
- ja lift
-metodit
apply
-metodilla voi pyytää tietyn merkin merkkijonosta antamalla sen järjestysnumeron
eli indeksin parametriksi. Järjestysnumerot alkavat nollasta, joten tässä esimerkissä
katsotaan ensin toinen ja sitten viimeinen merkki merkkijonosta "kissa":
"kissa".apply(1)res16: Char = i "kissa".apply(4)res17: Char = a
Huomaa palautusarvon tyyppi. Yksittäisiä merkkejä kuvaa
Scalassa Char
-luokka (lyhenne sanasta character eli
kirjoitusmerkki). String
-olio edustaa nollan tai useamman
merkin mittaista merkkijonoa, Char
täsmälleen yhtä merkkiä.
Merkin katsomiseen indeksin perusteella on myös hieman lyhyempi tapa: kokeile esimerkiksi
"kissa"(1)
.
Ja turvallisempi tapa: muilta kokoelmatyypeiltä tuttu lift
-metodi.
"kissa".lift(1)res18: Option[Char] = Some(i) "kissa".lift(4) res12: Option[Char] = Some(a) "kissa".lift(123)res19: Option[Char] = None
Osiksi split
-metodilla
split
-metodilla voi jakaa merkkijonon osiin käyttäen parametriksi annettua erotinta.
Tässä erottimena on välilyönti:
val lause = "Parempi kyy pivossa kuin kymmenen."lause: String = Parempi kyy pivossa kuin kymmenen. val sanat = lause.split(" ")sanat: Array[String] = Array(Parempi, kyy, pivossa, kuin, kymmenen.) sanat(1)res20: String = kyy
split
-metodin palautusarvon tyyppinä esiintyvä Array
on
vektoria ja puskuria muistuttava alkiokokoelma.
Voit käyttää taulukkoa pitkälti niin kuin vektoria. Käsitellään näiden kokoelmatyyppien eroja luvussa 12.1.
Erotin voi olla mikä vain merkkijono:
lause.split("pi")res21: Array[String] = Array(Parem, " kyy ", vossa kuin kymmenen.)
Kirjainkoko: toUpperCase
ja toLowerCase
val sevenNationArmy = "[29]<<e--------e--g--. e--. d--. c-----------<h----------->e--------e--g--. e--. d--. c---d---c---<h-----------/360"sevenNationArmy: String = [29]<<e--------e--g--. e--. d--. c-----------<h----------->e--------e--g--. e--. d--. c--- d---c---<h-----------/360 play(sevenNationArmy)
Se oli sen verran hienoa, että voisihan tuon tuutata lujempaakin.
play
-funktiomme soittaa isolla kirjoitetut nuotit kovempaa kuin pienellä
kirjoitetut. On olemassa helppo tapa tuottaa isoja kirjaimia:
play(sevenNationArmy.toUpperCase)
Tässä lisäesimerkki metodista ja sen ystävästä toLowerCase
:
"Little BIG Man".toUpperCaseres22: String = LITTLE BIG MAN "Little BIG Man".toLowerCaseres23: String = little big man
Vertailua: compareTo
-metodi
Merkkien etsimistä: indexOf
ja contains
Merkkijonojen indexOf
- ja contains
-metodit ovat samantapaisia kuin vektorien ja
puskurien vastaavat (luvusta 4.2). Niillä voi selvittää, esiintyykö parametriksi
annettu pätkä merkkijonossa:
"noitapiiri".contains("tapiiri")res24: Boolean = true "noitapiiri".contains("laama")res25: Boolean = false "noitapiiri".indexOf("tapiiri")res26: Int = 3 "noitapiiri".indexOf("laama")res27: Int = -1
take
ja drop
ja kumppanit
Myös take
, drop
, head
ja tail
sukulaisineen ovat analogisia luvun 4.2
esittelemien kokoelmametodien kanssa. Alla on muutama esimerkki:
val vasemmalta = "tunnelma".take(5)vasemmalta: String = tunne val oikealta = "tunnelma".drop(5)oikealta: String = lma "tunnelma".take(0)res28: String = "" "tunnelma".take(100)res29: String = tunnelma "tunnelma".takeRight(5)res30: String = nelma "tunnelma".dropRight(5)res31: String = tun
val eka = "tunnelma".headeka: Char = t val loput = "tunnelma".tailloput: String = unnelma val ekaJosOn = "tunnelma".headOptionekaJosOn: Option[Char] = Some(t) val tyhjanEka = "tunnelma".drop(10).headOptiontyhjanEka: Option[Char] = None
Lisätehtävä: lisäys merkkijonon keskelle
Kirjoita Miscellaneous-moduulin misc.scala
-tiedostoon funktio insert
, joka
lisää annetun merkkijonon tiettyyn kohtaan kohdemerkkijonoa. Sen tulee toimia tämän
esimerkin mukaisesti:
val kohde = "pupupaita"kohde: String = pupupaita" insert("jussi", kohde, 4)res32: String = pupujussipaita insert("!!!", kohde, 0)res33: String = !!!pupupaita insert("!!!", kohde, 100)res34: String = pupupaita!!! insert("!!!", kohde, -2)res35: String = !!!pupupaita
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Merkkijonoista puheen ollen: erikoismerkit merkkijonoissa
Luvussa 4.2 sivumainittiin, että rivinvaihtomerkin (newline) saa merkkijonoon
ilmaisulla \n
. Vastaavia erikoismerkintöjä on muitakin. Tässä oleellisimpia:
Merkki |
Merkintä merkkijonoliteraalissa |
---|---|
rivinvaihto |
|
sarkain (tab) |
|
lainausmerkki |
|
kenoviiva |
|
Käyttöesimerkki:
println("Ja minä, päässä side kauhun, " +
"huusin:\n\"Oi Mestari, mik' ääni tuo? " +
"ja keitä\nnuo, jotk' on niinkuin " +
"suuren tuskan orjat?\"")
Tämä tuottaa seuraavan tulosteen:
Ja minä, päässä side kauhun, huusin:
"Oi Mestari, mik' ääni tuo? ja keitä
nuo, jotk' on niinkuin suuren tuskan orjat?"
Kuten näet, tulosteen rivitys määräytyy \n
-merkkien mukaan eikä koodissa olevien rivitysten.
Scalassa on myös mahdollista muodostaa merkkijonoliteraali käyttäen kolmea lainausmerkkiä literaalin kummassakin päässä. Tällöin koko literaalin sisältö erikoismerkkeineen kaikkineen tulkitaan osaksi merkkijonoa eikä etukenoviivoja tarvita. Seuraava koodinpätkä tulostaa saman hilpeän värssyn kuin edellinenkin.
println("""Ja minä, päässä side kauhun, huusin:
"Oi Mestari, mik' ääni tuo? ja keitä
nuo, jotk' on niinkuin suuren tuskan orjat?"""")
Tehtävä: tempo
-funktio
Tehtävänanto
Alla on käyttöesimerkki funktiosta, jolle annetaan samantapainen musiikkikappaletta kuvaava
merkkijono kuin play
-funktiolle ja joka palauttaa tuon musiikin tempon kokonaislukuna.
tempo("cccedddfeeddc---/180")res36: Int = 180 tempo(s"""[72]${" "*96}d${"-"*39}e---f---d${"-"*39}e---f---d${"-"*15}e---f---d${"-"*15}e---f--- f#-----------g${"-"*17}&[62]${" "*104}(>c<afd)--(>c<afd)--------(afdc)--(>c<afd)-------(afdc) ${"-"*17} (>c<abfd)--(>c<abfd)--------(abfdc)--(>c<abfd)-------(abfdc)${"-"*17} (hbgfd) ${"-"*17}(hbg>fd<)${"-"*22}(>ce<hbg)---- (>d<hbge)---------- (>db<hbge)---------- (>c<hbg#e)----- &[29]${" "*96}<<<${"c-----------"*11}cb-----------<${"hb-----------"*3}hb-----&P:a----------- ${"a--------a--a-----------"*11}/480""")res37: Int = 480
Varsinaisella kappaleen sisällöllä funktio ei siis tee mitään, vaan se vain poimii tempon merkkijonon lopusta. Jos tempoa ei ole merkkijonoon kirjattu lainkaan, niin funktio palauttaa kokonaisluvun 120:
tempo("cccedddfeeddc---")res38: Int = 120
Kirjoita Miscellaneous-moduulin misc.scala
-tiedostoon funktio tempo
, joka
toimii esimerkin mukaisesti.
Ohjeita ja vinkkejä
Funktion tulee palauttaa nimenomaan
Int
-tyyppinen arvo, ei merkkijonoa, jossa on numeromerkkejä. Funktio osittelee annetun merkkijonon, poimii tietyn osan, ja tulkitsee sen sisällön lukuna.Tehtävän voi ratkaista hyvin monella eri tavalla. Pelkästään tässä luvussa mainitut merkkijonojen metodit tarjoavat useita eri mahdollisuuksia. Valitse itse jokin tapa. Keksitkö useita?
Voit olettaa, että parametriksi annetussa merkkijonossa on kauttaviivamerkkejä joko nolla tai yksi kappaletta. Voit myös olettaa, että jos kauttaviiva löytyy, niin sen perässä on yksi tai useampi numeromerkki eikä muuta. Sinun ei siis tarvitse tässä käsitellä sen erikoisempia tapauksia kuin mitä REPL-esimerkeissä yllä oli.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Musiikkikappaleiden kuvauksia kätevämmin?
Tässä materiaalissa on esiintynyt varsin pitkiä play
-funktiolle
osoitettuja merkkijonoja, jollaisten laatiminen ja muokkaaminen
on pitkäpiimäistä ja vetää puoleensa virheitä. Onneksi sinun ei
olekaan pakko.
Jos haluat, voit miettiä, millaisia apufunktioita voisi laatia, jotta kappaleita olisi helpompi määritellä kurssilla käytetyn kaltaisiksi nuottimerkkijonoiksi. Voit myös a) pohtia, millaisessa muussa muodossa nuotit voisivat olla kirjattuna tietokoneen muistissa kuin tuollaisina merkkijonoina; b) ideoida, millaisella sovelluksella loppukäyttäjän olisi kätevää kirjata kokonaisia musiikkikappaleita tietokonetta varten; ja c) selvittää, millaisia ratkaisuja on jo olemassa.
Tähtikarttatehtävä, osa 3/4: tähtiä tiedostoista
Luvun 4.4 tähtitehtävän lopuksi totesimme:
Noin niitä yksittäisiä tähtiä tosiaan kuvaan saa, mutta kovin kätevää kuvan luominen ei ole, jos tähtiä pitäisi saada esiin muutamaa enemmän. Kätevämpää olisi, jos tähtien tiedot voisi ladata kerralla jostakin, mihin ne on kirjattu.
Stars-moduulin kansioista test
ja northern
löytyy stars.csv
-nimiset tiedostot:
test
-kansion tiedostoon on kirjoitettu muutaman kuvitteellisen tähden tiedot puolipistein erotettuna. Kohta selviää, mitä kukin merkintä tiedostossa tarkoittaa.northern
-kansion tiedostossa on paljon pidempi luettelo, jossa on todellisten tähtien tietoja (alunperin VizieR-palvelusta).
Voit jättää northern
-kansion muut tiedostot nyt huomiotta.
Tehdään ohjelmastamme versio, joka osaa 1) lukea tähtien tietoja kuvaavat merkkijonot
tiedostosta, 2) jäsentää nuo merkkijonot Star
-olioiksi ja 3) näyttää tähdet
graafisesti. Viimeisen osaongelman olet jo ratkaissut luvussa 4.4. Toisen eli
merkkijonojen tulkitsemisen tähdiksi ratkaiset nyt. Ensimmäinen eli varsinainen
tiedostonkäsittely on ratkaistu puolestasi annetussa koodissa. (Tiedostonkäsittelystä
on tarjolla lisämateriaalia luvussa 11.3.)
Tehtävänanto
Aja annettu StarryApp-ohjelma. Pety sen mustaan näkymään ja sulje se.
Sinänsä tuo sovellus on jo pitkälti toimiva, mutta ratkaisevaa tehtävää hoitava
funktio puuttuu pakkauksesta o1.stars.io
. Funktion nimi on parseStarInfo
ja sillä
on juuri mainittu tehtävä: ottaa sisään yhtä tähteä kuvaava merkkijono (jollaisia löytyy
stars.csv
-tiedostojen riveiltä) ja luoda vastaava Star
-olio.
Lue parseStarInfo
n dokumentaatio. Se selittää mitä kukin merkkijonon osa tarkoittaa
ja mitkä osista ovat sovelluksemme kannalta merkityksellisiä. Toteuta sitten tuo funktio
tiedostoon o1/stars/io/fileio.scala
.
Ohjeita ja vinkkejä
Funktio on dokumentoitu osana
o1.stars.io
-pakkausta.Sovelluksen käyttämä kansio on määritelty
StarryApp.scala
n alussa. Siellä on nyt valittu käyttööntest
-kansio, jonka voit jättää aluksi käyttöön, kunnes funktiosi toimii. Vaihda sitten käyttöönnorthern
-kansio, niin saat esiin komeamman kuvan.Sinun ei tarvitse huomioida mahdollisuutta, että funktiolle välitetty merkkijono ei sisältäisi kuutta tai seitsemää puolipistein erotettua osaa tai olisi jotenkin muuten spesifikaation vastainen. Oleta, että saatu merkkijono kelpaa.
Ohjelma kaatuu ajonaikaiseen virheeseen, jos
stars.csv
-tiedostoihin menee kirjoittamaan jotakin kelvotonta. Tämä ei tietenkään olisi hyväksyttävää kunnollisessa tosielämän sovelluksessa, mutta kelvatkoon nyt meille.
Löydät hyödyllisiä metodeita mm. tästä samasta luvusta.
Osa datasta (z-koordinaatti ja toinen tunnisteluku) ei ole tehtävän kannalta merkityksellistä.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Lisämateriaalia: runsaat vs. niukat rajapinnat
Toisiaan muistuttavia metodeita — hyvä vai huono?
Opiskelijoiden kommentteja tämän luvun esittelemistä merkkijonometodeista ja aiemmin esitellystä kokoelmametodien kirjosta:
String
ien manipulaatio vaikuttaisi kätevältä
Scalassa. Hyvä että on vakiona [peruskirjastossa
tarjolla] noin paljon metodeita.
Tämä luku avasi Scalan perusolemusta lisää, mutta samalla pisti miettimään miksi sama asia tarvitsee saada ilmaistua kaksin eri tavoin. Mielestäni tämä aiheuttaa lähinnä mahdollisuuden sekaantua.
Onpas Scalan perusolioilla paljon valmiita metodeita, joista suuri osa vaikuttaa suhteellisen turhilta ts. tekevän samoja asioita vain eri nimillä.
Scalan merkkijonoja käsittelevät valmiit metodit vaikuttavat varsin kattavilta verrattuna moneen muuhun kieleen.
Ei tosiaan ole harvinaista, että luokalla on paljon metodeita, jotka tekevät osin saman asian ja joista kaikki eivät ole ehdottoman välttämättömiä. Tämä pätee monelle Scala-peruskirjaston luokallekin.
Nuo luokat eivät toisin sanoen tarjoa käyttäjilleen niukkaa rajapintaa (thin interface) vaan runsaan (rich). Teemaa on puitu mm. Kirjoja ja linkkejä -sivun suosittelemassa kirjassa Programming in Scala:
Thin versus rich interfaces represents a commonly faced trade-off in object-oriented design. The trade-off is between the implementers and the clients of an interface. A rich interface has many methods, which make it convenient for the caller. Clients can pick a method that exactly matches the functionality they need. A thin interface, on the other hand, has fewer methods, and thus is easier on the implementers. Clients calling into a thin interface, however, have to write more code. Given the smaller selection of methods to call, they may have to choose a less than perfect match for their needs and write extra code to use it.
Toki runsaan rajapinnan käyttäjä voi olla ensin hieman ymmällään vaihtoehtojen moninaisuuden kanssa, mutta se osoittautuu kyllä yleensä pian käteväksi.
Yhteenvetoa
Scala on puhdas olio-ohjelmointikieli, jossa kaikki arvot kuvataan olioina.
Esimerkiksi
Int
,Double
jaString
ovat luokkia ja näidentyyppiset arvot olioita. Niillä on monia käteviä ja yleishyödyllisiä metodeita.Niinsanotut operaattorit ovat metodeita. Scalassa metodeita voi kutsua kahdella tavalla: pistenotaatiolla ja operaattorinotaatiolla.
Lukuun liittyviä termejä sanastosivulla: olio-ohjelmointi; pistenotaatio, operaattorinotaatio.
Onko näin muissakin kielissä?
Scala ei suinkaan ole ainoa "kaikki on olioita" -kieli. Se kuitenkin eroaa puhtaalla oliolinjallaan eräistä yleisimmistä olio-ohjelmointikielistä. Esimerkiksi Java-kielessä monet asiat mallinnetaan luokkina ja olioina mutta toiset (esim. luvut, totuusarvot) ns. alkeistyyppien (primitive types) avulla. Scalan edustama linja vähentää erikoistapauksia ja johdonmukaistaa kielen "pelisääntöjä".
Palaute
Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Anna Valldeoriola Cardó ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee ja Kelmu.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Tapa, jolla käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin, on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki. Pääkehittäjänä on nyt Markku Riekkinen, jonka lisäksi A+:aa ovat kehittäneet kymmenet Aallon opiskelijat ja muut.
A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
Lisäkiitokset tähän lukuun
Stars-ohjelma on mukaelma Karen Reidin suunnittelemasta ohjelmointiharjoituksesta. Se käyttää tähtidataa VizieR-palvelusta.
Luvussa tehdään vääryyttä Glenn Millerin ja The White Stripesin musiikille. Kiitos ja anteeksi.
Päässä side kauhun huusi Dante Alighieri.
Palautusarvo on Scalan valmista tyyppiä nimeltä
Range
. Kyseessä on eräänlainen lukuja sisältävä muuttumaton alkiokokoelma, siis samantapainen kuin (luku)vektori.