Välikokeen viides tehtävä

Mallivastaus

Tehtävän anto

a) Kirjoita luokka cHavainto jota voidaan käyttää kuten seuraavassa pääohjelmassa. Kirjoitettava koko luokka (attribuutit, kaikki metodit yms.) Riittää kuitenkin tehdä "demovastaus", eli ei tarvitse pitää kirjaa mitä erikokoisia havaintoja on olemassa, vaan "toivotaan" että käyttäjä poistaa saman kokoisia "möykkyjä" kuin on laittanutkin. (5p)

int main(void) {
//...
}


b) Voiko luokka cHavainto pitää kirjaa pienimmästä ja suurimmasta arvosta ilman taulukkoa? Perustelut! (1p)

A-kohta

tarkoitus oli tehdä ilman taulukkoa, mutta osa ei tätä keksinyt tehtävän annon perusteella.

Tarvittavat metodit ja (de)konstruktorit olivat:

cHavainto()
~cHavainto() //vain dyn. taulukko
void lisaa(double)
void lisaa(const cHavainto &) //puuttui monelta!
void poista(double)
void tulosta();

"normaali" toteutus

yleiset virheet:

puuttuva metodi/metodi on täysin toimimaton
pikkuvirhe keskiarvon laskemisessa
division by zero (nollalla jako)
havaintojen lukumäärä saattoi mennä negatiiviseksi
algoritmi huono
tarpeeton attribuutti
metodi vaatii ristiriitaisia tai vääriä parametrejä (ei toimi kuten tehtävän annossa)

Nollalla jakaminen oli monilla tulostuksessa, kun havainto_lkm == 0:
cout << "havaintoja: " << havainto_lkm;
cout << "keskiarvo: "  << lampotilasumma/havainto_lkm << endl;

Arvon poistamisessa piti olla tarkistus ettei havainto_lkm mennyt negatiiviseksi:
void poista(double lampotila) {
	if (havainto_lkm == 0) {
		lampotilasumma = 0;
		return;
	}
	havainto_lkm--;
	lamptilasumma -= lampotila;
}


Koska arvostelussa painotettiin erityisesti suunnittelua (oliopohjaisuus, toimivuus jne), vaadittiin joko selkeää algoritmiä tai hyvää perustelua sille miksi epäselkeää käytettiin. Algoritmistiikkaahan käytiin ilmeisesti tämänkin kurssin alussa läpi (näköjään tarpeeseen tuli).

selkeä:
class cHavainto {
	double lampotilasumma;
	double havainto_lkm;
public:
	cHavainto() {
		lampotilasumma = 0;
		havainto_lkm = 0;
	}

	//...

	double keskiarvo() const {
		if (havainto_lkm == 0) {
			return 0;
		} else {
			return lampotilasumma/havaintolkm;
		}
	}
	//...

	void lisaa(const cHavainto &hav) {
		havainto_lkm = hav.havainto_lkm;
		lampotilasumma = hav.lampotilasumma;
	}
}

epäselkeä:
class cHavainto {
	double keskiarvo;
	double havainto_lkm;
public:
	cHavainto() {
		keskiarvo = 0;
		havainto_lkm = 0;
	}

	//...

	//Esim:
	void lisaa(const cHavainto &hav) {
		keskiarvo = ((keskiarvo * havainto_lkm)
			+ (hav.keskiarvo * hav.havainto_lkm))
			/ (hav.havainto_lkm + havainto_lkm);
		havainto_lkm = hav.havainto_lkm;	
	}
}


Niinikään oli yleistä että siellä oli havainto_lkm, lampotilasumma _ja_ keskiarvo attribuutteina. Keskiarvo voitiin laskea kahdesta edellisestä, joten ilman perustluja tuosta tuli pikkuruinen pistemenetys. Keskiarvon erillistä ylläpitämistä voi käyttää optimointiin (lasketaan vain silloin kun se on välttämätöntä), mutta tätä kukaan ei ollut tehnyt oikein, eikä ainakaan perustellut.
joillakin konstruktorin otsikko oli tämän näköinen: cHavainto(int lkm = 0, double lamposumma = 0, double keskiarvo = 0) Tämä on väärin, koska luokan toiminta voidaan rikkoa asettamalla kaksi ensimmäistä ja niistä laskettavissa olevan keskiarvon arvo ristiriitaisesti.

Taulukkototeutus

Sen lisäksi että taulukkototeutus oli tarpeettoman monimutkainen tehtävän annon huomioon ottaen, siihen oli vaikea tehdä samanlaista toiminnallisuutta kuin tuohon ei-taulukkoversioon, koska tämä oli niin virhealtista.

yleisimmät virheet:

virhe taulukkolaskussa
menee taulukon yli
poistaa mahd. yli yhden arvon
ylim public-juttuja
poista-algoritmi huono/ei toimi

poista-metodi, virheet:

Muistin varaamisesta new-operaattorilla

Taulukkototeutuksessa käytettiin usein new-operaattoria. Seuraavassa pieni selostus siitä kuinka CBuilder 5.0:ssa homma pitäisi hoitaa:


// ensin esitellään osoitin:
double *taulu;

try {
	//täällä käytetään new:tä, pyydetään muistia 
	//10-alkioiselle double-taulukolle:
	taulu = new double[10];
}
catch (std::bad_alloc) {
	// tänne tullaan jos muistia ei saatu varattua.
	cerr << "Muistia ei saatu varattua." << endl;
	return;
}

Vanhemmat kääntäjät eivät heitä mitään, vaan sijoittavat osoittimeen nollan jos muistinvaraus ei onnistunut:
double *taulu;
taulu = new double[10];
if (kissa == 0) {
	cerr << "Muistia ei saatu varattua." << endl;
	return;
}
Muistihan vapautetaan deletellä (ei eroa kääntäjien välillä):
delete [] taulukko;

konstruktorissa oli niinikään sellaisia ratkaisuja joissa havaintojen lukumäärä, keskiarvo tms. tuotiin parametrinä, mutta taulukosta ei kerrottu mitään. Taulukkototeutuksessahan nuo arvot lasketaan tietysti taulukoitujen havaintojen perusteella.

B-Kohta

vastaukset ja niistä annetut pisteet:

"Kyllä voi. Pidetään attribuutteja double suurin ja double pienin yllä, ja tarkastetaan aina kun lisätään arvo, että oliko se ennätyksellinen"
0.5p

"Ei voi. Perustelut: vaikka käytettäisiin arvoja double suurin, double pienin, tulee ongelma siinä vaiheessa kun suurin/pienin arvo poistetaan. Tällöinhän ei ole mitään mahdollisuutta tietää, mikä oli se toiseksi suurin/pienin arvo, koska sitä ei enää löydy mistään"
1p

"kyllä voi, linkitetyllä listalla!"
0p