Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Python: Syötteen korjaaminen

Sivun loppuun

karvinen [28.01.2022 14:47:41]

#

Koodailin ensimmäistä varsinaista sovellusta ja se toimiikin ihan ok, jos syöte on numeroita.

Ohjelma on lapsen kertolaskun harjoitteluun.

jos annat vastaukseksi esim kirjaimen, niin ohjelma keskeytyy siihen.

Mites tuo kannattaisi toteuttaa, että tulisi vain ilmoitus, että väärä vastaus ja ohjelma jatkaisi suoritusta normaalisti seuraavaan tehtävään?

import random
import math


print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
vaaditutpisteet = int(input("Haluttu pistemäärä "))
alku = int(input("Mistä kertotaulusta aloitetaan "))
loppu = int(input("mihin kertotauluun lopetetaan "))
pisteet = int(0)
while pisteet <= vaaditutpisteet - 1:

        luku = random.randrange(alku, loppu)
        luku2 = random.randrange(alku, loppu)
        vastaus = int(luku*luku2)

        print(" Pisteitä on nyt ",pisteet,"\n\n")
        print(" paljonko on ",luku, " * ",luku2," ")

        annettuvastaus = int(input("anna vastaus "))

        if vastaus == annettuvastaus:
            print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","Vastaus oli oikein, yksi piste lisää")
            pisteet += 1

            print(" Annoit vastaukseksi ", annettuvastaus, "oikea vastus oli", vastaus )

        else:
            print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","väärä vastaus, yksi miinuspiste")
            pisteet -= 1

            print(" Annoit vastaukseksi ", annettuvastaus, "oikea vastus oli", vastaus )

peran [28.01.2022 15:01:13]

#

https://www.w3schools.com/python/python_try_except.asp

karvinen [28.01.2022 15:03:59]

#

Kokeilin tälläkin tyylillä, mutta ei vaikutusta.
Ilmeisesti ennen tuota toista riviä pitäisi jotenkin tutkia, onko kyseessä kokonaisluku, jos ei niin antaa sille vaikka arvon 555.

annettuvastaus = input("anna vastaus ")
        annettuvastaus = int(annettuvastaus)

Jere Sumell [28.01.2022 17:03:34]

#

Itse varmaan lähtisin ratkaisua hakemaan siten, että käsittelisin syötteen merkkijono -tyyppisenä tietona, ja sitten ohjelmoisin funktion, jonka syöteparametriksi annetaan se käyttäjän syöte.

Tarkistussyötteen tarkistuksessa, kun merkkijono-muuttujat käsitellään pythonissa taulukoina, niin helppo palauttaa boolean - tyyppinen arvo sen mukaan, sisältääkö annettu syöte "laittomia" -merkkejä, eli jotain muuta kuin 0-9. Eli vähemmällä vaivalla selviää, kun tarkistaa nuo numeraaliset arvot vaikka merkkikerrallaan toistolausekkeessa, ja sitten jos merkki ei ole numeraalinen, return lauseella palautuu sitten sitten funktion kutsun seuraavalle riville, ja sitten jos se funktion kutsu on while (!validi(mjono)) -tyyliä, niin sittenhän pääohjelma kysyy käyttäjältä syötettä niin kauan, kun käyttäjä syöttää ensimmäisen kerran pelkästään numeraalisia arvoja.

For silmukkaan en tiedä, kannattaako siihen lisätä tuon return -lauseen jälkeen continue, kun funktion suoritus päättyy välittomästi returnin kohdattuaan, ja sitten jos silmukassa on continue, niin sitten suoritus jatkuu silmukan seuraavalle kierrokselle ehtojen mukaisesti, break sitten taas pomppaisi toistorakenteesta ulos seuraavalle suoritettavalle ohjelmariville.

En kai nyt ihan hakoteillä ole, vaikka pienen tauon olenkin pitänyt täällä putkakeskusteluissa.

jalski [28.01.2022 17:57:29]

#

Jere Sumell kirjoitti:

Itse varmaan lähtisin ratkaisua hakemaan siten, että käsittelisin syötteen merkkijono -tyyppisenä tietona, ja sitten ohjelmoisin funktion, jonka syöteparametriksi annetaan se käyttäjän syöte.

Tarkistussyötteen tarkistuksessa, kun merkkijono-muuttujat käsitellään pythonissa taulukoina, niin helppo palauttaa boolean - tyyppinen arvo sen mukaan, sisältääkö annettu syöte "laittomia" -merkkejä, eli jotain muuta kuin 0-9. Eli vähemmällä vaivalla selviää, kun tarkistaa nuo numeraaliset arvot vaikka merkkikerrallaan toistolausekkeessa, ja sitten jos merkki ei ole numeraalinen, return lauseella palautuu sitten sitten funktion kutsun seuraavalle riville, ja sitten jos se funktion kutsu on while (!validi(mjono)) -tyyliä, niin sittenhän pääohjelma kysyy käyttäjältä syötettä niin kauan, kun käyttäjä syöttää ensimmäisen kerran pelkästään numeraalisia arvoja.

Pythonissahan on valmiina "isnumeric" ja "isdigit" funktiot, joilla syöte voidaan tarkastaa. Oman funktion kirjoittaminen tähän tarkoitukseen on siis ylimääräistä työtä.

Metabolix [28.01.2022 18:40:00]

#

try:
  luku = int(input("Vastaus: "))
except:
  print("Vastauksen pitää olla luku!")
  luku = "tekstiä"

Jere Sumell [28.01.2022 19:18:21]

#

Kiitos, oi suuri mestari Metabolix, että valaisit näistä Pythonin valmisfunktioista.

