Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Python: Jääkiekon MM-kisat yksinkertaistettuna

Sivun loppuun

jsbasic [12.05.2016 10:08:22]

#

Jääkiekon MM-kisojen turnausformaatti yksinkertaisimmassa muodossaan. Koodilla voi testata sitä, kuinka hyvin pistelasku toimii, ja kuinka paljon se sietää epävarmuutta.

Joukkueet ovat vain kokonaislukuja, jotka edustavat niiden vahvuustasoja. Ottelut ratkaistaan normaalijakauman mukaan tuotetuilla satunnaisluvuilla. (random.gauss -funktio)

Olen käyttänyt lähes jokaisen funktion käytössä sorted-funktion kaltaista käyttöliittymää: Funktiolle syötetään tupleja vapaassa järjestyksessä, ja se palauttaa ne järjestettynä. Poikkeuksena ottelufunktion argumentit, joille joukkueet syötetään erikseen. Esim. pronssiottelu-muuttuja sisältää järjestetyn listan pronssiottelun tuloksista.

Koodirivejä on vain reilut 50. Onko tämä liiallista yksinkertaistamista?

Omien kokeiden perusteella otteluformaatti toimii hyvin, ja ainoa haaste on joukkueiden pelikohtainen varianssi.

Lisäinfoa projektista löytyy tuolta:
http://digitaalinenilluusio.blogspot.fi/2016/05/mm-jaakiekon-turnausformaatin.html

# -*- coding: utf-8 -*-

import random, itertools

#Suorittaa yksittäisen ottelun
#Palauttaa tuplen, jossa (voittaja, häviäjä)
def pelaa_ottelu_sorted(joukkue1, joukkue2):
  if(random.gauss(joukkue1 - joukkue2, 0.5) < 0):
    return (joukkue2, joukkue1)
  else:
    return (joukkue1, joukkue2)

#Suorittaa lohkolle alkusarjan pelit
#Palauttaa tuplen, jossa lohkon joukkueet järjestettynä pisteiden mukaisesti
def pelaa_alkusarja_sorted(lohko):
  pistetaulu = [0]*8
  for (id1, jo1), (id2, jo2) in itertools.combinations(enumerate(lohko), 2):
    if pelaa_ottelu_sorted(jo1, jo2)[0] == jo2:
      pistetaulu[id2]+=3
    else:
      pistetaulu[id1]+=3

  #Järjestetään pisteiden perusteella
  return [x for (y, x) in sorted(zip(pistetaulu, lohko), reverse=True)]

#Toteuttaa turnauksen
#palauttaa neljä parasta joukkuetta, järjestettynä sijoituksen mukaan voittajasta aloittaen
def turnaus_sorted(joukkueet):

  lohkoA = pelaa_alkusarja_sorted(joukkueet[0:8])
  lohkoB = pelaa_alkusarja_sorted(joukkueet[8:18])

  puolivaliera1 = pelaa_ottelu_sorted(lohkoA[0], lohkoB[3])
  puolivaliera2 = pelaa_ottelu_sorted(lohkoA[2], lohkoB[1])
  puolivaliera3 = pelaa_ottelu_sorted(lohkoB[0], lohkoA[3])
  puolivaliera4 = pelaa_ottelu_sorted(lohkoB[2], lohkoA[1])

  valiera1 = pelaa_ottelu_sorted(puolivaliera1[0], puolivaliera2[0])
  valiera2 = pelaa_ottelu_sorted(puolivaliera3[0], puolivaliera4[0])

  finaali = pelaa_ottelu_sorted(valiera1[0], valiera2[0])
  pronssiottelu = pelaa_ottelu_sorted(valiera1[1], valiera2[1])

  #jarjestetaan lopullinen ranking
  tuloslista = finaali + pronssiottelu

  return tuloslista


joukkueet = range(16)
random.shuffle(joukkueet)
print "Kaikki joukkeet:", joukkueet
print "Neljä parasta:  ", turnaus_sorted(joukkueet)

Metabolix [12.05.2016 17:55:35]

#

