Kuusijuhla

15. lokakuuta 2014 - 21.12

Yliopiston käytävillä (sekä fyysisillä että virtuaalisilla) kuului alkuviikosta legendaa jonkun keksineen ohjelmointiongelmaksi kirjoittaa koodi, joka piirtäisi ruudulle joulukuusen. Tämä tehtävä suoritetaan siis tekstinä, jolloin kuusta voidaan mallintaa vaikkapa #-merkeillä.

           #
          ###
         #####
          ###
         #####
        #######
         #####
        #######
       #########

Ohjelmalle tulee siis kertoa, kuinka monta kerrosta puuhun halutaan ja tämän jälkeen se piirtää halutun kokoisen kuusen. Ongelma ei ole mikään maailman vaikein ja sen pystyy suorittamaan ihminen, joka on käynyt, esimerkiksi, jonkin ohjelmoinnin perusteet -kurssin. Ohjelmoinnin hauskuus on siinä, että ratkaisuja on rajaton määrä. Toiset ovat nopeita, toiset hitaita. Toiset selviä, toiset kryptisiä. Ennen kaikkea jotkin ratkaisut ovat lyhyempiä kuin toiset. Ohjelmoinnin (leikkimieliset) kilpailut keskittyvät moniin asioihin, mutta useasti kyse on siitä kuka pystyy kirjoittamaan lyhimmän koodin ratkaisemaan ongelman X. Esimerkiksi, Brainfuck-tulkki pystytään C-kielellä koodaamaan alle kahdellasadalla merkillä.

Koodin lyhyyttä voidaan mitata yleisesti merkkimäärällä, mutta myös koodirivien määrää käytetään. Jälkimmäinen on kuitenkin huonompi mittari, sillä monet ohjelmointikielet sallivat, että koko ohjelma on periaatteessa koodattu yhdelle riville. Ei siinä ole mitään järkeä, mutta se on mahdollista.

Tällä kertaa joulukuusi piti kuitenkin ohjelmoida Pythonilla, joka ei salli tällaista yhdelle riville kaiken kirjoittamista – periaatteessa. Vaikken itse tehnytkään ensimmäistä versiota, niin heitän sen tähän alkuun kuitenkin. Se on niin pitkä ja huono :)

1. level = 15
2. counter = 1
3. i = 1
4. while True:
5.    s = ""
6.    for j in range(0, 10 - round(i/2) - counter % 2):
7.        s += " "
8.    for j in range(0, i):
9.        s += "#"
10.    print(s)
11.    if counter % 3 != 0:
12.        i += 2
13.    else:
14.        i -= 2
15.    counter += 1
16.   
17.    level -=1
18.    if not level:
19.        break

Itse tykkään koodailla PHP:llä, joten olin jo kerennyt unohtaa Pythonin monet hienoudet ja erikoisuudet, joten välilyönnit ja #-merkit oli tulosteltu ruudulle for-silmukoilla vaikka “#”*x olisi ollut huomattavasti simppelimpi vaihtoehto. Ratkaisin ongelman niin, että piirsin kuusta koko ajan leventäen, kuitenkin niin, että mikäli rivi oli kolmella jaollinen, mentiinkin kapeammaksi. Oma ratkaisuni oli siis noin 20 riviä koodia. Suht selkeää ja yksinkertaista, mutta niin pitkää…

Timo oli itse asiassa ensimmäinen, joka ongelman ratkaisi, mutta seuraavassa on hänen ratkaisunsa numero kaksi.

1. def makeString(length, chars):
2.        s = ""
3.        s = s+ chars*"#"
4.        for i in range(0, (length-chars)/2):
5.                s = " " + s + " "
6.        return s
7. trees = 15
8. rows = trees*3
9. l = [1, 3, 5]
10. while len(l) < rows :
11.        l += [x for x in range (l[len(l)-2], l[len(l)-1]+2+1) if not x%2==0 ]
12. for i in l:
13.        print makeString(rows, i)

Kuten huomaamme, on koodia vähemmän, noin 15 riviä. Timon ratkaisussa piirrettävän rivin rakentaminen ja kuusen rivien erot on eriytetty kahdeksi erilliseksi ongelmaksi. Ratkaisu on erilainen ja vähintäänkin lyhyempi. Toisaalta huomataan myös, että rivi 11 on pituudeltaan jo pidempi kuin yksikään ensimmäisen ratkaisun rivi. Sen minkä toisaalla voittaa, toisaalla menettää. Seuraavan ratkaisun tarjosi Jussi. Ohjelman pituus oli pudonnut kolmasosaan, sillä ratkaisumalli oli jälleen aivan erilainen. Jussi lähti liikenteeseen ajatuksesta, että puu kasvaa koko ajan alaspäin ja kerralla piirretään kolme kerrosta puusta. Tämän jälkeen ei tarvitse murehtia puun muodosta eikä kolmella jaollisuudesta, joten koodista tuli huomattavasti lyhyempi ja se on silti edelleen suht luettava.

1. def main(offset = 15, charset = 1, cutoff = 0):
2.    while offset > cutoff:
3.        print(" "*offset+"#"*charset+"\n"+" "*(offset-1)+"#"*(charset+2)+"\n"+" "*(offset-2)+"#"*(charset+4))
4.        charset = charset + 2
5.        offset = offset - 1
6. main()

Pekka katseli asiaa hetken ja totesi, että nuoremmille pitää nyt näyttää vähän mallia ja lyhensi koodia muuttamalla puun kasvatuksen toistorakenteeseen.

1. def main(offset = 15, cutoff = 0):
2.    for x in range (offset,cutoff,-1):
3.            print(" "*x+"#"*((offset+1-x)*2-1)+"\n"+" "*(x-1)+"#"*(((offset+1-x)*2-1)+2)+"\n"+" "*(x-2)+"#"*(((offset+1-x)*2-1)+4))
4. main()

Yllätys ei liene suuri, että rivin pituus alkoi näissä ratkaisuissa jo kasvaa entisestään. Ratkaisu oli kuitenkin lyhyempi kuin mikään edellisistä.

Tämä ei kuitenkaan Timoa tyydyttänyt, vaan hän lähti vielä yhdeksi illaksi liikenteeseen “challenge accepted” -asenteella ja tarjosi aamulla ratkaisuksi seuraavaa.

1.    (lambda trees=8, l=[1, 3, 5]: print( "\n".join( [ ( lambda length, chars: "".join([" " for i in range(0, int((length-chars)/2)) ]) + chars*"#" ) (trees*3, x) for x in (lambda: l if [ ( lambda : l.extend([ x for x in range(l[-2], l[-1]+3) if not x%2==0 ]) ) () for y in range(0, trees) ] else None) () ] ) ) ) ()

Eli loppujen lopuksi kuusen piirtämiseen Pythonilla vaadittiin kuin vaadittiinkin vain yksi rivi koodia. Timo oli pessyt tohtorit ja tutkijat. Toisaalta, jos koodin luettavuus ja selkeys olisi kriteereissä korkeammalla, niin ehkä tässä koodissa voisi olla jotain sanomista. Mutta oli miten oli: Timon ratkaisu oli lyhin. Siis riveissä. Merkeissä voiton vei Pekka.

Mitä tästä opimme? Ohjelmointi on mukavaa puuhaa ja leikkikenttää on käytössä rajattomasti :)

Lisää uusi kommentti