Itse olen varmaan joskus törmännyt noihin, mutta jäänyt vähän vähemmälle käytölle.
Olen muutoinkin pitänyt ohjelmoinnista nyt taukoa tovin.

Tässä omaa koodiani, mitä rakentelin, mutta ihmettelen sitä, miksi tuo input("?: ) -rivi oistuu loputtomiin, sama mitä käyttäjäsyöte on, eikä se tuon boolean -arvon jälkeen haaraudu mihinkään ehtolauseessa tuon isinstance-kutsun jäljiltä. Järjellä ajateltuna tuon pitäisi tulostaa syötetty luku * satunnaisesti sitten tuon randint.metodin syöteparametreina annettujen arvovälin kertolaskun tulos, jos tuon syötteen tietotyyppi on kokonaisluku int.

Tässä koodini, koodissa myös kommenttoituna omaa funktiokehitelmääni, joka sekään ei toiminut tuosta numeraalisen tiedon tarkistuksesta.

import random;

#def validi(mjono):
#	legals = range(11);
#	for x in range (len(legals)):
#		if str(legals[x]) not in mjono:
#			return True;
#	return False

while (True):
	mjono = input("?: ");
	if (isinstance(mjono,int)):
		print (int(mjono)*random.randint(0,10));
	else:
		continue;

jalski [28.01.2022 19:41:31]

#

Jere Sumell kirjoitti:

Tässä omaa koodiani, mitä rakentelin, mutta ihmettelen sitä, miksi tuo input("?: ) -rivi oistuu loputtomiin, sama mitä käyttäjäsyöte on, eikä se tuon boolean -arvon jälkeen haaraudu mihinkään ehtolauseessa tuon isinstance-kutsun jäljiltä. Järjellä ajateltuna tuon pitäisi tulostaa syötetty luku * satunnaisesti sitten tuon randint.metodin syöteparametreina annettujen arvovälin kertolaskun tulos, jos tuon syötteen tietotyyppi on kokonaisluku int.

Johtuisikohan siitä, että mjono muuttujan tyyppi on merkkijono eikä kokonaisluku?

jalski [28.01.2022 19:51:23]

#

Tuo Metabolixin laittama esimerkki on hyvä ja toimiva. Itse jotenkin Pythonia tuntematta ajattelin, että tuo olisi virheellisesti myös hyväksynyt desimaalierottimella varustettuja lukuja ja muuntanut kokonaisluvuksi.

En tiedä miksi, mutta jotenkin vierastan poikkeuksien käyttöä normaalin ohjelmalogiikan toteuttamiseen. Jotenkin omassa ajatusmaailmasssani poikkeus on sellainen virhe mistä pitäisi heittää ilmoitus ja lopettaa ohjelma... :D

Jere Sumell [28.01.2022 20:33:57]

#

On tuo minunkin mielestäni täysin totta, että tuo Metabolixin koodiesimerkki on tarpeeksi toimiva ja hieno esimerkki, jonka oivaltaminen ei vaadi lisäselvityksiä.

Tein pieniä muutoksia tuohon omaan ohjelma-koodiesimerkkiini, jonka aiemmassa postauksessa esitin, niin nythän mitä siinä käytetään tuota

mjono.isdigit() ehtolauseen tarkistuksessa, niin tämäkin kertotaulu - peli toimii:

def kertotauluPeli(mjono):

	if (len(mjono) >0) and mjono.isdigit():
		tulos = random.randint(0,10);
		print (str(int(mjono)*tulos));
		return True;
	return False;
mjono = input("?: ");
while (kertotauluPeli(mjono) == False):
	mjono = input("?: ");

Grez [28.01.2022 20:41:04]

#

Jere Sumell kirjoitti:

Jotenkin omassa ajatusmaailmasssani poikkeus on sellainen virhe mistä pitäisi heittää ilmoitus ja lopettaa ohjelma... :D

Ainoastaan käsittelemätön poikkeus on sellainen.

Jos tekee suorituskykyintensiivistä juttua niin silloin poikkeuksia on toki hyvä välttää ainakin loopissa, koska niiden käsittely on usein hitaampaa kuin tarkistaminen muilla keinoin.

Tuollaisessa käyttäjärajapinnan toiminnassa, missä saattaa tulla poikkeus harvemmin kuin kerran sekunnissa, pitäisin koodin selkeyttä tärkeämpänä, kuin mikrosekuntien säästöä.

Metabolix [28.01.2022 20:44:48]

#

Tietenkin merkkijonon saa tarkastaa etukäteen, jos haluaa. Silloin vain on tärkeää tietää, mitä tarkastaa. Jos ennakkotarkastus ei vastaa lopullisia ehtoja, ohjelma voi kaatua vialliseen syötteeseen kauniista ajatuksesta huolimatta.

Esimerkiksi yllä Jeren koodissa on isdigit-tarkastus, joka kelpuuttaa vaikka merkin ①, joka ei kuitenkaan kelpaa int-muunnokseen. Vastaavasti isnumeric hyväksyisi murtolukumerkin ¾, joka ei ole kokonaisluku.

Ilmeisesti isdecimal saattaisi vastata sitä, mikä int-muunnokseen kelpaa, (mutta en varmistanut tätä mistään dokumenteista). Tällekin tarkastukselle kelpaavat monet muutkin Unicode-merkit kuin vain ASCII 0–9.

Alla on pari Wikipediasta poimittua merkkiä ja niiden tulokset. Erityisen hauska on yhdistelmä 6६೬, joka muuttuu nätisti luvuksi 666 vaikka sisältää arkielämässä yhteensopimattomia merkkejä.

for s in ["1", "६", "①", "¾", "六", "6६೬"]:
  try:
    i = int(s)
  except:
    i = "virhe"
  print(f"int({s}) = {i}")
  print(f"{s} .isdecimal = {s.isdecimal()}")
  print(f"{s} .isdigit = {s.isdigit()}")
  print(f"{s} .isnumeric = {s.isnumeric()}")
  print()

Grez [28.01.2022 21:05:40]

#

Joo Pythonissa ei taida ole selkeää tapaa tehdä muulla, kuin poikkeuksen nappaamisella. Joissain muissa kielissä on kullekin tyyppimuunnokselle vastaava tarkistusmenetelmä ja/tai tyyppimuunnoksesta on olemassa versio, joka ei koskaan heitä poikkeusta vaan palauttaa totuusarvon, joka kertoo onnistuiko muutos.

jalski [28.01.2022 21:42:46]

#

Metabolix kirjoitti:

Tietenkin merkkijonon saa tarkastaa etukäteen, jos haluaa. Silloin vain on tärkeää tietää, mitä tarkastaa. Jos ennakkotarkastus ei vastaa lopullisia ehtoja, ohjelma voi kaatua vialliseen syötteeseen kauniista ajatuksesta huolimatta.

Itse olen ihmetellyt, miksei näissä nykyisissä "korkeamman tason" ohjelmointikielissä ole standardikirjastossa kunnollista validaattoria numeeriselle merkkijonosyötteelle? Yksinkertaisen tilakoneen avulla on kuitenkin mahdollista helposti tarkastaa onko syöte numero ja tukea kaikkia mahdollisia muotoja.

Alla validaattori numeeriselle syötteelle 8th-ohjelmointikielellä. Nykyisellään palauttaa vain validoinnin statuksen, mutta voisi hyvin myös palauttaa tiedon tyypistä viimeisen tilan perusteella:

\
\ Simple state machine based string validator for numbers.
\

ns?

ns: validate

32 constant SPACE
09 constant TAB

\ Status
0 constant EMPTY
1 constant PARTIAL
2 constant OK
3 constant ERROR

\ State
0 constant S0
1 constant IPART
2 constant FPART
3 constant ESIGN
4 constant EPART

: make-dword  \ lword hword -- dword
  0xffff n:band 16 n:shl swap
  0xffff n:band
  n:bor ;

: s1
  IPART "state" t:!
  PARTIAL "status" t:! ;

: s2
  IPART "state" t:!
  OK "status" t:! ;

: s3
  FPART "state" t:!
  PARTIAL "status" t:! ;

: s4
  ESIGN "state" t:!
  PARTIAL "status" t:! ;

: s5
  OK "status" t:! ;

: s6
  FPART "state" t:!
  PARTIAL "status" t:! ;

: s7
  ESIGN "state" t:!
  PARTIAL "status" t:! ;

: s8
  OK "status" t:! ;

: s9
  ESIGN "state" t:!
  PARTIAL "status" t:! ;

: s10
  EPART "state" t:!
  PARTIAL "status" t:! ;

: s11
  EPART "state" t:! ;

: s12
  OK "status" t:! ;

[ ( SPACE S0    make-dword "dword" t:@ n:= ) , ' noop  ,
  ( TAB   S0    make-dword "dword" t:@ n:= ) , ' noop  ,
  ('+     S0    make-dword "dword" t:@ n:= ) , ' s1 ,
  ( '-    S0    make-dword "dword" t:@ n:= ) , ' s1 ,
  ( '0    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '1    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '2    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '3    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '4    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '5    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '6    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '7    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '8    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '9    S0    make-dword "dword" t:@ n:= ) , ' s2 ,
  ( '.    S0    make-dword "dword" t:@ n:= ) , ' s3 ,
  ( 'e    S0    make-dword "dword" t:@ n:= ) , ' s4 ,
  ( 'E    S0    make-dword "dword" t:@ n:= ) , ' s4 ,
  ( '0 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '1 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '2 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '3 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '4 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '5 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '6 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '7 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '8 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '9 IPART    make-dword "dword" t:@ n:= ) , ' s5 ,
  ( '. IPART    make-dword "dword" t:@ n:= ) , ' s6 ,
  ( 'e IPART    make-dword "dword" t:@ n:= ) , ' s7 ,
  ( 'E IPART    make-dword "dword" t:@ n:= ) , ' s7 ,
  ( '0 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '1 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '2 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '3 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '4 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '5 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '6 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '7 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '8 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( '9 FPART    make-dword "dword" t:@ n:= ) , ' s8 ,
  ( 'e FPART    make-dword "dword" t:@ n:= ) , ' s9 ,
  ( 'E FPART    make-dword "dword" t:@ n:= ) , ' s9 ,
  ( '+ ESIGN    make-dword "dword" t:@ n:= ) , ' s10 ,
  ( '- ESIGN    make-dword "dword" t:@ n:= ) , ' s10 ,
  ( '0 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '1 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '2 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '3 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '4 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '5 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '6 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '7 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '8 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '9 ESIGN    make-dword "dword" t:@ n:= ) , ' s11 ,
  ( '0 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '1 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '2 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '3 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '4 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '5 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '6 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '7 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '8 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( '9 EPART    make-dword "dword" t:@ n:= ) , ' s12 ,
  ( ERROR "status" t:! ) ] var, state-table

: number
  S0 "state" t:!
  EMPTY "status" t:!
  ( "status" t:@ ERROR n:= if
      break
      2drop
    else
      nip "state" t:@ make-dword "dword" t:! state-table @ a:when
    then ) s:each
  "status" t:@ ;

ns


\
\  Demo starts here.
\

: result?  \ result -- string
  [ "EMPTY" , "PARTIAL" , "OK"  , "ERROR" ]
  caseof ;

: app:main
  " " validate:number result? . cr
  "-" validate:number result? . cr
  "5e" validate:number result? . cr
  "123.567" validate:number result? . cr
  "932e-10" validate:number result? . cr
  "29e100" validate:number result?  . cr
  "123mda45" validate:number result? . cr
  "1" validate:number result? . cr
  " -.1" validate:number result? . cr
  " -234e-100" validate:number result? . cr
  bye ;

Mukana olevan demon tulos:

EMPTY
PARTIAL
PARTIAL
OK
OK
OK
ERROR
OK
OK
OK

muuskanuikku [30.01.2022 06:25:00]

#

Grez kirjoitti:

(28.01.2022 21:05:40): Joo Pythonissa ei taida ole selkeää tapaa tehdä...

Sanot tämän ikään kuin Pythonin valitsema lähestymistapa olisi jotenkin huono tai kielen heikkous. Päin vastoin python on suunniteltu selkeys edellä ja siihen kuuluu myös poikkeusten käyttäminen.

Joidenkin muiden kielten kohdalla tosiaan vallitsee usein sellainen ajatusmaailma (devaajien parissa), että koska poikkeukset on lisätty johonkin kieleen jälkiajatuksena, niin niiden käyttäminen on pelottavaa ja tuomittavaa.

Siksi onkin vähän huvittavaa katsoa, kun poikaset yrittävät keksiä kiertoteitä ettei tarvitsisi tukeutua poikkeuksiin.

jalski [30.01.2022 10:07:29]

#

muuskanuikku kirjoitti:

Siksi onkin vähän huvittavaa katsoa, kun poikaset yrittävät keksiä kiertoteitä ettei tarvitsisi tukeutua poikkeuksiin.

Ei minulla poikkeuksia vastaan yleisellä tasolla mitään ole, mutta minusta niiden käyttäminen varsinaisen ohjelmalogiikan toteuttamiseen tekee koodista vähemmän luettavan ja tiiviissä loopissa suorituskyky on varmasti huonompi.

Grez [30.01.2022 10:34:46]

#

muuskanuikku kirjoitti:

Sanot tämän ikään kuin Pythonin valitsema lähestymistapa olisi jotenkin huono tai kielen heikkous. Päin vastoin python on suunniteltu selkeys edellä ja siihen kuuluu myös poikkeusten käyttäminen.

Toki voit kehitellä minkälaisia tulkintoja vaan, mutta tarkoitin sanoa että Pythonissa poikkeuksen käyttäminen tässä tapauksessa on selkeintä ja tuo aikaisempi kommentointini tarkistamisesta muilla keinoin koski siis lähinnä muita kieliä.

muuskanuikku kirjoitti:

Joidenkin muiden kielten kohdalla tosiaan vallitsee usein sellainen ajatusmaailma (devaajien parissa), että koska poikkeukset on lisätty johonkin kieleen jälkiajatuksena, niin niiden käyttäminen on pelottavaa ja tuomittavaa.

En toki sano etteikö suuri osa devaajista olisi toistaitoisia, mutta kyvykkäät devaajat kyllä osaavat arvioida kuvaamallani tavalla milloin poikkeuksia tulisi välttää ja milloin ei.

Ennenaikainen optimointi on myös yleinen synti, erityisesti niiden toistataitoisten devaajien keskuudessa.

Pythonissakin poikkeuksen käsittely silloin, kun poikkeus lentää, on huomattavasti hitaampaa, kuin yksinkertainen tarkistus. Jos siis suoritat loopissa miljardeja jakolaskuja ja noin joka toinen kerta jakaja on nolla, niin poikkeukseen luottava koodi on noin puolet hitaampaa, kuin nolla-jakajien tarkistaminen if-lauseella. Sitten taas kun tarkistetaan yksittäistä käyttäjän syötettä niin sillä vajaan mikrosekunnin erolla ei ole mitään merkitystä.

Sama pääperiaate pätee (lähes?) kaikkiin poikkeuksia tukeviin kieliin.

Silloin kun nopeudella ei ole merkitystä, niin koodin luettavuus mielestäni ratkaisee. Esim C#:ssä poikkeusta käyttävä versio ei ole erityisesti luettavampi, niin sitä voi jättää käyttämättäkin

//Poikkeusta käyttäen
try
{
    var luku = int.Parse(syöte);
}
catch
{
    Console.WriteLine("Virheellinen syöte");
}

//Ilman poikkeuksen käyttöä
if (!int.TryParse(syöte, out var luku))
{
    Console.WriteLine("Virheellinen syöte");
}

Jere Sumell [30.01.2022 14:04:27]

#

En tiedä, pystyykö kaikissa poikkeuksia tukevissa kielilssä heittämään poikkeusta käsiteltäväksi ylemmällä tasolla, vai onko joissain kielissä pakko poikkeus käsitellä samassa paikkaa, jossa mahdollinen poikkeus tulee heitetyksi.

Mitä "Grez" -nimimerkki antoi hyvän esimerkin poikkeusten käytön hidastavasta vaikutuksesta ohjelman suoritusajassa, tuo nolla jakolaskun tuloksena jossain paljon niitä tuloksia laskutoimituksen tuloksena saasdussa paljon laskutoimituksia suorittavassa toistorakenteessa, jollon nopeamman ohjelman suorituksen (parempi ratkaisutapa), on tarkistaa ilman poikkeusta vaikkapa ehto-lauseella sitten, aivan kuten tuossa "Grez"in samaisessa postauksessa tuossa C#-koodiesimerkissä. Tuossa esimerkissä nyt ainoastaan yhden syötteen tarkistus, jolloin suoritukseen menevä aika ei merkkitävästi varmaan ole nopeampi tai hitaampi, mutta jos puhuttaisiin merkittävästi suuremmista peräkkäisistä toimituksista, joissa voi tapahtua virheellinen syööte, niin aikaero alkaa kasvamaan suoritusajassa voisi kuvitella, jos käyttää poikkeusten käsittelyyn käytettäviä try-catch -lohkoja.

Katsoin muuten Wikipediasta "Exception Handling" -artikkelin, niin sieltä suora lainaus: "It's difficult to write a robust Python program without using its try and except keywords". Tuossakin mainitaan, että Pythonilla on vaikeaa kirjoittaa mitään täysin robustia tietokoneohjelmaa ilman, että käyttäisi noita poikkeuksia käsitteleviä avainsana-lohkoja. Tämä muuttujatyypin tarkistus, onko syöte pelkästään kokonaislukuja int-tyyppistä tietotyyppiä Pythonilla, lienee yksi noista vaikeammin ilman poikkeuksia kirjoitettavia ohjelmia, jos käyttää pelkästään Pythonin standardikirjastoja.

Aloitin joskus Ubuntu Suomen keskutelualueella keskustelusäikeen "Voiko tietokoneohjelma olla koskaan 100% robusti", kun en ole missään alan kirjallisuudessa koskaan törmännyt robustin -ominaisuusmääritelmään listattaessa niitä asioita, joita tietokoneen ohjelmakoodi oikea-oppisesti kirjoitettuna mitä ominaisuuksia sillä olisi. Varmaan täysin robustia ohjelmaa ei ole olemassakaan, mutta jollain tasolla kait ohjelmakoodi voi olla robustista.

Esimerkinä väitteelle, "Täysin robustisti toimivaa tietokone-ohjelmaa ei liene olemassakaan ainakaan toistaiseksi sellaista ei ole kukaan ihminen luonut", kun ajatellaan jotain tietokoneen käyttöjärjestelmiä, käyttis jonkinlainen tietokoneen emo-ohjelma, kun ilman minkäänlaista käyttöjärjestelmää tietokoneella ei oikein voi tehdä mitään järkeen käypää, niin jos ajatellaan jotain Linuxia, en tiedä kun sen historia alkaa vuodesta 1990, oliko ensimmäinen versio lähellä, mutta kehitystyö on niin paljon laajentunut avoimuuden vuoksi, että ei yksikään Linux-jakelu tänä päivänä kyllä ole lähellekkään robustia tietokone-ohjelmaa. Linux nyt kuvittelisi, että se voisi olla, on se ainakin enemmän mitä Windows, ja Windows nyt lähimainkaan ole, se nyt on selvää.

Grez [30.01.2022 14:18:48]

#

Jere Sumell kirjoitti:

Varmaan täysin robustia ohjelmaa ei ole olemassakaan, mutta jollain tasolla kait ohjelmakoodi voi olla robustista.

Eikös joku perus "Hello world" ole esimerkki täysin vankasta ohjelmasta.

Tai vaikka: int main() { return 0; }

On olemassa ohjelmointikieliä ja menetelmiä, joiden tavoitteena on vankkojen ohjelmien tekeminen. Esimerkiksi ohjelman oikeaksi todistaminen.

Todella yksinkertainen ohjelma on helppo kirjoittaa täysin vankaksi. Ohjelmiston monimutkaisuuden kasvaessa toki se helposti haurastuu, ellei asiaan erityisesti panosteta.

Voisi piirtää tällaisen perinteisen kolmion, josta kaksi nurkkaa voit saada, mutta et kolmea: Vankka, monipuolinen, halpa.

Jere Sumell [30.01.2022 14:41:47]

#

Grez kirjoitti:

Eikös joku perus "Hello world" ole esimerkki täysin vankasta ohjelmasta.

Tai vaikka: int main() { return 0; }

Todella yksinkertainen ohjelma on helppo kirjoittaa täysin vankaksi. Ohjelmiston monimutkaisuuden kasvaessa toki se helposti haurastuu, ellei asiaan erityisesti panosteta.

Totta, että onhan nuo, samaa esimerkkiä esitti täysin robustista ohjelmasta joku siellä Ubuntu Suomen Keskustelualueellakin vastauspostauksissa tosin BASIC - kielellä. Kommentoin sinne, ja kommentoin saman tähän, että ohjelmiahan nuokin on, mutta eipä nuo mitään kovinkaan järkeen käypää ja hyödyllistä tehtävää suorita tai palvele mitään reaalielämän ongelman ratkontaa.

Palaan vielä tuohon tietotyypin tarkistukseen sen verran, että kaiken logiikan mukaan pitäisi tämän toimia, mutta jostain syystä se suostu yli 0 -merkin pituista kokonaislukusyötettä tulkitsemaan int-tyyppiseksi, vaikka tuossa on and -operattorilla isinstance palauttaa true, jos tuo syöte on int -tyyppinen, eli en löydä mitään virhettä tästä koodista, silti ohjelma pyörii ikiloopissa while-silmukassa, koska tuo "pelaa" -funktio palauttaa joka kerta "False".

def pelaa(mjono):

	if (len(mjono) >0) and isinstance(mjono,int):
		tulos = random.randint(0,10);
		print (str(int(mjono)*tulos));
		return True;
	return False;

Sitten mitä tässä while-silmukassa -syötettä kysytään käyttäjältä niin pitkään, kunnes tuolta pelaa-funktiosta palautuu totuusarvo "Tosi".

mjono = input("?: ");
while (pelaa(mjono) == False):
	mjono = input("?: ");

Tuo lienee järkevintä tässä karvisen alkuperäisessä ongelman asettelupostauksessa toteuttaa ehkä poikkeuksien käsittelyllä, niin ohjelma toimii 100%-varmasti joka tilanteessa, ja koska käyttäjältä tarkistetaan vain yksi syöte kerrallaan, ja ihmisen syöttämä syöte, niin ohjelman suoritusnopeudessa ei ole merkittäviä hidastumia suhteessa johonkin muuhun ratkaisuun, esim lienee mitä Metabolix esitti tuota mjono.isdigit() -funktion käyttöä noista standardikirjastoista, niin lienee paras vaihtoehto, jos ei käytä poikkeuksia, mutta kuten Metabolix itsekin totesi viitaten tuohon esimerkkikoodiini, jota tuosta isdigit() -käyttäen esitin, niin sehän hyväksyy myös muitakin merkkejä ja sellaisia, jotka sitten eivät todellisuudessa ole kokonaislukuja, jolloin ohjelma kaatuu.

Tämä ohjelmakoodi, jonka seuraavassa esitän, niin on vaihtoehtoratkaisuna tuolle aiemmin esittämälleni tässä ketjussa, ja tämä on toteuttu poikkeuksia käyttäen:

def pelaa(mjono):
	try:
		mjono = int(mjono);
	except:
		return False;
	tulos = random.randint(0,10);
	print (str(int(mjono)*tulos));
	return True;

mjono = input("?: ");
while (pelaa(mjono) == False):
	mjono = input("?: ");

Alkuperäinen ketjun avaaja "karvinen", ei ole kommentoinut mitään, onko saanut ohjelman toimimaan, varmaan näitä mitä tässä nyt on esitetty, vaikka vähän soveltaen, niin saat varmasti ohjelmasi toimintakuntoon haluamallasi tavalla, epäilisin.

jalski [30.01.2022 15:26:45]

#

Jere Sumell kirjoitti:

Palaan vielä tuohon tietotyypin tarkistukseen sen verran, että kaiken logiikan mukaan pitäisi tämän toimia, mutta jostain syystä se suostu yli 0 -merkin pituista kokonaislukusyötettä tulkitsemaan int-tyyppiseksi, vaikka tuossa on and -operattorilla isinstance palauttaa true, jos tuo syöte on int -tyyppinen, eli en löydä mitään virhettä tästä koodista, silti ohjelma pyörii ikiloopissa while-silmukassa, koska tuo "pelaa" -funktio palauttaa joka kerta "False".

Vihjasin jo aiemmin, että isinstance(mjono, int) palauttaa false, koska muuttujan mjono tyyppi on merkkijono eikä int.

Itse olen sitä mieltä, että aiemmin esittämäni tilakoneeseen pohjautuva validaattori on paras ja joustavin.

Grez [30.01.2022 15:51:13]

#

jalski kirjoitti:

Itse olen sitä mieltä, että aiemmin esittämäni tilakoneeseen pohjautuva validaattori on paras ja joustavin.

Vaikka trollaatkin niin laita nyt vielä ohjeet miten tuota 8th:lla koodattua tilakonevalidaattoriasi käytetään Python-ohjelmassa.

jalski [30.01.2022 16:09:49]

#

Grez kirjoitti:

jalski kirjoitti:

Itse olen sitä mieltä, että aiemmin esittämäni tilakoneeseen pohjautuva validaattori on paras ja joustavin.

Vaikka trollaatkin niin laita nyt vielä ohjeet miten tuota 8th:lla koodattua tilakonevalidaattoriasi käytetään Python-ohjelmassa.

Niin, tuon idean varmasti saa Pythonillakin toteutettua...

Grez [30.01.2022 16:24:42]

#

Varmasti joo, mutta se olisi täysin idioottimaista. Ei siis mielestäni lähellekään vastaa väitettäsi että olisi "paras ja joustavin".

Jere Sumell [30.01.2022 16:41:25]

#

Tässä nyt alkuperäisen "karvinen" -nimimerkin samalla tavalla toimiva python -ohjelma, jota muokkasin hieman, ja lisäsin jotain koodia, että tämä toimii:

import random;

vaaditutpisteet = int(0);
alku = int(0);
loppu = int(0);
pisteet = 0;

def alku():
    global vaaditutpisteet;
    global alku;
    global loppu;

    print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
    try:
        vaaditutpisteet = int(input("Haluttu pistemäärä "));
        alku = int(input("Mistä kertotaulusta aloitetaan "));
        loppu = int(input("mihin kertotauluun lopetetaan "));
        return True;
    except:
        return False;

def tarkista(mjono):
	try:
		mjono = int(mjono);
	except:
		return False;
	return True



def peli():
    global vaaditutpisteet;
    global alku;
    global loppu;
    global pisteet;

    while (pisteet <= vaaditutpisteet - 1):

        luku = random.randrange(alku, loppu);
        luku2 = random.randrange(alku, loppu);
        vastaus = int(luku*luku2);

        print(" Pisteitä on nyt ",pisteet,"\n\n");
        print(" paljonko on ",luku, " * ",luku2," ");
        annettuvastaus = "vastaus";
        while (tarkista(annettuvastaus) == False):
            annettuvastaus = input("Anna vastaus: ");

        if (vastaus == int(annettuvastaus)):
            print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","Vastaus oli oikein, yksi piste lisää");
            pisteet += 1;

            print(" Annoit vastaukseksi ", annettuvastaus, "oikea vastus oli", vastaus );

        else:
            print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","väärä vastaus, yksi miinuspiste");
            pisteet -= 1;

            print(" Annoit vastaukseksi ", annettuvastaus, "oikea vastus oli", vastaus );
            peli();

ini = alku();
while (ini == False):
    alku();
peli();

Jere Sumell [30.01.2022 16:49:14]

#

jalski kirjoitti:

Vihjasin jo aiemmin, että isinstance(mjono, int) palauttaa false, koska muuttujan mjono tyyppi on merkkijono eikä int.

No nyt oivalsin! python tosiaan käyttäjäinputin käsittelee defaulttina String -tyyppisenä tietona, tässä nyt ratkaistuna tuo ongelmanani pitämäni koodiesimerkki siten, että toimii:

import random;

def pelaa(mjono):

	if (len(mjono) >0) and isinstance(int(mjono),int):
		tulos = random.randint(0,10);
		print (str(int(mjono)*tulos));
		return True;
	return False;

mjono = input("?: ");
while (pelaa(mjono) == False):
	mjono = input("?: ");

Tuohon piti ainoastaan tuohon ehtolauseen toiseen tarkistusehtoon pitää laittaa tuon boolean -arvon palauttavaan isinstance-metodin ensimmäisen syoteparametrin tuo mjono pitää käyttää int -metodin syoteparametrina, niin nyt ohjelma toimii!

Jere Sumell [30.01.2022 18:50:21]

#

Tässä nyt lopullinen versio alkuperäiselle vastaajalle toimii 100% -varmasti ILMAN ainuttakaan poikkeusta:

import random;

vaaditutpisteet ="";
alku = "";
loppu = "";
pisteet = int(0);

def alkuun():
    global vaaditutpisteet;
    global alku;
    global loppu;

    print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
    while (tarkista(vaaditutpisteet) == False):
        vaaditutpisteet = input("Haluttu pistemäärä ");
    while (tarkista(alku) == False):
        alku = input("Mistä kertotaulusta aloitetaan ");
    while (tarkista(loppu) == False):
        loppu = input("mihin kertotauluun lopetetaan ");
    return True;


def tarkista(mjono):

    if (len(mjono) >0) and isinstance(int(mjono),int):
        return True;
    return False;



def peli():
    global vaaditutpisteet;
    global alku;
    global loppu;
    global pisteet;


    while (int(pisteet) <= int(vaaditutpisteet) - 1):

        luku = random.randrange(int(alku), int(loppu));
        luku2 = random.randrange(int(alku), int(loppu));
        vastaus = (luku*luku2);

        print(" Pisteitä on nyt ",pisteet,"\n\n");
        print(" paljonko on ",luku, " * ",luku2," ");
        annettuvastaus = "";
        while (tarkista(annettuvastaus) == False):
            annettuvastaus = input("Anna vastaus: ");

        if (vastaus == int(annettuvastaus)):
            print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","Vastaus oli oikein, yksi piste lisää");
            pisteet += 1;

            print(" Annoit vastaukseksi ", annettuvastaus, "oikea vastus oli", vastaus );

        else:
            print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","väärä vastaus, yksi miinuspiste");
            pisteet -= 1;

            print(" Annoit vastaukseksi ", annettuvastaus, "oikea vastus oli", vastaus );
            peli();

ini = alkuun();
while (ini == False):
    alkuun();
peli();

Nyt se on sama, minkä kokonaisluku int-tyyppisen arvon käyttäjä syöttää pythonin int-muuttujatyypin arvoalueväliltä -2147483648 - 2147483647, niin toimii, ja jos syöttää jotain muuta missä kohtaa tahansa mitä kysytäänkin, niin kysyy niin kauan, kunnes ensimmäinen validi syöte annetaan.

Onko tästä jotain vielä?

jalski [30.01.2022 19:08:38]

#

Grez kirjoitti:

Varmasti joo, mutta se olisi täysin idioottimaista. Ei siis mielestäni lähellekään vastaa väitettäsi että olisi "paras ja joustavin".

Millähän perusteella? Sanotaan, että haluat kirjoittaa skannerin parseria varten mikä lukee merkki kerrallaan tekstitiedostosta. Mikä on mielestäsi nopeampi ja parempi tapa kuin tilakoneen käyttäminen, jos halutaan tukea kaikkia mahdollisia muotoja numerojen esittämiseen ja halutaan skannerin tunnistavan luvun tietotyyppi?

Grez [30.01.2022 19:30:23]

#

jalski kirjoitti:

Mikä on mielestäsi nopeampi ja parempi tapa kuin tilakoneen käyttäminen

Varmaan funktion read() käyttäminen tmv.

Lähtökohtaisesti kielen vakiotoiminnallisuuksien korvaaminen omilla ratkaisuilla on ilman jotain erikoistarvetta huono idea. Siinä tulee vain enemmän koodattavaa, virheitä, testattavaa, korjattavaa, ja mikä pahinta, enemmän ylläpidettävää.

jalski [31.01.2022 11:52:00]

#

Grez kirjoitti:

(30.01.2022 19:30:23): ”– –” Varmaan funktion read() käyttäminen tmv. ...

Jos tilakone on 13 kertaa nopeampi työkalu hommaan niin mielestäni pari sataa riviä ylläpidettävää koodia ei ole paha. Otetaan vaikka pieni haaste:

Laske tekstitiedostossa olevat luvut siten, että erottelet luvun tyypin. Luvut voi olla eroteltu pilkulla, välilyönneillä, puolipisteeellä, tabulaattorilla tai rivinvaihdolla. Jos syötteessä on virhe niin ohjelman pitää osata tarkalleen kertoa rivi ja sarake missä virhe on tapahtunut. Luvuissa pitää olla E-notaatio tuettuna myös.

Jos saat tuon toteutettua nopeammin toimivaksi ohjelmointikielen vakiotoiminnallisuudella kuin käsin kirjoitettu skanneri mihin integroituu tilakone lukujen tunnistamista varten ja mikä laskee lukumäärät suoraan skannausvaiheessa, niin hattu lähtee päästä ja syvä kumarrus! 😄

Grez [31.01.2022 11:58:04]

#

En epäile etteikö se jossain erityistilanteessa voisi olla paras ratkaisu.

Vastaavasti vaikka naulojen naulaamiseen käsipelillä vasara olisikin paras ratkaisu, niin ei voi silti sanoa että rakennushommissa yleisesti "Vasara on paras ja joustavin".

Tässä ketjussa keskusteltiin tilanteesta, jossa halutaan lukea käyttäjän syöttämä kokonaisluku. Siihen tarkoitukseen Metabolix on esittänyt täysin toimivan, selkeän, helposti ylläpidettävän ratkaisun.

Pari sataa riviä ylläpidettävää koodia (esimerkiksi) tuohon tarkoitukseen olisi edelleenkin täysin idioottimainen ratkaisu.

Jos nyt vielä kuitenkin palaan tuohon ratkaisuusi täsmäräätöityyn tehtävänantoon (jollaista todellisuudessa tuskin tulee vastaan), niin tämä kohta on vähän epäselvä:

jalski kirjoitti:

Laske tekstitiedostossa olevat luvut siten, että erottelet luvun tyypin.

Eli mitä tarkoittaa "luvun tyyppi"? Onko se esim. luonnollinen, reaali-, rationaali- vai kompleksiluku ja lasketaanko esim. luonnolliset luvut noista jokaiseen luokkaan?

Ja jos ajoista puhutaan niin viimeisen päälle viilattu suoritusnopeus on aika harvoin olennaista. 99% tapauksissa joukosta vaihtoehtoisia ratkaisuja paras on se, missä seuraava aika on pienin: Toteuttamiseen (koodaamiseen) kuluva aika + ylläpitoon järjestelmän elinaikana kuluva aika + miljoonaan suorituskertaan kuluva aika.

No toi miljoona nyt on hatusta vedetty, oikeampi kerroin saattaa olla tuhat. Paljon on tilanteita, joissa kerroin on jopa 1 tai vähemmän, mutta ei varmastikaan 99% kaikista.

jalski [31.01.2022 14:29:04]

#

Grez kirjoitti:

Jos nyt vielä kuitenkin palaan tuohon ratkaisuusi täsmäräätöityyn tehtävänantoon (jollaista todellisuudessa tuskin tulee vastaan), niin tämä kohta on vähän epäselvä:

jalski kirjoitti:

Laske tekstitiedostossa olevat luvut siten, että erottelet luvun tyypin.

Eli mitä tarkoittaa "luvun tyyppi"? Onko se esim. luonnollinen, reaali-, rationaali- vai kompleksiluku ja lasketaanko esim. luonnolliset luvut noista jokaiseen luokkaan?

Meinasin ihan vaan yksinkertaisesti, että tekstitiedostossa olisi int ja float lukuja (ei tarvitse rajoittaa luvun kokoa). Näiden lukumäärät sitten laskettaisiin vaan erikseen.

Metabolix [02.02.2022 13:31:17]

#

Jere Sumell kirjoitti:

Tässä nyt lopullinen versio alkuperäiselle vastaajalle

Turha kirjoittaa koko ohjelmaa uusiksi, jos ei ole oikeasti niin paljon korjattavaa ja parannettavaa.

Yksinkertainen muutos pyydettyyn asiaan (vastauksena annettu kirjaimia) koskee ihan alkuperäisestä koodista vain yhtä riviä, joka muuttuu seuraavasti (aiemman vinkkini mukaan):

annettuvastaus = input("anna vastaus ")
try:
  annettuvastaus = int(annettuvastaus)
except ValueError:
  pass

Tässä on jätetty muut kuin ValueError käsittelemättä, jotta muunlaiset virheet kuten syötteen loppuminen ja ohjelman sulkeminen eivät aiheettomasti katoa.

Jos vastaavasti halutaan korjata muutkin luvun lukemiset, voidaan tehdä uusi funktio ja käyttää sitä. Funktiossa voi lukea syötettä, kunnes tuloksena on kelvollinen luku.

# Koodin alkuun:
def input_int(pyyntö):
  while True:
    try:
      return int(input(pyyntö))
    except ValueError:
      print("Pitää antaa luku!")

# Myöhemmin koodissa muutetaan kaikki input-kohdat, esimerkiksi:
vaaditutpisteet = input_int("Haluttu pistemäärä ")
annettuvastaus = input_int("anna vastaus ")

Jere Sumell kirjoitti:

toimii 100% -varmasti ILMAN ainuttakaan poikkeusta

En tiedä, mitä horiset ja oletko edes testannut koodiasi. Koodissasi ei ole yhtään oikeaa tarkastusta ja se kaatuu jokaisessa syötteenluvussa, jos antaa syötteeksi kirjaimia. Eli et ole edes ratkaissut aloittajan esittämää ongelmaa.

Tekemäsi isinstance-tarkastus on 100% virheellinen tarkastus. Jos rivillä oleva int-muunnos onnistuu, isinstance on aina True (koska selvästikin int on int). Jos int-muunnos ei onnistu, ohjelma kaatuu eikä isinstance-tarkastusta koskaan tehdä. Hassua, että aiemmin teit oikein try-rakenteeseen perustuvan version mutta sitten kaivoit jostain tämän virheellisen idean.

Muita vikoja koodissa on väärä puolipisteiden käyttö (Pythonissa ei käytetä puolipisteitä rivien lopussa), globaalien muuttujien käyttö, tarpeeton int muuttujan alustuksessa ja, kun kerran esimerkistä on kyse, tarpeeton pitkien \n-rimpsujen käyttö oikean ruuduntyhjennysfunktion tai tällaisen viritelmän tapauksessa edes lyhyemmän "\n"*50-merkinnän sijaan.


Sivun alkuun

Vastaus

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

Tietoa sivustosta