Merkkijono

Ohjelmissa pitää usein käsitellä erilaista teksitietoa, esimerkiksi nimiä, osoitteita sekä erilaisia tunnuksia, kuten opiskelijanumeroita ja autojen rekisterinumeroita. Näitä voidaan käsitellä kätevästi merkkijonojen avulla. Merkkijono koostuu yhdestä tai useammasta peräkkäisestä merkistä. Merkkijono voi myös olla tyhjä, jolloin siinä ei ole yhtään merkkiä.

Merkkijono esitetään yksin- tai kaksinkertaisten lainausmerkkien avulla. Seuraavat kaksi sijoituskäskyä

>>> mjono = 'appelsiini'
>>> mjono = "appelsiini"

tarkoittavat täysin samaa.

Toisin kuin monissa muissa ohjelmointikielissä, Pythonissa ei eroteta toisistaan yksittäisiä merkkejä ja merkkijonoja. Myös yksittäiset merkit esitetään aina yhden merkin mittaisina merkkijonoina.

Pythonissa on tyyppi str merkkijonojen esittämiseen. Merkkijonojen käsittely Python-ohjelmissa muistuttaa hyvin paljon listojen käsittelyä. Olennaisin ero on siinä, että listan sisältöä voidaan muuttaa listan luomisen jälkeen, mutta merkkijonon sisältöä ei voida. Esimerkiksi seuraavat Python-rivit ovat täysin mahdollisia:

>>> lukulista = [5, 2, 7]
>>> lukulista[1] = 4
>>> lukulista
[5, 4, 7]

Mutta sen sijaan seuraavat rivit aiheuttavat virhetilanteen, koska aikaisemmin luodun merkkijonon sisältöä ei voi muuttaa:

>>> sana = "sitruuna"
>>> sana[1] = "a"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

Merkkijonoja voidaan kuitenkin muuten käsitellä monilla samoilla tavoilla kuin listoja. Esimerkiksi merkkijonon mjono indeksillä i olevan kirjaimen saa selville ilmauksella mjono[i], vaikka kirjainta ei pystykään vaihtamaan sijoituskäskyllä, esimerkiksi

>>> mjono = "appelsiini"
>>> print(mjono[3])
e

Merkkijonojen merkit voidaan myös käydä läpi for-käskyn avulla samalla tavalla kuin listan merkit. Esimerkiksi

>>> mjono = "appelsiini"
>>> for merkki in mjono:
...     print(merkki)
...
a
p
p
e
l
s
i
i
n
i

Merkkijonosta voidaan myös ottaa alimerkkijonoja samalla tavalla kuin listoista antamalla hakasulkujen sisässä indeksialue, esimerkiksi

>>> mjono = "appelsiini"
>>> print(mjono[3:7])
elsi

Ennen kaksoispistettä oleva luku kertoo jälleen ensimmäisen alimerkkijonoon otettavan indeksin ja kaksoispisteen jälkeen oleva luku ensimmäisen indeksin, jota ei oteta mukaan alimerkkijonoon.

Merkkijonon pituuden saa selville funktiolla len:

>>> mjono = "appelsiini"
>>> print(len(mjono))
10

Operaattorilla in pystyy tutkimaan sitä, esiintykö kirjain merkkijonossa ja metodilla index saa selville parametrina annetun merkin ensimmäisen indeksin merkkijonossa:

>>> mjono = "appelsiini"
>>> if "i" in mjono:
...     print("i esiintyy sanassa")
...     print("indeksilla", mjono.index("i"))
...
i esiintyy sanassa
indeksilla 6

Operaattorin in avulla voi tutkia myös sitä, esiintyykö yhtä kirjainta pitempi merkkijono osana toista merkkijonoa:

>>> mjono = "appelsiini"
>>> print("elsii" in mjono)
True
>>> print("aelsi" in mjono)
False

Vaikka itse merkkijonoa ei voikaan muuttaa sen jälkeen, kun se on luotu, niin merkkijonoon viittaava muuttuja voidaan sijoituskäskyllä panna viittaamaan kokonaan toiseen merkkijonoon. Esimerkiksi seuraavat ohjelmarivit ovat täysin sallittuja:

>>> mjono = "appelsiini"
>>> print(mjono)
appelsiini
>>> mjono = "apelsiini"
>>> print(mjono)
apelsiini

Tässä alkuperäinen merkkijono "appelsiini" ei kuitenkaan muutu miksikään, vaan luodaan kokonaan uusi merkkijono "apelsiini", ja muuttuja mjono pannaan viittaamaan luotuun uuteen merkkijonoon.

