(toiminnot)

hwechtla-tl: Listakeräelmien käyttötapauksia

Kierre.png

Mikä on WikiWiki?
nettipäiväkirja
koko wiki (etsi)
viime muutokset


Tässä osiossa esitellään erilaisia tapoja käyttää listakeräelmiä halutun tuloksen saamiseen. Kun siis edellisessä osiossa lähdettiin siitä, että on jonkinlainen listakeräelmä, josta yritetään ymmärtää, mitä se tekee, tässä osiossa lähdetään siitä, että on jokin tarve, johon vastaava listakeräelmä yritetään kehittää (keksiä).

Sopivan listakeräelmän keksiminen (samoin kuin minkä tahansa sopivan toistorakenteen keksiminen) on monimutkainen taito, jossa harjaantuu pikku hiljaa. Taito perustuu siihen, että joko on nähnyt aiemmin samanlaisia tarpeita ja osaa soveltaa niitä käsillä olevaan ongelmaan tai sitten osaa oikeasti tehtävän vaatimuksista johdella ohjelman, jota siihen tarvitaan. Niin tai näin, esimerkit ovat keskeisessä asemassa taidon oppimisessa.

Osio on jaoteltu (karkeasti) sen mukaan, millaisesta tiedonkäsittelystä on kysymys.

Listan elementtien muunnos

Tämä on triviaalein tapaus. Kun halutaan käydä jokin iso aineisto läpi ja tehdä sen osille jokin muunnos, vakioresepti on seuraava:

  1. hajotetaan aineisto halutunlaisiksi paloiksi
  2. käydään nämä palat läpi listakeräelmällä
  3. jos tarpeen, yhdistetään tuloslista takaisin joksikin

Esimerkiksi:

' '.join([nimi[0].upper() + nimi[1:].lower() for nimi in kokonimi.split(' ')])

Jos kokonimi = "PANU aleksanteri KAlliokoski", niin kokonimi.split(' ') on ['PANU', 'aleksanteri', 'KAlliokoski']. nimi-muuttuja käy siis läpi eri nimet. Jokaisesta se muodostaa muunnoksen, jossa ensimmäinen kirjain on iso ja loput pieniä, siis keräelmän tulos on ['Panu', 'Aleksanteri', 'Kalliokoski']. Lopuksi nämä yhdistellään välilyönnein, tulos on "Panu Aleksanteri Kalliokoski". (Määritelmä on muuten turha: Pythonin merkkijonojen .title()-palvelu tekee saman homman.)

Toiseksi esimerkiksi seuraava laskee lukujen neliöiden summan:

sum([x * x for x in luvut])

Kolmanneksi esimerkiksi seuraava laskee tarkoituksen elämälle, maailmankaikkeudelle ja kaikelle (ja kerää ne, tietenkin, listaan):

[tarkoitus(asia) for asia in ['elämä', 'maailmankaikkeus', 'kaikki']]

Ylipäänsä, tällaisissa tilanteissa listakeräelmä on muotoa [... muuttuja ... for muuttuja in lista]. Muunnos voi tietenkin olla kuinka monimutkainen tahansa ja usein sitä varten kannattaa kirjoittaa oma ohjelmansa (kuten yllä funktio tarkoitus()).

Listasta valikointi

Jos halutaan löytää aineistosta tietty osa, tarvitaan if-alilausetta. Esimerkiksi seuraava hakee aineistosta sanat, joiden pituus on 3:

[sana for sana in aineisto if len(sana) == 3]

Etsintäehto voi taas kerran olla vaikka kuinka monimutkainen. Seuraava hakee suvustani henkilöt, jotka ovat biologisia tai kasvattilapsiani:

[hlo for hlo in mun_suku if isa(hlo) == mina or aiti(hlo) == mina or
                            elamankumppani(isa(hlo)) == mina or
                            elamankumppani(aiti(hlo)) == mina]

Tai usein kannattaa määritellä ehto erillisenä ohjelmana. Seuraava etsii kunnolliset aineistot (mitä se tarkoittaneekin) aineistojen listasta:

[aineisto for aineisto in aineistot if kunnollinenko(aineisto)]

Ylipäänsä, tällaisessa tapauksessa keräelmä on aina muotoa [muuttuja for muuttuja in lista if ... muuttuja ...].

Kahden aineiston yhdistäminen rinnan

Tämä ei ole kovin yleinen tarve. Tämä on pikemminkin johdatus seuraavaan tapaukseen (aineiston yhdistämiseen itseensä rinnan). Joka tapauksessa, joskus on tarvetta tuottaa uusi lista siten, että syötteenä käytetään kahta listaa, jotka käydään läpi "samaan tahtiin": tuloslistan 1. elementti riippuu syötelistojen 1. elementeistä, 2. elementti syötelistojen 2. elementeistä ja niin edelleen.

Esimerkiksi käy vektorien pistetulo. Kahden vektorin pistetulon saa kertomalla vektorien komponentit keskenään ja summaamalla nämä tulot. Esimerkiksi [3, 5, -1] · [2, -2, 5] = 3 * 2 + 5 * (-2) + (-1) * 5 = 6 - 10 - 5 = -9. Tarvitaan siis tapa tuottaa listakeräelmällä vektorikomponenttien tulot, ja siihen tarvitaan syötteeksi molempia vektoreita. Käy näin:

sum([komp1 * komp2 for komp1, komp2 in zip(vektori1, vektori2)])

zip()-funktio ottaa kaksi listaa [e1, e2, ...] ja [e3, e4, ...] ja tuottaa niistä "yhdistelmälistan" [(e1, e3), (e2, e4), ...], joka siis sisältää pareiksi yhdistettyinä syötelistojen elementit. Esimerkiksi tämä on totta:

zip([3, 5, -1], [2, -2, 5]) == [(3, 2), (5, -2), (-1, 5)]

for muuttuja1, muuttuja2 -rakenne osaa lukea näistä pareista arvot eri muuttujiin pari kerrallaan.

Toinen esimerkki: ajatellaanpa, että meillä on kaksi listaa, ja haluamme katsoa, onko niissä jossain kohdassa sama elementti. Sen voi tehdä seuraavasti siten, että kerätään yhdistellään listat pareittain zip():llä, käydään parit läpi ja katsotaan, ovatko elementit samat, kerätään nämä totuusarvot listaan, ja katsotaan, onko tässä listassa mikään totuusarvo tosi (True):

True in [e1 == e2 for e1, e2 in zip(lista1, lista2)]

Kolmas esimerkki: ajatellaanpa, että haluamme numeroida jossakin listassa olevat rivit. Tähän tarvitsemme listan numeroita, jonka voi tuottaa range()-funktiolla (esim. range(5) == [0, 1, 2, 3, 4]). Lauseke näyttää tältä:

[str(n+1) + ". " + rivi for n, rivi in zip(range(len(rivilista)), rivilista]

Selitys: range(len(rivilista)) tuottaa rivilistan pituisen sarjan numeroita. Tämä zip()ataan yhteen vastaavien rivien kanssa ja kun esim. n = 0 ja rivi = 'hiphei', niin (str(n+1) + ". " + rivi) on "1. hiphei".

Koska numerointi on sen verran yleinen tarve, Pythonissa on myös funktio enumerate(lista), joka tuottaa saman kuin zip(range(len(lista)), lista).

Kun kaksi aineistoa yhdistetään rinnan, lauseke on yleistä muotoa:

[... muuttuja1 ... muuttuja2 ...
       for muuttuja1, muuttuja2 in zip(aineisto1, aineisto2)]

Aineiston yhdistäminen itseensä rinnan

Usein aineistoa tarvitsee käydä läpi siten, että tarkastellaan kunkin elementin suhdetta edelliseen tai seuraavaan elementtiin. Oletetaan vaikkapa, että meidän pitää löytää sanojen listasta kaikki sanat, joita seuraa sana "ahjo". Käy näin:

[sana for sana, seuraava_sana in zip(aineisto, aineisto[1:])
      if seuraava_sana == 'ahjo']

Selitys: jos aineisto = ['tämä', 'on', 'ahjo', 'joka', 'toimii'], niin aineisto[1:] = ['on', 'ahjo', 'joka', 'toimii'], joten zip(aineisto, aineisto[1:]) tuottaa listan pareista, jossa jokaisessa parissa on aineiston sana ja sitä seuraava sana:

[('tämä', 'on'), ('on', 'ahjo'), ('ahjo', 'joka'), ('joka', 'toimii')]

(zip() katkaisee tuloksen lyhimmän listan mittaiseksi.) Niinpä, kun sana = 'on' ja seuraava_sana = 'ahjo', if-ehto täyttyy ja tuloslistaan tulee sana 'on'. Koko keräelmän tuloksena on lista ['on'].

Toinen esimerkki: halutaan tarkistaa, onko lista nousevassa aakkosjärjestyksessä eli onko jokaisen listan sanan perässä sana, joka tulee aakkosissa myöhemmin. Onnistuu siten, että keräämme näiden vertailujen tulokset listaan ja katsomme, ettei siellä ole yhtään poikkeusta (tilannetta, jossa seuraava sana ei ollutkaan aakkosjärjestyksessä myöhemmin):

False not in [sana1 < sana2 for sana1, sana2 in zip(aineisto, aineisto[1:])]

Jos esimerkiksi

aineisto = ['aa', 'pee', 'see']
niin
zip(aineisto, aineisto[1:]) == [('aa', 'pee'), ('pee', 'see')]
joten listakeräelmän tulos
[sana1 < sana2 for sana1, sana2 in zip(aineisto, aineisto[1:])] ==
['aa' < 'pee', 'pee' < 'see'] == [True, True]
eli jokainen sanapari oli aakkosjärjestyksessä. Tuloslista ei siis sisällä False-arvoja eli koko lista oli aakkosjärjestyksessä. (Saman testin voi tosin tehdä sanomalla aineisto == sorted(aineisto). Isolla aineistolla esittelemämme metodi on ainakin periaatteessa nopeampi.)

Kolmas esimerkki. Olkoon meillä lista pisteitä, joka määrittää avoimen polun (eli siksakviivan, joka kulkee näiden pisteiden kautta). Koko polun pituuden saa summaamalla eri pisteiden välisten matkojen pituudet eli kaavalla

sum([etaisyys(piste1, piste2) for piste1, piste2 in zip(polku, polku[1:])])

On huomattava, että aineistosta voi tutkia myös vierekkäisten elementtien sijaan esim. elementtejä, jotka ovat kahden kohdan päässä toisistaan sanomalla zip(aineisto, aineisto[2:]). Mutta jos tarvitaan mielivaltaisen kaukana toisistaan olevien kohtien yhdistelyä, tarvitaan ristiinyhdistämistä, josta puhutaan kahdessa seuraavassa osiossa.

Kahden aineiston yhdistäminen ristiin

Aineiston yhdistäminen itseensä kaikkina yhdistelminä

Kahden aineiston yhdistäminen upotetuilla listakeräelmillä

Aineiston osien pilkkominen pienemmiksi

Upotetun aineiston käsittely upotetuilla listakeräelmillä


Listakeräelmien käyttö


Pikalinkit:


kommentoi (viimeksi muutettu 11.11.2008 15:39)