Tietotyyppejä ja tiedon esitysmuotoja
Learning objectives
- Tutustut muuttujien tyyppeihin.
- Tiedät minkälaisia arvoja tietyn tyyppisillä muuttujilla voidaan esittää, ja toisaalta minkälaisia arvoja muuttujilla ei voi esittää.
- Tutustut tiedon esittämiseen siirrettävässä muodossa CSV- ja JSON-formaattien avulla.
- Osaat muuntaa JSON-muotoisen merkkijonon sanakirjaksi ja sanakirjan JSON-muotoiseksi merkkijonoksi.
Tarkastelimme edellisessä osassa informaation, datan, ja tiedon suhdetta. Tutustuimme järjestyksen tärkeyteen ja opimme, että on olemassa laajennettu ASCII -merkistö, missä kukin merkkijonon merkki esitetään 8 bitin bittijonona. Opimme myös luomaan ja käyttämään satunnaislukuja ja tarkastelimme arvauspelin kautta miten tietokone voi näennäisesti muodostaa tietoa käyttäjältä kerätyn datan perusteella.
Tarkastellaan seuraavaksi hieman syvemmin miten muuttujat toimivat kurssilla käyttämässämme Dart-ohjelmointikielessä (ja monessa muussa ohjelmointikielessä). Aloitetaan tutuksi tulleesta ohjelmasta, missä tulostetaan Ada Lovelacen nimi ja syntymävuosi.
main() {
var nimi = 'Ada Lovelace';
var syntymavuosi = 1815;
print('$nimi on syntynyt vuonna $syntymavuosi.');
}
program output
Ada Lovelace on syntynyt vuonna 1815.
Yllä olevassa esimerkissä muuttujat nimi
ja syntymavuosi
esitellään var
-avainsanan (variable) avulla. Kun muuttuja esitellään ja siihen asetetaan arvo, muuttujalle määräytyy tyyppi. Tämä näkyy esimerkiksi seuraavassa esimerkissä, missä syntymavuosi
-muuttujan arvoksi yritetään asettaa nimeä.
main() {
var nimi = 'Ada Lovelace';
var syntymavuosi = 1815;
print('$nimi on syntynyt vuonna $syntymavuosi.');
syntymavuosi = nimi;
print('$syntymavuosi');
}
Yllä olevan ohjelman suorittaminen ei onnistu ja ohjelma näyttää virheen A value of type 'String' can't be assigned to a variable of type 'int' - line 6
eli merkkijonoa ei voi asettaa kokonaislukutyyppiseen muuttujaan (virhe rivillä 6). Mitä ihmettä tässä oikein tapahtuu?
Kun käytämme avainsanaa var
muuttujan esittelyyn, ohjelmointikielen kääntäjä päättelee muuttujan tyypin muuttujaan asetetun arvon ja muuttujan käytön perusteella. Muuttujan tyyppi määrittelee muuttujaan liittyvät ominaisuudet sekä sen mitä muuttujalla voi tehdä. Jokaisella muuttujalla on tyyppi.
Muuttujan tyypin saa selville muuttujaan liittyvän arvon runtimeType
kautta. Tämä tapahtuu kirjoittamalla muuttuja.runtimeType
, missä muuttuja
on muuttujan nimi.
main() {
var nimi = 'Ada Lovelace';
var syntymavuosi = 1815;
print('$nimi on syntynyt vuonna $syntymavuosi.');
print(nimi.runtimeType);
print(syntymavuosi.runtimeType);
}
program output
Ada Lovelace on syntynyt vuonna 1815. String int
Yllä muuttuja nimi
on merkkijonotyyppinen (String
) ja muuttuja syntymavuosi
on kokonaislukutyyppinen (int
).
Muuttujan tyypin määrittely
Ohjelmoija voi määritellä muuttujan tyypin käyttämällä avainsanan var
sijaan konkreettista tyyppiä. Tällöin ohjelmointikielen kääntäjän ei tarvitse päätellä muuttujan tyyppiä, mikä mahdollistaa muuttujien tyyppien päättelyyn liittyvien virheiden vähentämisen.
Alla olevassa Ada Lovelacen syntymävuoden tulostavassa ohjelmassa muuttujat esitellään niiden konkreettisten tyyppien avulla avainsanan var
sijaan. Nimi on merkkijono, joten se esitellään String
-tyyppisenä. Syntymävuosi taas on kokonaisluku, joten se esitellään int
-tyyppisenä.
main() {
String nimi = 'Ada Lovelace';
int syntymavuosi = 1815;
print('$nimi on syntynyt vuonna $syntymavuosi.');
print(nimi.runtimeType);
print(syntymavuosi.runtimeType);
}
program output
Ada Lovelace on syntynyt vuonna 1815. String int
Ohjelmoidessa sekä avainsanan var
että konkreettisen muuttujan tyypin käyttäminen on sallittua. Mikäli muuttujan tyypin määrittelee itse, ohjelmointikielen kääntäjän ei tarvitse päätellä sitä. Kyse on siis vain siitä, kumpi -- ohjelmoija vai ohjelmointikielen kääntäjä -- ottaa vastuun muuttujien tyypin määritteltystä. Muuttujalle asetetaan joka tapauksessa tyyppi.
Tarkastellaan muuttujien tyyppien määrittelyn tuomia hyötyjä kahden esimerkin kautta. Esimerkit käsittelevät yksinkertaista erotuslaskun tekevää funktiota.
Ensimmäisessä esimerkissä kaksiparametrinen funktio erotus
on määritelty ilman funktion parametreille määriteltyjä tyyppejä. Huomaamme alla olevasta lähdekoodista, että ohjelma ei todennäköisesti tule toimimaan, sillä merkkijonon ja luvun miinuslaskua ei voi käyttämässämme ohjelmointikielessä tehdä. Ohjelmointiympäristö ei kuitenkaan osaa kertoa, että ohjelmassa on jotain pielessä. Kokeile ohjelman suorittamista.
Ohjelma ilmoittaa virheestä Uncaught TypeError
, eli ohjelman suorituksessa tapahtui muuttujien tyyppeihin liittyvä virhe. Virheen näkee vasta kun ohjelman suorittaa.
Mitä suurempi ohjelmoitava ohjelma on, sitä enemmän ohjelmaan päätyy ohjelmoijien tekemiä virheitä. Kun muuttujien tyyppejä ei määritellä, osa muuttujien tyyppien perusteella pääteltävistä virheistä (kuten yllä oleva merkkijonon ja luvun erotuslasku) huomataan vasta ohjelmaa suorittaessa. Laajemmissa ohjelmissa tai pitkään suoritettavissa ohjelmissa kuten vaikkapa tieteellisesssä laskennassa ohjelma saattaa olla useammankin päivän käynnissä ennen virheen kohtaamista. Virhetilanteessa ohjelma kaatuu ja useamman päivän laskentatyö menetetään.
Tarkastellaan seuraavaksi toista esimerkkiä, joka on alla olevassa ohjelmointiympäristössä. Toisessa esimerkissä kaksiparametrinen funktio erotus
on määritelty siten, että parametreilla on tyypit. Parametrien tyyppien määrittely tapahtuu asettamalla tyyppi (esim. int
) ennen parametrina olevan muuttujan nimeä. Alla olevassa esimerkissä parametrit on määritelty kokonaislukutyyppisiksi (int), jolloin funktio hyväksyy funktiokutsun yhteydessä vain sellaiset arvot, jotka ovat kokonaislukuja.
Kuten alla huomaat, ohjelmointiympäristö alleviivaa main
-funktiossa olevan funktiokutsun merkkijonomuotoisen parametrin, ja ilmoittaa ohjelmoijalle virheestä. Virheen huomaamiseksi ohjelmaa ei erikseen tarvitse suorittaa.
Virhe yllä olevassa ohjelmassa on The argument type 'String' can't be assigned to the parameter type 'int' - line 2
, eli merkkijonoa ei voi asettaa kokonaislukutyyppisen parametrin arvoksi. Kun tällaisen virheen huomaa, ei ohjelmaa edes kannata suorittaa ennen kuin virhe on korjattu.
Oleellinen ero näissä kahdessa esimerkissä on se, että toisessa esimerkissä ohjelmointiympäristö ilmoittaa virheestä ennen ohjelman suoritusta, kun toisessa virheestä ilmoitetaan vasta ohjelman suorituksen aikana.
Question not found or loading of the question is still in progress.
Tällä kurssilla ei oteta vahvasti kantaa muuttujien tyyppien käyttöön, mutta todetaan että niistä on hyötyä.
Usein käytettyjä muuttujien tyyppejä
Tutustutaan seuraavaksi neljään usein käytettyyn muuttujan tyyppiin, eli merkkijonoon, kokonaislukuun, desimaalilukuun ja totuusarvoon.
Merkkijono eli String
Merkkijonot asetetaan String
-tyyppisen muuttujan arvoksi. Merkkijonot ovat meille jo tuttuja -- alla olevassa esimerkissä luodaan merkkijono, joka tulostetaan kahteen kertaan.
main() {
String nimi = 'Ada Lovelace';
print(nimi);
print('$nimi');
}
program output
Ada Lovelace Ada Lovelace
Sekä käsky print(nimi);
että käsky print('$nimi')
tuottavat saman tulostuksen. Tässä on oikeastaan kyse siitä, että ensimmäisessä käskyssä pyydämme ohjelmaa tulostamaan muuttujan nimi
arvon, kun taas toisessa käskyssä muodostamme tulostettavan merkkijonon osana käskyä (print('$nimi');
). Lausekkeen '$nimi'
evaluaation tulos on merkkijono siinä missä muutkin merkkijonot, ja se voidaan asettaa esimerkiksi muuttujan arvoksi.
main() {
String etunimi = 'Ionic';
String sukunimi = 'Bond';
String teksti = '$sukunimi, $etunimi $sukunimi';
print(teksti);
}
program output
Bond, Ionic Bond
Merkkijonoja voi yhdistää toisiinsa myös +
-operaation avulla. Alla on yllä kuvattu ohjelma eri tavalla esitettynä.
main() {
String etunimi = 'Ionic';
String sukunimi = 'Bond';
String teksti = sukunimi + ', ' + etunimi + ' ' + sukunimi;
print(teksti);
}
program output
Bond, Ionic Bond
Question not found or loading of the question is still in progress.
Useamman rivin merkkijono
Useammasta rivistä koostuva merkkijono aloitetaan kolmella hipsulla ('''
) ja lopetetaan kolmella hipsulla ('''
). Aivan kuten yhdestä rivistä koostuvassa merkkijonossa, myös useammasta rivistä koostuvaan merkkijonoon voi asettaa muuttujien arvoja.
main() {
String etunimi = 'Blaise';
String teksti = '''Hiljaa yössä.
$etunimi istui keittiössä. Mietti
miksei hänen ohjelmansa toiminut.
''';
print(teksti);
}
program output
Hiljaa yössä. Blaise istui keittiössä. Mietti miksei hänen ohjelmansa toiminut.
Merkkijonot muistissa
Dart-ohjelmointikielessä merkkijonot on toteutettu UTF-16 -merkistöä käyttäen. UTF-16 -merkistössä kukin merkkijonon merkki esitetään joko 16 tai 32-bitin bittijonona, missä kukin bittijono vastaa tiettyä merkkiä. Tämä tarkoittaa sitä, että ohjelmissa voidaan käyttää mitä tahansa UTF-16 -merkistön merkkiä, kuten vaikkapa jalkapalloa (⚽), lumiukkoa (⛄), tai linnaa (🏰). Näiden toimivuutta rajaa kuitenkin myös käytössä oleva käyttöjärjestelmä ja selain -- kaikki merkit eivät näy kaikilla käyttöjärjestelmillä.
Merkkijonot koostuvat merkeistä, ja merkkijonoja voi käsitellä hieman kuten listoja. Merkkijonon merkin tietystä indeksistä voi hakea samalla tavalla kuin listalla olevan arvon tietystä indeksistä. Alla olevassa esimerkissä tulostetaan merkkijonon ensimmäinen kirjain.
main() {
var merkkijono = 'merkkijono';
print(merkkijono[0]);
}
program output
m
Aivan kuten listalla, myös merkkijonolla on pituus. Pituus saadaan selville jokaisella merkkijonolla olevasta arvosta length
. Arvon selvittäminen tapahtuu kirjoittamalla muuttuja.length
, missä muuttuja
on merkkijonomuuttujan nimi. Alla oleva ohjelma tulostaa ensin merkkijonon pituuden, sitten merkkijonon viimeisen merkin, ja sitten merkkijonon merkki merkiltä.
main() {
var merkkijono = 'merkkijono';
var pituus = merkkijono.length;
print(pituus);
var viimeinenMerkki = merkkijono[merkkijono.length - 1];
print(viimeinenMerkki);
for (var i = 0; i < merkkijono.length; i++) {
print(merkkijono[i]);
}
}
program output
11 o m e r k k i j o n o
Kokonaisluvut ja desimaaliluvut (int ja double)
Kokonaisluvuilla (int) ja desimaaliluvuilla (double) voi toteuttaa tyypillisiä laskuoperaatioita, kuten plus-, erotus-, ja kertolaskuja. Alla esimerkki kokonaisluvuilla tehdystä pluslaskusta.
main() {
int eka = 3;
int toka = 10;
int summa = eka + toka;
print('Lukujen $eka ja $toka summa on $summa.');
}
program output
Lukujen 3 ja 10 summa on 13.
Desimaaliluvut esitellään muuttujan tyypillä double
. Alla olevassa esimerkissä kahden kokonaisluvun jakolaskun tulos asetetaan double
-tyyppiseen muuttujaan.
main() {
int eka = 3;
int toka = 10;
double osamaara = eka / toka;
print('$eka / $toka = $osamaara');
}
program output
3 / 10 = 0.3
Siinä missä kokonaisluvun lukeminen käyttäjältä onnistuu komennolla int.parse(input.readLineSync())
, voi desimaaliluvun lukea käyttäjältä komennolla double.parse(input.readLineSync())
. Desimaalilukuvun erottimena käytetään pistettä (esim 3.1
).
main() {
print('Syötä kokonaisluku.');
int kokonaisluku = int.parse(input.readLineSync());
print('Syötä desimaaliluku.');
double desimaaliluku = double.parse(input.readLineSync());
print('Syötit luvut $kokonaisluku ja $desimaaliluku.');
}
program output
Syötä kokonaisluku. < 3 Syötä desimaaliluku. < 3.1 Syötit luvut 3 ja 3.1.
Kokonaisluvut ja desimaaliluvut muistissa
Sekä kokonaisluvut että desimaaliluvut esitetään 64-bitin bittijonona, eli kukin kokonaisluku ja kukin desimaaliluku esitetään 64 bitillä. Näiden esitysmuoto kuitenkin poikkeaa toisistaan, joten tiettyä kokonaislukua vastaava bittijono ei vastaa saman luvun desimaalilukua. Esimerkiksi luvun 42
bittijonoesitys ei ole sama luvun 42.0
bittijonoesityksen kanssa.
Tämä näkyy muunmuassa siinä, että mikäli yritämme asettaa desimaalilukua kokonaislukutyyppiseen muuttujaan, ohjelmointiympäristö antaa virheen.
main() {
int eka = 3;
int toka = 10;
int osamaara = eka / toka;
print('$eka / $toka = $osamaara');
}
program output
A value of type 'double' can't be assigned to a variable of type 'int' - line 5
Edeltävä ohjelma toimii mikäli jakolaskun tuloksen asettaa double
-tyyppiseen muuttujaan. Kokeile tätä alla olevalla ohjelmalla.
Sen lisäksi, että kokonaislukujen ja desimaalilukujen esitys muistissa ole samat toisiaan, bittimäärä myös rajoittaa esitettävissä olevia lukuja. Esimerkiksi int
-muuttuja voi esittää mitä tahansa kokonaislukua väliltä [-263 -- 263-1]. Toisaalta, esimerkiksi kokonaislukua 264 (eli 18446744073709551616) ei voi asettaa int
-tyyppisen muuttujan arvoksi.
Isot luvut ja Javascript
Kun kokeilemme alla olevaa ohjelmaa selaimessa toimivassa ohjelmointiympäristössä, näemme virheen The integer literal 18446744073709551616 can't be represented in 64 bits - line 2
. Tämä tarkoittaa käytännössä, ettei int
-tyyppiseen muuttujaan voi asettaa näin suurta lukua.
main() {
int eka = 18446744073709551616;
}
Mielenkiintoiseksi tilanteen tekee seuraava ohjelma.
main() {
int eka = 1844674407370955;
int toka = 1844674407370955;
int tulo = eka * toka;
print(tulo);
}
Kun ohjelman suorittaa selainympäristössä, ohjelman tulostus on 3.402823669209384e+30
eli noin 3.4 1030. Ohjelma siis näennäisesti toimii* vaikka muuttujaan tulo
asetetaan luku, joka on merkittävästi suurempi kuin int
-tyyppiseen muuttujaan hyväksyttävät luvut. Tässä on taustalla Dart-kielisten ohjelmien suorittaminen Javascriptinä selaimessa -- selainympäristössä Dart-ohjelmat käännetään Javascriptiksi ennen niiden suorittamista, jolloin kääntäjä saattaa tehdä muutoksia ohjelman toimintaan. Yllä muuttujan tulo
tyyppi muuttuu ohjelman käännösaikana eikä ohjelman suorittaminen aiheuta virhettä.
Vastaavasti myös double
-tyyppisten muuttujien mahdolliset arvot ovat rajattuja. Tämä voi aiheuttaa ongelmia -- esimerkiksi kaksi desimaalilukua sisältävä lasku voi johtaa pyöristysvirheeseen, kuten alla oleva esimerkki näyttää.
main() {
double eka = 0.1;
double toka = 0.2;
double summa = eka + toka;
print('$eka + $toka = $summa');
}
program output
0.1 + 0.2 = 0.30000000000000004
Yllä olevan esimerkin ja laajemmin desimaalilukuihin liittyvien mahdollisten pyöristysvirheongelmien takia desimaalilukuja käytetään harvoin hyvin tarkkaa laskentaa edellyttävissä järjestelmissä. Esimerkiksi harva -- jos yksikään -- pankkijärjestelmä käyttää desimaalilukuja rahan esittämiseen.
Mikäli ohjelmissa tarvitaan suuria kokonaislukuja tai hyvin tarkkoja desimaalilukuja, on niitä varten tyypillisesti erilliset kirjastot. Esimerkiksi Dart-kielessä on BigInt-muuttuja isojen kokonaislukujen käsittelyyn, jonka lisäksi desimaalilukuja voi käsitellä esimerkiksi Decimal-kirjaston avulla. Näihin ei kuitenkaan syvennytä tässä tarkemmin.
Totuusarvo eli bool
Totuusarvoja käytetään totta tai epätotta olevien asioiden esittämiseen, kuten esimerkiksi sähkölaitteen päälläolemisen tai tehtävän tehdyksisaamisen kuvaamiseen. Totuusarvo esitellään avainsanalla bool
ja se saa arvokseen joko arvon true
eli totta tai false
eli epätotta.
main() {
bool totta = true;
bool epatotta = false;
print('totta: $totta, epätotta: $epatotta');
}
program output
totta: true, epätotta: false
Vaikka emme ole eksplisiittisesti käyttäneet totuusarvomuuttujia ohjelmissamme, olemme käyttäneet totuusarvoja ehkäpä huomaamattamme. Esimerkiksi, kun kirjotamme ehtolauseen, ehtolauseen lauseke arvioidaan ohjelman suoritusaikana totuusarvoksi true
tai false
, jonka perusteella päätetään suoritetaanko ehtolauseeseen liittyvä ohjelmakoodi.
main() {
int luku = 3;
if (luku == 3) {
print('Hiphei!');
}
}
program output
Hiphei!
Yllä olevan ehtolauseen lauseke luku == 3
arvioidaan ohjelman suorituksen aikana totuusarvoksi. Yllä olevan ohjelman tapauksessa arvoksi tulee true
.
Voimme demonstroida tätä myös erottamalla ehtolauseen lausekkeen totuusarvon selvittäminen omaksi rivikseen, missä saatu arvo asetetaan totuusarvomuuttujaan.
main() {
int luku = 3;
bool ehtolauseenLauseke = luku == 3;
print('luku == 3: $ehtolauseenLauseke');
if (ehtolauseenLauseke) {
print('Hiphei!');
}
}
program output
luku == 3: true Hiphei!
Question not found or loading of the question is still in progress.
Totuusarvo muistissa
Totuusarvon true
tai false
voisi esittää muistissa yhdellä bitillä, jonka arvo on joko 1
tai 0
. Näin ei kuitenkaan käytännössä ole.
Saattaa tuntua omituiselta, ettei totuusarvoa esitetä muistissa yhden bitin avulla, sillä se olisi tehokkainta muistin käytön kannalta. Käytännössä ohjelmointikielten toteuttajien tulee huomioida myös muita asioita, kuten ohjelman tehokkuutta. Tarkastellaan tätä hieman kärjistetyt esimerkin kautta. Oletetaan, että käytössämme on 32 bittiä eli 4 tavua, ja haluamme tallentaa muistiin 2 totuusarvoa ja 2 laajennetun ASCII-merkistön merkkiä. Nämä mahtuvat hyvin käytössämme olevaan 32 bittiin, sillä totuusarvot vievät 1 bitin ja merkit 8 bittiä.
Oletetaan, että tallennamme ensin totuusarvon, sitten merkin, sitten totuusarvon, ja sitten vielä merkin. Tätä voi ajatella seuraavanlaisena bittijonona bmmmmmmmmbmmmmmmm
, missä b
vastaa totuusarvon arvoa ja mmmmmmm
-sarja vastaa yhden merkin arvoa. Tällaisen bittijonon käyttö tarvitsee tiedon totuusarvojen ja merkkien sijainneista. Koska ohjelmien tulee toimia tehokkaasti, on mielekkäämpää jakaa muisti sovitun kokoisiin palasiin kuten tavuihin. Tällöin muistia voidaan käydä läpi tavu tavulta, eikä yksittäiset lyhyemmät tai pidemmät bittojonot vaikuta seuraavan tavun sijaintiin.
Yhteenveto
Alla on yhteenveto käsitellyistä muuttujien tyypeistä.
Tyyppi | Arvot | Esimerkki |
---|---|---|
String | Merkkijonot. | String nimi = 'Ada Lovelace'; |
int | Kokonaisluvut. | int vuosi = 1950; |
double | Liukuluvut. | double pii = 3.14; |
bool | Totuusarvo (true tai false ). | bool onnellinen = true; |
Emme käsitelleet listoja ja sanakirjoja, mutta oikeastaan kaikilla käyttämillämme muuttujilla ja tietorakenteilla on tyyppi. Alla oleva esimerkki tulostaa listan ja sanakirjan tyypit.
main() {
var lista = [];
var sanakirja = {};
print(lista.runtimeType);
print(sanakirja.runtimeType);
}
program output
JSArray<dynamic> JsLinkedHashMap<dynamic, dynamic>
Listan ja sanakirjan tyypeiksi tulostuu JSArray<dynamic>
ja JsLinkedHashMap<dynamic, dynamic>
. Tämä selittyy ohjelmointiympäristön kääntäjän toiminnalla -- selainympäristössä Dart-kieliset ohjelmat käännetään JavaScript-kielelle, jossa on omat tyypit em. tietorakenteille. Määre <dynamic>
kuvastaa sitä, että tietorakenteisiin lisättävien muuttujien tyyppi voi vaihdella, eli tietorakenteisiin voidaan lisätä erityyppisiä muuttujia.
Mikäli Dart-kielellä haluaa määritellä listan ja sanakirjan tyypit eksplisiittisesti, tapahtuu se seuraavalla tavalla. Listaa vastaa tyyppi List
ja sanakirjaa tyyppi Map
.
main() {
List lista = [];
Map sanakirja = {};
print(lista.runtimeType);
print(sanakirja.runtimeType);
}
Ohjelmassa voidaan myös määritellä eksplisiittisesti listalle ja sanakirjaan lisättävien muuttujien tyyppi. Alla olevassa esimerkissä listalle voidaan asettaa vain kokonaislukuja, ja sanakirjan avaimet ovat merkkijonoja ja arvot kokonaislukuja.
main() {
List<int> lista = [];
Map<String, int> sanakirja = {};
print(lista.runtimeType);
print(sanakirja.runtimeType);
}
Tiedon esittämiseen tarkoitetut formaatit
Edellä kuvattiin miten muutamat käyttämämme ohjelmointikielen muuttujat toimivat ja miten ne tallennetaan tietokoneen muistiin. Ohjelmointikielillä ja laajemmin tietokoneilla ja tietokoneohjelmilla on pitkä historia, johon liittyy paljon yhteensopivuusongelmia. Yhdellä tietokoneella kirjoitettu ohjelma ei ole aina toiminut toisella tietokoneella, aivan kuten yhdellä tekstinkäsittelyohjelmalla kirjoitettu tekstidokumentti ei aina toimi toisella tekstinkäsittelyohjelmalla.
Ratkaisuna tähän on sopimusten tekeminen. Esimerkiksi aiemmin käsitelty laajennettu ASCII -merkistö, aivan kuten lyhyesti mainitsemamme UTF-16, on virallinen standardi, joka määrittelee miten bittijonot muutetaan merkeiksi ja merkit bittijonoiksi. Noudattamalla sopimuksia eli standardeja voidaan päästä tilanteeseen, missä merkittävä osa yhteensopivuusongelmista väistyy.
Tarkastellaan seuraavaksi kahta yleisesti tiedon siirtoon käytettyä formaattia: Comma-Separated Values (CSV) ja JavaScript Object Notation (JSON). Kummassakin näistä tietoa esitetään merkkijonomuodossa (esimerkiksi tiedostoon tallennettuna tai verkkopalvelusta haettuna), ja kumpikin näistä käyttää tiettyä muotoa tiedon esittämiseen.
Comma-Separated Values (CSV)
Comma-Separated Values (CSV) on tiedostojen ja tiedon tallentamiseen käytetty melko yksinkertainen muoto. Siinä tallennettavat arvot erotellaan toisistaan pilkuilla (comma) tai puolipisteillä. Alla on esimerkki CSV-tiedostosta, joka sisältää nimiä ja syntymävuosia -- nimet ja syntymävuodet on eroteltu pilkulla toisistaan.
Walt Disney,1901
John Wayne,1907
Äiti Teresa,1910
Nelson Mandela,1918
Andy Warhol,1928
Elvis Presley,1935
Merkkijonon pilkkominen osiin onnistuu merkkijonon split
-metodilla, joka saa parametrinaan merkin, jonka perusteella pilkkominen tapahtuu. Metodia kutsutaan muodossa muuttuja.split(',')
, missä muuttuja
on pilkottava merkkijono, ja ','
on kohta, jonka perusteella merkkijono pilkotaan. Metodi palauttaa listan, joka sisältää merkkijonoja.
Alla olevassa esimerkissä merkkijono Hei maailma!
pilkotaan välilyöntien kohdalta.
main() {
var mjono = 'Hei maailma!';
var osat = mjono.split(' ');
print('Osia: ${osat.length}');
print('Osat:');
for(var i = 0; i < osat.length; i++) {
print(osat[i]);
}
}
program output
Osia: 2 Osat: Hei maailma!
Merkkijonoissa olevat rivinvaihdot on merkitty erityismerkeillä, jotka eivät näy käyttäjälle. Pääosin rivinvaihto tapahtuu \n
-merkillä. Alla olevassa esimerkissä useamman rivin sisältävä merkkijono pilkotaan riveihin, jonka jälkeen rivit tulostetaan.
main() {
String data = '''Walt Disney,1901
John Wayne,1907
Äiti Teresa,1910
Nelson Mandela,1918
Andy Warhol,1928
Elvis Presley,1935''';
var rivit = data.split('\n');
for (var i = 0; i < rivit.length; i++) {
var rivi = rivit[i];
print(rivi);
}
}
program output
Walt Disney,1901 John Wayne,1907 Äiti Teresa,1910 Nelson Mandela,1918 Andy Warhol,1928 Elvis Presley,1935
Täydennetään esimerkkiä siten, että ohjelmassa tulostetaan vain ne rivit, joilla oleva henkilö on syntynyt ennen vuotta 1910. Jotta tämä onnistuisi, kukin rivi tulee pilkkoa vielä erikseen osiin. CSV-muodossa rivin osien erottimena on pilkku, joten pilkotaan rivit komennolla split(',')
. Rivin osia kuvaavassa muuttujassa ensimmäisessä indeksissä tulee olemaan nimi, ja toisessa indeksissä syntymävuosi.
main() {
String data = '''Walt Disney,1901
John Wayne,1907
Äiti Teresa,1910
Nelson Mandela,1918
Andy Warhol,1928
Elvis Presley,1935''';
var rivit = data.split('\n');
for (var i = 0; i < rivit.length; i++) {
var rivi = rivit[i];
var osat = rivi.split(',');
if (osat[1] >= 1910) {
continue;
}
print(osat[0]);
}
}
Huomaamme ohjelmassa kuitenkin virheen. Ohjelmointiympäristö näyttää virheen The operator '>=' isn't defined for the type 'String' - line 16
eli syntymävuoden vertailussa käytetty suurempi tai yhtäsuuri -operaattori (>=
) ei toimi merkkijonoille. Tämä johtuu siitä, että yritämme verrata merkkijonoa ja kokonaislukua. Käytetään komentoa int.parse
syntymävuotta kuvaavan merkkijonon muuntamiseen kokonaisluvuksi.
main() {
String data = '''Walt Disney,1901
John Wayne,1907
Äiti Teresa,1910
Nelson Mandela,1918
Andy Warhol,1928
Elvis Presley,1935''';
var rivit = data.split('\n');
for (var i = 0; i < rivit.length; i++) {
var rivi = rivit[i];
var osat = rivi.split(',');
if (int.parse(osat[1]) >= 1910) {
continue;
}
print(osat[0]);
}
}
Nyt ohjelma tulostaa ennen vuotta 1910 syntyneiden henkilöiden nimet.
program output
Walt Disney John Wayne
Mikäli kirjoitat ohjelmia omalla koneellasi, voit lukea tietoa myös tiedostoista. Tiedostoihin ei selainympäristössä toistaiseksi pääse käsiksi. Tiedostojen lukemiseen käytetään dart:io
-kirjastosta löytyvää File
-apuvälinettä sekä siihen liittyvää readAsStringSync()
-metodia. Alla olevassa esimerkissä luodaan tiedosto, luetaan tiedosto, ja asetetaan tiedoston sisältö muuttujan sisalto
arvoksi. Tämän jälkeen muuttuja pilkotaan riveiksi, jotka tulostetaan yksi kerrallaan.
import 'dart:io';
main() {
var tiedosto = File('tiedosto.csv');
var sisalto = tiedosto.readAsStringSync();
var rivit = sisalto.split('\n');
for (var i = 0; i < rivit.length; i++) {
var rivi = rivit[i];
print(rivi);
}
}
JavaScript Object Notation (JSON)
JSON (JavaScript Object Notation) on tiedon tallentamiseen ja siirtämiseen käytetty tietomuoto. JSON-muotoiset dokumentit ovat tekstidokumentteja, jotka sisältävät tietyssä muodossa esitettyä tietoa. Merkkijono, joka sisältää JSON-muotoista dataa, alkaa aaltosululla {
ja päättyy aaltosulkuun }
.
main() {
String data = '{}';
print(data);
}
program output
{}
JSON-muotoiseen dokumenttiin voidaan määritellä muuttujia sekä niihin liittyviä arvoja. Muuttujien nimi kirjoitetaan hipsujen ("
) sisään, esim. "nimi"
, hipsuja seuraa kaksoispiste :
, joita seuraa muuttujan arvo, esim. "Ada Lovelace"
.
main() {
String data = '{"nimi":"Ada Lovelace"}';
print(data);
}
program output
{"nimi":"Ada Lovelace"}
Muuttujat erotellaan toisistaan pilkulla. Alla olevassa esimerkissä JSON-muotoiseen merkkijonoon on lisätty syntymävuosi. Aivan kuten ohjelmoidessa, kokonaisluvut (ja desimaaliluvut) esitetään ilman hipsuja, ja merkkijonot hipsujen kanssa.
main() {
String data = '{"nimi":"Ada Lovelace", "syntymavuosi": 1815}';
print(data);
}
program output
{"nimi":"Ada Lovelace", "syntymavuosi": 1815}
JSON-muotoiseen dataan voi lisätä myös listoja. Listat alkaa [
-merkillä ja loppuu ]
-merkillä. Listan sisälle voi lisätä hipsuilla eroteltuja arvoja. Alla olevassa esimerkissä Ada Lovelacelle on asetettu muutama supertaito. Merkkijono on esitetty useammalla rivillä lukemisen helpottamiseksi.
main() {
String data = '''{
"nimi":"Ada Lovelace",
"syntymavuosi": 1815,
"supertaidot": ["matematiikka", "ohjelmointi"]
}''';
print(data);
}
program output
{ "nimi":"Ada Lovelace", "syntymavuosi": 1815, "supertaidot": ["matematiikka", "ohjelmointi"] }
Koska JSON on standardi, eli se noudattaa annettuja sääntöjä, on JSON-muotoisen tekstidokumentin ohjelmointikielen ymmärtämään muotoon muuntamiseen valmiita ohjelmoijan elämää helpottavia apuvälineitä. Dart tarjoaa valmiin kirjaston dart:convert
, jonka avulla tekstimuotoinen JSON-dokumentti voidaan muuntaa sanakirjaksi.
import 'dart:convert';
main() {
String data = '''{
"nimi":"Ada Lovelace",
"syntymavuosi": 1815,
"supertaidot": ["matematiikka", "ohjelmointi"]
}''';
print(data);
var sanakirja = json.decode(data);
print(sanakirja);
}
program output
{ "nimi":"Ada Lovelace", "syntymavuosi": 1815, "supertaidot": ["matematiikka", "ohjelmointi"] } {nimi: Ada Lovelace, syntymavuosi: 1815, supertaidot: [matematiikka, ohjelmointi]}
Kun JSON-muotoinen dokumentti on muunnettu sanakirjaksi, on tietyn muuttujan arvon noutaminen sanakirjasta suoraviivaista. Alla olevassa esimerkissä tulostetaan merkkijonomuotoisen JSON-dokumentin ja sanakirjan lisäksi Ada Lovelacen syntymävuosi.
import 'dart:convert';
main() {
String data = '''{
"nimi":"Ada Lovelace",
"syntymavuosi": 1815,
"supertaidot": ["matematiikka", "ohjelmointi"]
}''';
print(data);
var sanakirja = json.decode(data);
print(sanakirja);
int vuosi = sanakirja['syntymavuosi'];
print('Syntymävuosi on $vuosi.');
}
program output
{ "nimi":"Ada Lovelace", "syntymavuosi": 1815, "supertaidot": ["matematiikka", "ohjelmointi"] } {nimi: Ada Lovelace, syntymavuosi: 1815, supertaidot: [matematiikka, ohjelmointi]} Syntymävuosi on 1815.
JSON-formaatista tekee mielenkiintoisen muunmuassa se, että monet ohjelmointirajapinnat hyödyntävät sitä tiedonvälityksessä. Esimerkkinä tällaisesta ohjelmointirajapinnasta on runoja tarjoavan Poemist-palvelun rajapinta. Osoitteessa https://www.poemist.com/api/v1/randompoems on palvelu, joka palauttaa jokaiselle pyynnölle satunnaisesti valitun joukon runoja.
Alla oleva esimerkki hakee yllä kuvatusta rajapinnasta joukon runoja ja näyttää niistä yhden käyttäjälle. Palaamme esimerkissä esiintyviin omituisiin sanoihin (mm. async
ja await
) hieman myöhemmin -- ohjelmointirajapintoja tarkastellaan enemmän selainohjelmointiin liittyvällä kurssilla.
Hi! Please help us improve the course!
Please consider the following statements and questions regarding this part of the course. We use your answers for improving the course.
I can see how the assignments and materials fit in with what I am supposed to learn.
I find most of what I learned so far interesting.
I am certain that I can learn the taught skills and knowledge.
I find that I would benefit from having explicit deadlines.
I feel overwhelmed by the amount of work.
I try out the examples outlined in the materials.
I feel that the assignments are too difficult.
I feel that I've been systematic and organized in my studying.
How many hours (estimated with a precision of half an hour) did you spend reading the material and completing the assignments for this part? (use a dot as the decimal separator, e.g 8.5)
How would you improve the material or assignments?