Uusi merkkijono voi riippua vanhasta merkkijonosta. Pythonissa on metodeita, joilla voidaan luoda kokonaan uusi, mutta vanhaa merkkijonoa muistuttava merkkijono. Aikaisempaan merkkijonoon viitannut muuttuja voidaan silloin sijoituskäskyllä panna viittaamaan luotuun, kokonaan uuteen merkkijonoon. Esimerkiksi metodi lower luo uuden merkkijonon, joka sisältää muuten samat merkit kuin nykyinen merkkijono, mutta kaikki isot kirjaimet on muutettu pieniksi kirjaimiksi.

>>> mjono = "AppelSiIni"
>>> mjono = mjono.lower()
>>> print(mjono)
appelsiini

Tämä metodi on hyvin kätevä silloin, kun pitää lukea käyttäjän syötettä ja verrata sitä johonkin muuhun tekstiin. Jos ei tiedetä, antaako käyttäjä syötteen isoina vai pieninä kirjaimina, voidaan syöte muuttaa ensin kokonaan pieniksi kirjaimiksi, jolloin sitä tarvitsee verrata vain pienillä kirjaimilla kirjoitettuun tekstiin.

Vastaavasti metodilla upper voidaan luoda uusi merkkijono, joka sisältää muuten samat merkit kuin nykyinen merkkijono, mutta kaikki pienet kirjaimet on muutettu isoiksi kirjaimiksi.

>>> mjono = "AppelSiIni"
>>> mjono = mjono.upper()
>>> print(mjono)
APPELSIINI

Käyttäjän syötettä lukiessa tarvitaan usein myös metodia strip. Se luo uuden merkkijonon, jossa on poistettu nykyisen merkkijonon alussa ja lopussa esiintyvät tyhjät merkit. Tyhjillä merkeillä tarkoitetaan välilyönti-, tabulointi, rivinvaihto- ja sivunvaihtomerkkejä. Tarkoituksena on poistaa luetun merkkijonon alusta ja lopusta mahdolliset käyttäjän vahingossa kirjoittamat välilyönnit ja vastaavat. Alla on esimerkki strip-metodin käytöstä. Merkkijonot on nyt tulostettu lainausmerkkien kanssa niin, että näkyy tarkemmin, mistä merkkijono alkaa ja mihin se loppuu.

>>> mjono = "           appelsiini   "
>>> mjono
'    \tappelsiini   '
>>> mjono = mjono.strip()
>>> mjono
'appelsiini'

Esimerkin merkkijonossa aluksi esiintynyt merkki "\t" tarkoittaa tabulointi-merkkiä.

Useampi merkkijono voidaan yhdistää yhdeksi +-operaattorin avulla, ja merkkijonoja voi jopa monistaa *-operaattorilla:

>>> etunimi = "Mikko"
>>> sukunimi = "Mallikas"
>>> kokonimi = etunimi + " " + sukunimi
>>> print(kokonimi)
Mikko Mallikas
>>> merkki = "&"
>>> rivi = 5 * merkki
>>> print(rivi)
&&&&&
>>> rivit = 3 * (rivi + "\n")
>>> print(rivit)
&&&&&
&&&&&
&&&&&

Yksi käytännössä usein tarvittava merkkijonoja käsittelevä metodi on split. Sen avulla voi jakaa merkkijonon useampaan osaan niin, että jako tehdään aina halutun merkin tai pidemmän merkkijonon kohdalta. Oletusarvoisesti jako tehdään välilyöntimerkin kohdalla. Jaon tuloksena syntyneet osamerkkijonot voidaan tallentaa listaan seuraavan esimerkin mukaisesti:

>>> teksti = "Monta eri sanaa samassa merkkijonossa"
>>> sanat = teksti.split()
>>> print(sanat)
['Monta', 'eri', 'sanaa', 'samassa', 'merkkijonossa']
>>> for yksittainen_sana in sanat:
...     print(yksittainen_sana)
...
Monta
eri
sanaa
samassa
merkkijonossa

Jaossa käytetty merkki (tässä tapauksessa välilyönti) ei kuulu yhteenkään jaon tuloksena syntyneeseen osamerkkijonoon. Jos jako halutaan tehdä jonkin muun kuin välilyöntimerkin kohdalta, annetaan tämä merkki (tai pidempi merkkijono) parametrina split-metodille, esimerkiksi

>>> teksti2 = "sanat/erotettu/toisistaan/kauttaviivalla"
>>> sanat2 = teksti2.split("/")
>>> print(sanat2)
['sanat', 'erotettu', 'toisistaan', 'kauttaviivalla']