Nyt en ymmärrä, mikä tästä tekee mielestäsi koodivinkin. Siirrän keskusteluun.

jsbasic [12.05.2016 18:49:21]

#

Mikä siitä mielestäsi ei tee koodivinkkiä?

Oletko täysin järjissäsi? Millä oikeudella leikit täällä ylläpitäjää?

Todella turhauttavaa sinun toimintasi. Jatkuvaa inttämistä keskustelussa, käyttäjätilini poistettiin ja sitten tämä. Olen kirjoitellut koodivinkkejä jo vuonna 2003, ja ne olivat pidettyjä.

Voisitko selittää?

Metabolix [12.05.2016 19:02:04]

#

jsbasic kirjoitti:

Mikä siitä mielestäsi ei tee koodivinkkiä?

Jää täysin epäselväksi, mitä tuosta koodista olisi tarkoitus oppia. Hieno juttu, osaat laittaa taulukon satunnaiseen järjestykseen ja simuloida muutamaa ”ottelua”. Entä sitten? Tässä ei vielä ole ohjelmoinnin kannalta mitään kiinnostavaa.

Normaalijakaumasi on ihan hatusta vedetty. Osaatko edes selittää, miksi vertailu menee noin ja millaisia todennäköisyyksiä siitä tulee?

”Vinkissä” esität myös itse kysymyksen (”Onko tämä liiallista yksinkertaistamista?”), miksi? Koodivinkin pitäisi antaa vastauksia eikä kysymyksiä.

jsbasic kirjoitti:

käyttäjätilini poistettiin

Mikä käyttäjätilisi nyt on poistettu? Tässähän sinulla on käyttäjätili.

jsbasic kirjoitti:

Olen kirjoitellut koodivinkkejä jo vuonna 2003, ja ne olivat pidettyjä.

Onneksi ohjelmoinnin taso on noussut vuodesta 2003. Myöskään ”pidetty” vinkki ei ole sama asia kuin hyvä vinkki. Olet lähettänyt näköjään vain pari vinkkiä, ja yhdessäkin on tällainen hieno kommentti koodissa: ”Tässä on joitain vakioita (500, 50 ja 3) joita en oikein ymmärrä.” Kuulostaako hyvältä vinkiltä?

jsbasic [12.05.2016 19:26:36]

#

En aio kirjoittaa ammattimaisia vinkkejä ilmaiseksi. Eikä Suomesta montaa sellaista löydy, jotka tekisivät sitä sinulle. Tuskin täällä muuten olisi niin hiljaista nykyisin...

Mielestä tämän Putkan idea oli, että kaikki voi ohjelmoida. Laaksonen kirjoitti alussa hurjasti BASIC-vinkkejä, ja edelleen GOTO-käsky näkyy logossa. Se ei anna hirveän ammattimaista kuvaa 2010-luvulla. ;)

Sen sijaan koodissani oli pythonin huippukehittynyt iteraattori, joka hakee kombinaatioita itemien kesken. Ja gaussin satunnaisgeneraatio.

No, linkittämästäni blogista löytyy lisäinfoa...

Metabolix [12.05.2016 19:44:46]

#

jsbasic kirjoitti:

Mielestä tämän Putkan idea oli, että kaikki voi ohjelmoida.

Tietenkin saa ohjelmoida. Koodivinkin julkaiseminen on eri asia kuin ohjelmointi. Satunnaisia juttuja voi aivan hyvin lähettää keskusteluun. Jos taas on tarkoitus kehittää jotain ideaa pidemmällekin ja tehdä koodista uusia versioita, projektialue voi olla siihen otollisempi paikka. Koodivinkit ovat oppimista varten, ja sieltä löytyy helpommin oikeaa asiaa, jos ei tungeta sinne lisää epäselvyyksiä. Toki vinkeissä on myös kategoria ”omat tuotokset”, mutta silloin ohjelmasta saisi mielellään saada ulos muutakin kuin 16+4 lukua osittain satunnaisessa järjestyksessä.

jsbasic kirjoitti:

