Sisend ja väljund
Eesti Informaatikaolümpiaadi ülesannetes antakse programmile sisend ette standardsisendi kaudu ja oodatakse väljundit standardväljundist (kui ülesande tekstis ei ole sätestatud teisiti). Inimkeeles on standardsisend see, mida saab programmi jooksutades "käsitsi" sisse tippida, ja standardväljund see, mida programm ekraanile välja trükib.
Võib-olla sattusid sa sellele lehele, kuna võistlusserver soovitas pärast 0 punkti saamist seda lehte külastada. On väga oluline rõhutada, et esitatud programmide testimine ja hindamine toimub automaatselt. See tähendab, et ülesande tekstis kirjeldatud sisendi ja väljundi vorminguid tuleb täpselt järgida. Sellele on vaid üks erand: vastus loetakse korrektseks, kui ridade lõpus on täiendavaid tühikuid vmt.
Vaatleme näiteks üht lihtsat ülesannet.
1. Kahe arvu liitmine
Sisendi esimesel real on täisarv A. Sisendi teisel real on täisarv B. Trükkida väljundisse nende summa A + B.
Näide. | Sisend | Väljund |
---|---|---|
2 5 | 7 |
Mõned õpilased kirjutavad oma esimesel informaatikaolümpiaadil selle lahenduseks midagi sellist:
# KATKINE KOOD, MITTE KASUTADA a = int(input("Sisesta esimene arv: ")) b = int(input("Nüüd pane teine arv: ")) print(a + b)
Selline lahendus saaks serveris 0 punkti. Miks? Sest server näeb, et standardväljundisse on kirjutatud
Sisesta esimene arv: Nüüd pane teine arv: 7
See ei ole aga sama, mis õige vastus, milleks on lihtsalt
7
Teisisõnu, serveri jaoks paistavad need "Sisesta nüüd see arv" tekstid välja täpselt samasugused, nagu ülesande tegelik vastus. Lahenduseks on neid mitte kirjutada:
a = int(input()) b = int(input()) print(a + b)
Analoogiliselt ei saa punkte, kui print(a + b)
asemel kirjutada print("Vastus on", a + b)
või muud taolist.
Toome veel ühe näite.
2. Arvude jada liitmine
Sisendi esimesel real on täisarv N. Sisendi teisel real on N täisarvu A_1, A_2, ... , A_N. Väljastada summa A_1 + A_2 + ... + A_N.
Näide. | Sisend | Väljund |
---|---|---|
4 3 5 1234 35 | 1277 |
Siin on veel üks tüüpviga.
# KATKINE KOOD, MITTE KASUTADA n = int(input()) jada = [] for i in range(n): x = int(input()) jada.append(x) summa = 0 for i in range(n): summa += jada[i] print(summa)
Seekord saab lahendus jälle null punkti ja seekord on kommentaariks "Programmi täitmine ebaõnnestus, nullist erinev veakood". Mis siis nüüd viga?
Pöörame tähelepanu ülesande tekstis kirjeldatud sisendi vormingule. Jada A elemendid on kõik ühel real ja tühikutega eraldatud. Esimene kord, kui programm jõuab reale, kus on x = int(input())
, loeb programm sisse terve selle rea 3 5 1234 35
ja üritab kogu rida täisarvuks teisendada. See muidugi ei õnnestu, ja Python annab veateate.
Sisendi vormingut tuleb täpselt järgida. Kui arvud, sõnad jne on tühikutega eraldatud, siis on tühikutega eraldatud. Kui on eraldi ridadel, siis on eraldi ridadel. Toimiv lahendus sellele ülesandele oleks:
n = int(input()) jada = input().split() # split tükeldab stringi jadaks tühikute ja muude tühemike järgi jada = [int(x) for x in jada] # see teisendab kõik jada elemendid täisarvudeks summa = 0 for i in range(n): summa += jada[i] print(summa)
Või lühemalt:
n = int(input()) jada = [int(x) for x in input().split()] print(sum(jada))
Ka väljundi vormingut tuleb täpselt järgida. Siin on üks näide kolmandast tüüpveast.
3. Jagajad
Sisendi esimesel real on täisarv N. Väljastada ühele reale kasvavas järjetuses kõik täisarvud, millega N jagub.
Näide. | Sisend | Väljund |
---|---|---|
12 | 1 2 3 4 6 12 |
Võib-olla kirjutab keegi lahenduseks midagi sellist:
# KATKINE KOOD, MITTE KASUTADA n = int(input()) jagajad = [] # saab ka kavalamalt, aga hoiame näite lihtsana for d in range(1, n + 1): if n % d == 0: jagajad.append(d) print(jagajad)
Ülesande tekst ei ütle küll otse, et jagajad tuleb trükkida tühikutega eraldatult, aga näitest on näga, et seda oodatakse. Praegusel kujul kood trükib välja
[1, 2, 3, 4, 6, 12]
Tegelikult peaks väljund olema aga
1 2 3 4 6 12
Toimiv kood oleks selline:
n = int(input()) jagajad = [] for d in range(1, n + 1): if n % d == 0: jagajad.append(d) for jagaja in jagajad: print(jagaja, end=" ") print()
See paneb küll viimase jagaja järele ka tühiku, aga see on OK: testimissüsteem ei tee väljundi lõpus olevatest täiendavatest tühikutest numbrit.
Viimaseid ridu saab ka lühemalt:
print(*jagajad)
Lause print(*jagajad)
tähendab, et oleks justkui kutsutud print(jagajad[0], jagajad[1], ...)
.
Mõnikord kirjutavad õpilased lahenduse kirjutamise ajal print-lausetega enda jaoks muud abiinfot. Näiteks summa ülesadnes võib-olla teeb mõni nii:
# KATKINE KOOD, MITTE KASUTADA n = int(input()) jada = [int(x) for x in input().split()] print(jada) # tahtsin näha kas ikka õigesti teisendati print(sum(jada))
Nüüd aga on jälle väljundis
[3, 5, 1234, 35] 1277
mitte lihtsalt
1277
mis seal tegelikult olema peaks.
Selle probleemi ennetamiseks tasub igasugune selline lisainfo kirjutada nn veaväljundisse:
import sys n = int(input()) jada = [int(x) for x in input().split()] print(jada, file=sys.stderr) print(sum(jada))
Niimoodi file=sys.stderr
parameetriga kirjutatud väljund prinditakse oma arvutis joostes samamoodi ekraanile, aga see läheb tehniliselt teise kohta ja testimisserveri jaoks on sinna kirjutatu nähtamatu. Sellegipoolest ei tasu ka sinna liiga palju kirjutada: kui kirjutatavate andmete maht on väga suur, siis võtab see kirjutamine ka omajagu aega. C++ keeles on sys.stderr
vasteks std::cerr
.
Mõned tavalisemad mustrid
Python
# esimesel real arv n n = int(input()) # teisel real n täisarvu # selgitus: input() loeb sisendist ühe rea # split() tükeldab selle tühikute juures stringide listiks # map teeb listi iga elemendi peal int() # list teeb asja jälle tavaliseks arvude listiks tagasi arr = list(map(int, input().split())) # kui map liiga segane tundub, siis võib ka nii: arr = [int(x) for x in input().split()] # kolmandal real üks sõne s = input() # väljundisse arvude listi kirjutamine # tärn tähendab, et oleks kirjutatud justkui print(arr[0], arr[1], arr[2], ...) print(*arr) # ujukomaarvu väljastamine etteantud täpsusega really_small = 1.0 / 1_000_000 # lihtsalt print(really_small) tegemine trükib ekraanile 1e-06, mis ei pruugi serverile meeldida # lisaks on mõnes ülesandes palutud väljundis väljastada täpselt X kohta pärast koma # see rida trükib arvu täpselt 8 kohta pärast koma print(f"{really_small:8f}")
C++
Kõige mugavam on kasutada cin
ja cout
#include <iostream> #include <vector> using namespace std; // using namespace std kasutamine võib nii mõndagi C++ programmeerijat häirida, kuna // see nimeruum sisaldab väga palju igasuguseid asju. suuremates projektides ei tohiks // niimoodi teha. // võistlusprogrammeerimises on selle kasutamine OK, kuna koodi eluiga on lühike // ja kokkulangevuste endi risk ka madal. // kui oma koodi kuskile StackOverflowsse postitada, tasub see rida profülaktika mõttes // eemaldada. int main () { // esimesel real arv n, teisel real n täisarvu int n; cin >> n; // cin loeb iga arvu järgmise tühemikuni (tühikud, reavahetused jms) // seega pole selle koodi jaoks oluline, kas need n täisarvu on kõik // järgmisel real või igaüks eraldi real või hoopis muudmoodi vector<int> arr (n); for (int i = 0; i < n; i++) { cin >> arr[i]; } // kolmandal real üks sõne string s; cin >> s; // sõnesid saab lugeda täpselt samamoodi, nagu arve // sisendi kuni "lõpuni" lugemine vector<string> other_input; string x; while (cin >> x) { // kui sisend otsa lõppeb, siis tsükkel katkeb other_input.push_back(x); } // väljundisse kirjutamine for (int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << '\n'; // ujukomaarvu väljastamine etteantud täpsusega double really_small = 1.0 / 1'000'000; // lihtsalt cout << really_small << '\n'; tegemine trükib ekraanile 1e-06, mis ei pruugi serverile meeldida // lisaks on mõnes ülesandes palutud väljundis väljastada täpselt X kohta pärast koma // see rida trükib arvu täpselt 8 kohta pärast koma cout << fixed << setprecision(8) << really_small << '\n'; // 0.00000100 }
Ridade kaupa lugemine:
#include <iostream> #include <sstream> using namespace std; int main () { // mõnikord on vaja lugeda sisse terve rida, teadmata, mitu sõne/arvu/jne seal on. // näiteks on reale kirjutatud "0 118 999 881 999 119 725 3" string line; getline(cin, line); // nüüd sisaldab line muutuja seda, mis sellele reale enne kirjutatud oli // kuidas sealt realt nüüd need arvud kätte saab? istringstream line_stream(line); // nüüd saab line_stream kasutada samamoodi, nagu cin vector<int> arr; int x; while (line >> x) { arr.push_back(x); } }
Kiirem sisend ja väljund
Python
Funktsioonid input()
ja print()
on tegelikult teatud situatsioonides üsna aeglased. Väga suurte sisendite ja väljundite korral võib nendega ajahätta jääda. Nende asemel võib kasutada sys.stdin.readline()
ja sys.stdout.write()
.
C++
C++ keeles on kasulikud järgmised read, mis võib panna näiteks main
-funktsiooni algusesse. Nende kasutamine teeb cin
ja cout
abil sisendi-väljundi kirjutamise märksa kiiremaks.
ios::sync_with_stdio(false); cin.tie(nullptr);
Kui programmis kasutada ios::sync_with_stdio(false)
, siis ei tohi selles programmis kasutada C-stiilis sisendit ja väljundit (printf()
, scanf()
jt), kuna neid ei hoita enam C++-stiilis sisendi ja väljundiga (cout
, cin
jt) sünkroonis.
Selgitust selle kohta, mida teeb cin.tie(nullptr)
, vaata
siit.
NB: EIO-l ja sarnastel informaatikavõistlustel võib neid funktsioone kasutada ilma midagi kartmata (peale ülal juba toodud möönduste). "Tõsisemates" projektides tasub aga läbi mõelda, kas need on selle projekti jaoks ikka õiged. Tegemist on mingis mõttes konfiguratsiooniga, ning on olemas head põhjused, miks need read ei ole "vaikimisi konfiguratsioon".
Samuti ei tasu reavahetuse väljastamiseks kasutada endl
, kuna lisaks reavahetuse väljastamisele tühjendab see väljundpuhvri, mis on jälle aeglane. Selle asemel võib kasutada lihtsalt '\n'
. Erandiks on loomulikult interaktiivsed ülesanded.
Muud nõuanded
Testimisel sisendi käsitsi kopeerimine on veaohtlik. Lisaks võib tekkida soov testida oma arvutis programmi mingi väga suure sisendiga (näiteks selleks, et testida, kas on piisavalt kiire).
Kui salvestada sisend faili input.txt
, siis käsurealt järgmised käsud käivitavad programmi ja saadavad selle faili sisu programmile justkui oleks see klaviatuurilt kirjutatud:
g++ lahendus.cpp -o lahendus ./lahendus < input.txt
Windowsi Command Promptis on ./lahendus
asemel lahendus.exe
, aga muidu sama (Powershellis on küll teine süntaks). Pythonis kirjutatud lahenduste korral samamoodi:
python lahendus.py < input.txt