Tässä esimerkissä näkyy myös hyvin se, että listan alkioiden ei tarvitse suinkaan olla lukuja, vaan lista voi sisältää myös esimerkiksi merkkijonoja.

Alla oleva animaatio esittää vielä esimerkin merkkijonon jakamisesta ja sen tuloksena syntyneen listan läpikäynnistä.

Jos jaon tuloksena syntyneitä osia halutaan käsitellä lukuarvoina, pitää split-operaation tuloksena syntyneen listan alkiot muuttaa int- tai float-tyyppisiksi ennen laskutoimituksia. Alla on esimerkki ohjelman osasta, joka lukee käyttäjältä yhden rivin, jakaa sen osiin välilyönnin kohdalta, muuttaa osat kokonaisluvuiksi ja tallentaa ne toiseen listaan:

print("Anna kaikki pisteet yhdella rivilla.")
print("Erota luvut toisistaan valilyonnilla.")
rivi = input()
osat = rivi.split()
lukulista = []
for osa in osat:
    osa_lukuna = int(osa)
    lukulista.append(osa_lukuna)
print("Alkuperainen lista:", osat)
print("Lukulista:", lukulista)

Näiden rivien suoritus voi näyttää esimerkiksi seuraavalta:

Anna kaikki pisteet yhdella rivilla.
Erota luvut toisistaan valilyonnilla.
22 33 56
Alkuperainen lista: ['22', '33', '56']
Lukulista: [22, 33, 56]

Alkuperäisen (split-operaation tuloksena syntyneen) listan tulostuksessa lukujen ympärillä on lainausmerkit. Se kertoo siitä, että Python-tulkin mielestä listassa ei ole lukuja, vaan merkkijonoja. Toisen listan alkiot taas ovat kokonaislukuja.

Metodin split avulla edellisen luvun matriisi-esimerkkiä voidaan muuttaa siten, että käyttäjä antaa matriisin kunkin rivin alkiot yhdellä rivillä. Kokonainen rivi siis luetaan yhdellä input-käskyllä, jaetaan sitten osiin ja osat muutetaan desimaaliluvuiksi. Desimaalilukuja sisältävä lista lisätään koko matriisia kuvaavaan kaksiulotteiseen listaan. Alla on esitetty esimerkistä vain muutettu funktio lue_matriisi. Muu ohjelma ei muutu mitenkään.

def lue_matriisi(rivilkm, sarakelkm):
    matriisi = []
    print("Anna matriisin alkiot riveittain,")
    print(rivilkm, "rivia ja", sarakelkm, "saraketta.")
    print("Anna yhden rivin alkiot yhdella rivilla")
    print("valilyonnilla erotettuna.")
    i = 0
    while i < rivilkm:
        kayttajan_rivi = input()
        osat = kayttajan_rivi.split()
        if len(osat) != sarakelkm:
            print("Vaara maara alkioita rivilla.")
        else:
            rivi_lukuina = [0.0] * sarakelkm
            for j in range(sarakelkm):
                rivi_lukuina[j] = float(osat[j])
            matriisi.append(rivi_lukuina)
            i += 1
    return matriisi

Merkkijonoja voidaan myös vertailla operaattoreilla ==, !=, <=, >=, < ja >. Esimerkiksi mjono1 == mjono2 on tosi, jos muuttujan mjono1 viittaama merkkijono sisältää täsmälleen samat merkit (samassa järjestyksessä) kuin muuttujan mjono2 viittaama merkkijono. Vertailussa pienet ja suuret kirjaimet katsotaan eri merkeiksi. Muut operaattorit käyvät läpi vertailtavia merkkijonoja merkki kerrallaan, kunnes kohdataan ensimmäistä kertaa eri merkit. Tällöin näiden merkkien asema käytetyssä merkkikoodausjärjestelmässä (siinä, miten kukin merkki esitetään tietokoneen muistissa binäärilukuna) ratkaisee sen, kumpi merkkijonoista katsotaan toista pienemmäksi. Käytännössä vertailu menee useimmiten aakkosjärjestyksen mukaan, mutta kaikki isot kirjaimet ovat järjestyksessä ennen pieniä kirjaimia ja skandinaavisten aakkosten (å, ä ja ö) osalta vertailu ei toimi aakkosjärjestyksen mukaisesti.