En aio kirjoittaa ammattimaisia vinkkejä ilmaiseksi.

Jos ei kiinnosta kirjoittaa hyvää ja perusteellista vinkkiä, älä sitten kirjoita vinkkiä. Mitään ei menetetä, koska huono vinkki ei ole juuri minkään arvoinen.

Ammattimainen on myös eri asia kuin hyvä.

jsbasic kirjoitti:

Sen sijaan koodissani oli pythonin huippukehittynyt iteraattori, joka hakee kombinaatioita itemien kesken. Ja gaussin satunnaisgeneraatio.

Et selitä näitä mitenkään etkä kuvaile näiden järkevää käyttöä, joten ei voi sanoa, että vinkkisi kertoisi näistä. Erityisesti normaalijakauman suhteen jää täysin epäselväksi, mitä edes yritetään saada aikaan. Jos 50 rivistä 1–2 riviä on asiaa, suhde ei ole vielä kovin vakuuttava.

jsbasic kirjoitti:

No, linkittämästäni blogista löytyy lisäinfoa...

Siinäpä se! Onko tarkoitus läntätä koodivinkkeihin melko epämääräisesti selitetty koodi ja ohjata muualle lukemaan lisää, vai onko tarkoitus selittää koodivinkissä asiat kunnolla? Jos kaikki tuo lisäinfo olisi ollut koodivinkissä, olisi voinut olla jotain toivoa, tosin selitykset ovat blogissakin mielestäni todella epämääräisiä ja ideat heikosti perusteltuja.

groovyb [12.05.2016 19:49:26]

#

En itsekään ymmärrä mitä vinkki koskee. Vinkin tulisi olla ratkaisu tai toteutusmalli yleisluonteiseen ongelmaan, ei "näin teet lottogenerattorin" -tyylinen snippet.

Grez [12.05.2016 20:04:53]

#

Voihan lottogeneraattorinkin laittaa vinkiksi jos se on perustellusti toteutettu.

Tuollaiset että "selitetty blogissa" on sikäli hankalia, että sitten kun blogi on jossain vaiheessa bittiavaruudessa niin vinkistä tulee vaan 404:ää.

jsbasic [12.05.2016 20:41:09]

#

En kirjoittanut koodia alunperin vinkiksi, vaan testasin pythonin ilmaisukykyä. Mutta tein sittemmin yksinkertaistetun version, joka on mielestäni suhteellisen kaunis. Jaoin sitä joillekin nettisivuille, kuten tänne - tosin kullekin sivustolle eri muodoissaan.

Tarkoitus oli, että koodi puhuu puolestaan. En julkaisuvaiheessa kiinnitänyt tekstiin suurta huomiota, koska on epävarmuutta siitä, että onko tämä Putka oikea kohderyhmä. Onko täällä niitä, jotka kaipaavat tälläistä vinkkiä. Editointimahdollisuushan täällä on, jos tämä olisi vinkinksi päätynyt.

Teuro [13.05.2016 13:50:43]

#

jsbasic kirjoitti:

#Suorittaa lohkolle alkusarjan pelit
#Palauttaa tuplen, jossa lohkon joukkueet järjestettynä pisteiden mukaisesti
def pelaa_alkusarja_sorted(lohko):
  pistetaulu = [0]*8
  for (id1, jo1), (id2, jo2) in itertools.combinations(enumerate(lohko), 2):
    if pelaa_ottelu_sorted(jo1, jo2)[0] == jo2:
      pistetaulu[id2]+=3
    else:
      pistetaulu[id1]+=3

  #Järjestetään pisteiden perusteella
  return [x for (y, x) in sorted(zip(pistetaulu, lohko), reverse=True)]

Alkusarjan otteluilla on lupa päättyä tasan, jolloin koodisi on tältä osin puutteellinen. Lisäksi kotiottelun merkitys ja muut peliin liittyvät asiat (loukkaantumiset, pelikiellot) ynnä muut sellaiset loistavat poissaolollaan. Sinällään ihan näppäriä rakenteita kielessä kieltämättä on, mutta niiden käytölle pitäisi löytyä paremmin selitettyjä perusteita.

