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.SisendVä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.SisendVä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.SisendVä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
Lehekülg viimati muudetud November 19, 2024, at 07:55 PM