Esimerkkinä merkkijonojen vertailusta esitetään vielä ohjelma, joka tekee lämpötilamuunnoksia fahrenheit-asteista celsius-asteiksi ja päinvastoin. Ohjelma ensin kysyy käyttäjältä, minä asteina hän haluaa lämpötilan antaa. Ohjelma lukee käyttäjältä muunnettavan lämpötilan ja tekee muunnoksen haluttuun suuntaan. Sen jälkeen ohjelma kysyy käyttäjältä, haluaako hän jatkaa antamalla toisen lämpötilan. Tätä jatketaan niin kauan, kunnes käyttäjä kertoo, että hän ei halua jatkaa.

def muunna_celsiuksiksi(F_asteet):
    celsius_asteet = (F_asteet - 32) * 5.0 / 9.0
    return celsius_asteet

def muunna_fahrenheiteiksi(C_asteet):
    fahrenheit_asteet = 9.0/5.0 * C_asteet + 32
    return fahrenheit_asteet

def main():
    jatko = "kylla"
    while jatko != "ei":
        rivi = input("Mina asteina annat lampotilan (C/F)? ")
        yksikko = rivi.upper()
        if yksikko == "C":
            asteet = float(input("Anna lampotila celsius-asteina: "))
            fahrenheit = muunna_fahrenheiteiksi(asteet)
            print(asteet, "C on", fahrenheit, "F.")
        elif yksikko == "F":
            asteet = float(input("Anna lampotila fahrenheit-asteina: "))
            celsius = muunna_celsiuksiksi(asteet)
            print(asteet, "F on", celsius, "C.")
        else:
            print("Virheellinen yksikko, pitaisi olla C tai F")
        rivi = input("Haluatko jatkaa (kylla/ei)? ")
        jatko = rivi.lower()

main()

Alla esimerkkiajo ohjelman suorituksesta:

Mina asteina annat lampotilan (C/F)? c
Anna lampotila celsius-asteina: 25.0
25.0 C on 77.0 F.
Haluatko jatkaa (kylla/ei)? kylla
Mina asteina annat lampotilan (C/F)? F
Anna lampotila fahrenheit-asteina: -40.0
-40.0 F on -40.0 C.
Haluatko jatkaa (kylla/ei)? kylla
Mina asteina annat lampotilan (C/F)? f
Anna lampotila fahrenheit-asteina: 92.0
92.0 F on 33.3333333333 C.
Haluatko jatkaa (kylla/ei)? Ei

Useammalle kuin yhdelle riville jakaantuvaa merkkijonoa voidaan merkitä kolmen lainausmerkin avulla, esimerkiksi

>>> teksti = """Hei, opiskelija!
... meidan Tosi on -pankistamme saat
... opintolainat edullisesti"""
>>> print(teksti)
Hei, opiskelija!
meidan Tosi on -pankistamme saat
opintolainat edullisesti
>>> mainos = '''Hei, opiskelija!
... tule suoraan omaan pankkiisi,
... ala vilkuile naapureihin'''
>>> print(mainos)
Hei, opiskelija!
tule suoraan omaan pankkiisi,
ala vilkuile naapureihin

Useammasta rivistä koostuva teksti voidaan tehdä ohjelmassa lisäämällä rivinvaihtojen kohtaan \n-merkkejä. Esimerkiksi seuraava ohjelma pyytää käyttäjältä henkilön nimi- ja osoitetiedot sekä tekee niistä merkkijonon, joka sisältää myös rivinvaihtoja.

def tee_osoite():
    print("Anna seuraavan henkilon tiedot.")
    etunimi = input("Etunimi: ")
    sukunimi = input("Sukunimi: ")
    katuosoite = input("Katuosoite: ")
    postinumero = input("Postinumero: ")
    postitoimipaikka = input("Postitoimipaikka: ")
    osoite = etunimi + " " + sukunimi + "\n" + katuosoite + "\n" + \
        postinumero + " " + postitoimipaikka.upper()
    return osoite

def main():
    print("Talla ohjelmalla voit syottaa ja tulostaa osoitteita.")
    osoitteet = []
    jatko = True
    while jatko:
        uusi_osoite = tee_osoite()
        osoitteet.append(uusi_osoite)
        vastaus = input("Haluatko antaa lisaa osoitteita (k/e)?\n")
        if vastaus.lower() == "e":
            jatko = False
    print()
    print("Antamasi osoitteet:")
    for osoitetieto in osoitteet:
        print(osoitetieto)
        print()