jsbasic [13.05.2016 14:42:19]

#

Kiitos palautteesta. Nuo MM-kisojen alkusarjapelit pelataan nykyisin ratkaisuun asti. Tosin, jos ratkaisu ei tule varsinaisella peliajalla, hyvitetään hävinneelle yksi piste. Ottelu voi pisteiden osalta päättyä siis neljällä eri tavalla. Lisäksi sijoitus alkusarjassa, ja pudotuspeleista pudonneiden lopullinen sijoitus turnauksessa määräytyy monien sääntöjen mukaan. Tässä koodissa ei sellaisia ole.

Chiman [14.05.2016 11:06:25]

#

Käyttäisin joukkueen tietorakenteena dictiä tuplen tilalla. Tällöin epäselvien indeksien sijasta voidaan käyttää kuvaavia merkkijonoja. Tiedon hallinta helpottuu muutenkin.

Esimerkki:

joukkue = {'nimi': 'Suomi', 'pisteet': 0}

Tuolla rakenteella saat joukkueen kaikki tiedot yhden muuttujan taakse.

Aina on tietysti mahdollista, että nälkä kasvaa syödessä eli lisäät yksityiskohtia ja jossain vaiheessa joukkueesta onkin parasta tehdä oma luokkansa. Dict on kuitenkin hyvä ja kevyt perusrakenne tuohon, miltä toteutus nyt näyttää, pieni laajennusvara huomioiden.

jsbasic [14.05.2016 19:36:16]

#

Chiman kirjoitti:

Käyttäisin joukkueen tietorakenteena dictiä tuplen tilalla. Tällöin epäselvien indeksien sijasta voidaan käyttää kuvaavia merkkijonoja.

Ohjelmassani joukkueen numero ja vahvuustaso ovat sama asia. Niinhän ei voi olla asian laita, jos ohjelmaa haluaa laajentaa. :)

Erillisen pistetaulun käyttäminen on minusta sen sijaan perusteltua. Tuo enumeraten ja sortedin käyttö on varmaan koodin rumin kohta, mutta se ei näy funktion ulkopuolelle.

Chiman [15.05.2016 21:31:34]

#

Hahmottelin vaihtoehtoisen toteutuksen tuolle alkusarjan funktiolle, tosin testaamatta:

# Suorittaa lohkolle alkusarjan pelit
# Palauttaa tuplen, jossa lohkon joukkueet järjestettynä pisteiden mukaisesti
def pelaa_alkusarja_sorted(lohko):
  pistetaulu = dict(zip(lohko, [0] * len(lohko)))
  for jo1, jo2 in itertools.combinations(lohko, 2):
    voittaja = pelaa_ottelu_sorted(jo1, jo2)[0]
    pistetaulu[voittaja] += 3

  # Järjestetään pisteiden perusteella
  return [j for j in sorted(lohko, key=lambda x: (pistetaulu[x], x), reverse=True)]

jsbasic [15.05.2016 21:55:20]

#

Eli pistetaulu dictinä. Hyvin oivallettu!

Suunnittelin semmosta, että lohko järjestettäisiin jo tuossa samassa silmukassa, jossa pisteetkin jaetaan. Silloin sorted-funktiota ei tarvittaisi.

Olen myös yrittänyt jakaa noita alkusarjan pelejä kierroksiin. Tällä hetkellä ensimmäinen joukkue joutuu pelaamaan kaikki pelinsä peräkkäin. Tehtävä on melkoinen puzzle, johon en ole vielä algoritmia keksinyt.

Chiman [15.05.2016 22:46:49]

#

jsbasic kirjoitti:

Suunnittelin semmosta, että lohko järjestettäisiin jo tuossa samassa silmukassa, jossa pisteetkin jaetaan. Silloin sorted-funktiota ei tarvittaisi.

Todennäköisesti tuosta tulisi sekä monimutkaisempi että hitaampi ratkaisu. En suosittele.

lainaus:

Olen myös yrittänyt jakaa noita alkusarjan pelejä kierroksiin. Tällä hetkellä ensimmäinen joukkue joutuu pelaamaan kaikki pelinsä peräkkäin. Tehtävä on melkoinen puzzle, johon en ole vielä algoritmia keksinyt.

Tuossa on eräs ratkaisu siihen:
https://en.m.wikipedia.org/wiki/Round-robin_tournament

Metabolix [16.05.2016 17:04:38]

#

jsbasic kirjoitti:

Olen myös yrittänyt jakaa noita alkusarjan pelejä kierroksiin. Tällä hetkellä ensimmäinen joukkue joutuu pelaamaan kaikki pelinsä peräkkäin. Tehtävä on melkoinen puzzle, johon en ole vielä algoritmia keksinyt.

Kumma juttu, mutta joku on jaksanut tehdä ilmaiseksi koodivinkin aiheesta täyskierrosturnauksen paritus.

jsbasic [18.05.2016 22:34:13]

#

"Round-robin scheduling" on näköjään se avainsana. MM-kisaohjelman luomiseen käytetään sen yhteydessä varmasti muitakin algoritmeja...

Mutta tarkoitus oli testata Venäjän kisojen toimivuutta. Siksi kopioin niiden sarjaohjelman -- päivien mukaan.

lohkoOhjelma=(
            ((1,4),(0,2)),
            ((3,7),(5,6),(4,2)),
            ((7,0),(5,3),(1,6)),
            ((4,0),(1,2)),
            ((3,6),(7,5)),
            ((3,4),(1,7)),
            ((2,5),(0,6)),
            ((2,7),(6,4)),
            ((5,1),(0,1),(7,4)),
            ((6,2),(3,1)),
            ((0,5),(6,7)),
            ((2,3),(4,5),(0,1))
        )

Ja sitten...

for paiva in lohkoOhjelma:
  for (koti, vieras) in paiva:
    pelaa_ottelu_sorted(lohko[koti], lohko[vieras])

...ja niin voin simuloida vaikka joukkueiden väsymystä, joka aiheutuu peräkkäisistä peleistä. Voisi odottaa, että mitä samantasoisempia joukkueet ovat, sitä enemmän väsymystä aiheutuu.

jsbasic [23.09.2016 01:29:26]

#

Tulipa mieleen tämä normaalijakauma-asia, kun pelataan World Cupia.

Metabolix kirjoitti:

Normaalijakaumasi on ihan hatusta vedetty. Osaatko edes selittää, miksi vertailu menee noin ja millaisia todennäköisyyksiä siitä tulee?

Asia on selitetty Wikipediassa, mutta se avautuu paljon helpommin visuaalisesti. Kannattaa hakea videohaulla Probability Machinea.

Normaalijakauma tarkoittaa lyhyesti ilmaistuna satunnaislukujen keskiarvon jakautumista. Yleensähän on se ilmiö, että mitä enemmän satunnaislukuja on keskiarvoon laskettu, sitä "keskemmälle" se hakeutuu. Siksi gaussinkäyrä on korkea keskeltä.

Oletin algoritmissani, että jääkiekkopeli on "sattumien summa" siinä merkityksessä, että jokainen peliminuutti tasoittaa lopullista tulosta. Vastaavasti jokainen pelaaja tasoittaa tulosta. Siksi en laskisi joukkueen onnistumista yhden satunnaisluvun, vaan useiden satunnaislukujen keskiarvon mukaan. (Algoritmini laski nimenomaan onnistumista.)

Toisaalta jääkiekossa (ja etenkin jalkapallossa) tulos ratkaistaan muutaman maalin perusteella. Jokaisella minuutilla ei tehdä maaleja, jokainen pelaaja ei niitä tee (tarkoitan eniten ratkaisevaa pelaajaa kyseiselle maalille enkä sitä, jolle tarjoiltiin namupassi). Siten pelissä voisi olettaa olevan "turhia minuutteja ja pelaajia", jotka jäävät lopputuloksessa ottelun kulmakivien varjoon.