main()
Osoite muodostetaan siten, että nimen ja katuosoitteen sekä katuosoitteen ja postinumeron välille lisätään rivinvaihto.
Postitoimipaikka muutetaan isoilla kirjaimilla kirjoitetuksi
Ohjelma pyytää käyttäjältä uusia osoitteita
Funktiolla tee_osoite muodostettu osoite lisätään listaan osoitteet
Käyttäjän vastaus jatkokysymykseen muutetaan pieniksi kirjaimiksi, jolloin sekä e että E kelpaavat ohjelman suorituksen lopettaviksi vastauksiksi
Kun käyttäjä on antanut haluamansa määrän osoitteita, listassa olevat osoitteet tulostetaan.
print()-käskyt ilman tulostettavaa tekstiä lisäävät tyhjiä rivejä selkiyttämään ohjelman tulostusta.

Esimerkki ohjelman suorituksesta:

Talla ohjelmalla voit syottaa ja tulostaa osoitteita.
Anna seuraavan henkilon tiedot.
Etunimi: Tiina
Sukunimi: Teekkari
Katuosoite: Jamerantaival 3 C 324
Postinumero: 02150
Postitoimipaikka: Espoo
Haluatko antaa lisaa osoitteita (k/e)?
k
Anna seuraavan henkilon tiedot.
Etunimi: Teemu
Sukunimi: Teekkari
Katuosoite: Servinkuja 4 B 44
Postinumero: 02150
Postitoimipaikka: espoo
Haluatko antaa lisaa osoitteita (k/e)?
e

Antamasi osoitteet:
Tiina Teekkari
Jamerantaival 3 C 324
02150 ESPOO

Teemu Teekkari
Servinkuja 4 B 44
02150 ESPOO

Käyttäjän syötettä lukiessa halutaan usein, että käyttäjä voi lopettaa syötteen antamisen tyhjällä rivillä sen jälkeen, kun hän on antanut haluamansa määrän rivejä. Kun syötettä luetaan input-funktion avulla, tunnistetaan tyhjä rivi siitä, että funktio palauttaa arvonaan tyhjän merkkijonon "".

Seuraavassa esimerkissä luetaan käyttäjän antamia lukuja niin kauan, että käyttäjä antaa tyhjän rivin. Sen jälkeen ohjelma laskee ja tulostaa annettujen lukujen keskiarvon. Käyttäjän antama rivi muutetaan luvuksi vasta sen jälkeen, kun on ensin tutkittu, onko luettu merkkijono tyhjä. Jos muunnos tehtäisiin ennen merkkijonon tutkimista, ohjelma kaatuisi siihen, että tyhjää merkkijonoa ei voi muuttaa luvuksi.

def main():
    print("Lasken keskiarvon antamistasi desimaaliluvuista.")
    print("Lopeta tyhjalla rivilla.")
    lukujen_maara = 0
    summa = 0.0
    loppu = False
    while not loppu:
        rivi = input()
        if rivi == "":
            loppu = True
        else:
            luku = float(rivi)
            summa = summa + luku
            lukujen_maara = lukujen_maara + 1
    if lukujen_maara > 0:
        keskiarvo = summa / lukujen_maara
        print("Niiden keskiarvo on", keskiarvo)
    else:
        print("Et antanut yhtaan lukua.")

main()

Toisessa vaihtoehdossa tutkitaan while-käskyn ehdossa, onko käyttäjä antanut viimeksi tyhjän rivin. Tässä vaihtoehdossa käyttäjän syöte pitää lukea ensimmäisen kerran jo ennen toistokäskyä. Uusi syöte luetaan aina kunkin kierroksen lopuksi. Näin pidetään huoli siitä, että ohjelma ei yritä muuttaa tyhjää riviä desimaaliluvuksi.

def main():
    print("Lasken keskiarvon antamistasi desimaaliluvuista.")
    print("Lopeta tyhjalla rivilla.")
    lukujen_maara = 0
    summa = 0.0
    rivi = input()
    while rivi != "":
        luku = float(rivi)
        summa = summa + luku
        lukujen_maara = lukujen_maara + 1
        rivi = input()
    if lukujen_maara > 0:
        keskiarvo = summa / lukujen_maara
        print("Niiden keskiarvo on", keskiarvo)
    else:
        print("Et antanut yhtaan lukua.")

main()

Esimerkki ohjelman (kumman tahansa version) suorituksesta:

Lasken keskiarvon antamistasi desimaaliluvuista.
Lopeta tyhjalla rivilla.
-5.0
15.0
8.0

Niiden keskiarvo on 6.0

Toinen esimerkki, jossa käyttäjä antaa heti aluksi tyhjän rivin:

Lasken keskiarvon antamistasi desimaaliluvuista.
Lopeta tyhjalla rivilla.

Et antanut yhtaan lukua.