Usein vedotaan psykologiaan: Joukkueella on itseluottamusta tai on momentum-ilmiö. Kuitenkin, muutama yksittäinen maali (tai vastustajan hukattu maali) voi aiheuttaa voittoputken, jolle haetaan selitystä kaikista pelaajista. Kyse voi olla kuitenkin muutamasta peräkkäisestä onnekkaasta pompusta.

Siis, jos jääkiekko-ottelun tuloksilla on jonkinlainen todennäköisyysjakauma, niin millainen se on?

Metabolix [23.09.2016 18:30:09]

#

jsbasic kirjoitti:

Asia on selitetty Wikipediassa – –

Onneksi en kysynytkään, mikä on normaalijakauma, vaan kysyin, oletko itse tietoinen, millaisia todennäköisyyksiä tuosta kaavasta tulee. Tosin alussa tavallaan vastasitkin jo kysymykseen:

jsbasic kirjoitti:

Omien kokeiden perusteella otteluformaatti toimii hyvin, ja ainoa haaste on joukkueiden pelikohtainen varianssi.

Eli haaste on, että käyttämäsi jakauma ei vastaa otteluiden tulosten todellista (tai simulaatiossa toivottavaa) jakaumaa.

Kuvaat joukkueen hyvyyttä kokonaisluvulla. Silloin pienin mahdollinen joukkueiden ero on 1. Silloin käyttämäsi kaava antaa huonommalle noin 2 %:n mahdollisuuden voittaa. Minusta se on aika vähän. Jos joukkuiden ero on 2, huonomman voittomahdollisuus on enää sadastuhannesosia eli aivan olematon. Näillä lukemilla ottelu ei nyt todellakaan ole ”sattumien summa”, vaan lopputulos käytännössä määräytyy ennalta asetetuista joukkueiden hyvyysluvuista.

Kuulostaisi selvemmältä (ja enemmän sinunkin sanallista selitystäsi vastaavalta) määrittää kummankin joukkueen onnistuminen normaalijakauman avulla ja verrata sitten näitä onnistumisia pelin lopputuloksen määrittämiseksi:

# Joukkueen onnistuminen voi noudattaa normaalijakaumaa:
# yleensä menee oman tason mukaan, joskus paremmin, joskus huonommin
def onnistuminen(hyvyys, vaihtelu):
  return random.gauss(hyvyys, vaihtelu)

# Yksinkertainen käyttöesimerkki nykyisessä koodissasi,
# jossa ei vielä ole joukkuekohtaista vaihtelua (-> laitetaan 1) mutta
# on kuitenkin joukkuekohtainen hyvyys tiedossa:
def pelaa_ottelu_sorted(joukkue1, joukkue2):
  hyvyysluvun_kerroin = hyke = 0.4
  if (onnistuminen(joukkue1 * hyke, 1) < onnistuminen(joukkue2 * hyke, 1)):
    return (joukkue2, joukkue1)
  else:
    return (joukkue1, joukkue2)

Tuosta voi onnistumisten erotuksen perusteella päättää vaikka ottelun maalimäärän niin, että isompi onnistuminen tuo enemmän maaleja.

jsbasic [24.09.2016 16:54:29]

#

Metabolix kirjoitti:

Eli haaste on, että käyttämäsi jakauma ei vastaa otteluiden tulosten todellista (tai simulaatiossa toivottavaa) jakaumaa.

Tarkoitin turnauksen formaattia, joka on IIHF:n ylläpitämä järjestelmä. Omassa koodissani formaatti on identtinen IIHF:n kanssa. (Lukuunottamatta sitä, että muodostan alkulohkot satunnaisesti, kun taas IIHF:n järjestelmässä ne järjestyvät rankingin ja monimutkaisten sääntöjen mukaan.)

Tuo 2% on siis se määrä, jonka tuo MM-kisaformaatti sietää satunnaisuutta. Todellisuudessa satunnaisuus on paljon korkeampi, ja siksi huonompikin joukkue voi voittaa mestaruuden. Turnaukset ovat viihdettä -- eivät tieteellinen koe.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta