Kokeen 9.11.1999 tehtävien ratkaisuja ja arvosteluperusteet ============================================================== Näitä ilmestyy tänne lähipäivinä lisää. Tehtävä 1 (Liisa Marttinen): ============================ Kirjoita ohjelma, joka tulostaa alla olevan mallin mukaisen kolmion. Tulostettavan kolmion korkeus riveinä ja tulostukseen käytetty merkki annetaan komentorivillä. Z ZZZ ZZZZZ ZZZZZZZ ZZZZZZZZZ ZZZZZZZZZZZ Ratkaisuja: ----------- int main(int argc, char *argv[]){ int i,j,r; char c; /*varmistetaan argumenttien oikea määrä*/ if (argc !=3) return 1; /*muutetaan annettu korkeus merkkimuotoon */ r = atoi(argv[1]); c = *argv[2]; /*tulostettava merkki*/ /* tulostetaan kolmio*/ for (i=1; i<=r; i++){ /*korkeuden ilmoittama määrä rivejä*/ for (j=r-i; j>=0; j--) printf(" "); /* kullakin rivillä korkeus - rivinro tyhjää alussa*/ for(j=2*i-1; j>0; j--) printf("%c",c); /*sitten 2*rivinnumero-1 merkkiä*/ printf("\n"); /* rivinvaihto siirtää seuraavalle riville*/ } return 0; } /*Ratkaisussa tarkistetaan vain argumenttien oikea määrä. Jos määrä ei täsmää, lopetetaan eikä tehdä mitään. marttine@kuutti:~/c99/harjtyot$ a.out marttine@kuutti:~/c99/harjtyot$ a.out 20 A A AAA AAAAA AAAAAAA AAAAAAAAA AAAAAAAAAAA AAAAAAAAAAAAA AAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Jos komentorivillä annetaan 0 tai negatiivinen luku, tulostussilmukkaa ei suoriteta kertaakaan. Jos luku on liian suuri, niin kuvio on sotkuinen Jos merkkijonossa on muita kuin numeroita, niin atoi() muuttaa merkkijonoa kokonaisluvuiksi niin kauan kuin löytää numeroita. marttine@kaurakari:~/fshome/c99/harjtyot$ a.out 5AA B B BBB BBBBB BBBBBBB BBBBBBBBB Jos komentorivillä annetaan useita merkkejä, niistä käytetään tulostukseen vain ensimmäistä. Muut jätetään huomiotta. marttine@kuutti:~/c99/harjtyot$ a.out 12 ABCD A AAA AAAAA AAAAAAA AAAAAAAAA AAAAAAAAAAA AAAAAAAAAAAAA AAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAA Mistään virheestä ei anneta virheilmoitusta. */ Sama tehtävä hieman eri tavalla ratkaistuna: #include int main(int argc, char **argv){ int i,j,r; char c; if (argc !=3) return 1; r = atoi(*(++argv)); c = **(++argv); for (i=1; i<=r; i++){ for (j=r-i; j>=0; j--) putchar(' '); for(j=2*i-1; j>0; j--) putchar(c); putchar('\n'); } return 0; } Lähes sama vielä kerran: #include int main(int argc, char *argv[]){ int i,j,r; char c; if (argc !=3) return 1; r = atoi(argv[1]); c = argv[2][0]; for (i=1; i<=r; i++){ for (j=r-i; j>=0; j--) putchar(' '); for(j=2*i-1; j>0; j--) putchar(c); putchar('\n'); } return 0; } Tehtävän arvostelusta: ---------------------- Komentorivin argumenttien käyttö: * Jos ei ole lainkaan käyttänyt komentorivin argumentteja, vaan esim. lukenut korkeuden ja merkin näppäimistöltä, taulukosta tms, niin tehtävästä voi saada korkeintaan 6 pistettä. * int main (int lkm, char *taulu[]) pelkästään ei toimi, vaan edellyttää määrittelyjä: #define argc lkm #define argv taulu -1 p * komentorivin argumentteihin otetaan mukaan myös ohjelman nimi: kolmio 10 X => argc = 3, argv[0]-> kolmio, argv[1]->10, argv[2)->X Tämän unohtamisesta esim. argc !=2 tai atoi(argv[0]) -1p tai -2p jos kautta linjan * argumenttilista argv[] on osoitinlista; osoittimet osoittavat argumentteina oleviin merkkijonoihin siis merkki = argv[2][0], merkki = (*argv+2)[0], merkki = *argv[2], merkki = *(*argv+2) ja lkm = atoi(argv[1]) esim. merkki = argv[2] => -1 p * tulostettavien rivien lukumäärä on merkkimuodossa ja tulee muuttaa kokonaisluvuksi ennenkuin sitä käytetään. Yksinkertaisimmillaan muunnos tehdään ANSI-standardin kirjastofunktiolla int atoi(char *string). - jos oletti, että annetaan korkeintaan yksi numero (siis korkeus 0-9), niin -1p. - merkkimuotoisen tiedon käyttäminen sellaisenaan => -2 p - virhe muuntamisessa => -1 Omien fuktioiden laatiminen silloin, kun käytettävissä on valmis kirjastofunktio ei ole oikein C-mäistä, mutta tästä ei ole kuitenkaan otettu pois pisteitä. * tarkistuksista - argumenttien oikea lukumäärä on aina tarkistettava jos tätä ei tehdä, niin väärä määrä johtaa segmentation fault -virheeseen -1 p - negatiivinen tai liian suuri tulostettavien rivien määrä ja merkkien lukumäärä: riippuen toteutuksesta nämä virheet joko aiheuttavat tai eivät aiheuta ongelmia. Jos ohjelmassa ei ole muita virhetarkastuksia ja negatiivinen lukumäärä => ei tulosteta mitään liian suuri määrä =>jos ei mahdu tulostusalustalle, tulee sekava kuvio liian monta merkkiä => käytetään vain ensimmäistä niin virhetarkastusten puuttuminen ei vie pisteitä. * muista virheistä - käytetään return -lauseen tilalla break-lausetta breakia voi käyttää vain silmukoiden tai switchin keskeyttämiseen. Se ei keskeytä koko ohjelman suoritusta. - return puuttuu -1p - määrittelemätön muutuja -1p - määrittelyt väärässä kohtaa (C:ssä määrittelyt lohkon alussa) -1p * ohjelman logiikka eli kolmion tulostaminen oikealla tavalla - karkeasti kolme osaa: oikea määrä rivejä, oikea määrä tyhjiä ja oikea määrä haluttua merkkiä rivillä Kukin osa vastaa 2 pistettä. Jos osa täysin pielessä tai puuttuu kokonaan -2p. Jos pieni virhe, -1 p. - * muuta jos on hyvin hankala ja sekava ohjelma, niin on voitu vähentää 1p. Ainakaan tällaisesta ei anneta täysiä pisteitä. ================================================================================ Tehtävä 2 (Mikko Rauhala): ========================== a) Kirjoita funktio char *reverse(char *s), joka kääntää syötteenä saamansa merkkijonon päinvastaiseen järjestykseen eli viimeisen alkion ensimmäiseksi, toiseksi viimeisen toiseksi, jne. Esimerkiksi merkkijono "oli synkkä ja myrskyinen syysyö" kääntyy merkkijonoksi "öysyys neniyksrym aj äkknys ilo". /* Tehtävä 2a */ #include char *reverse(char *s) { char *alku, *loppu; alku = s; loppu = s + strlen(s) - 1; while (alku < loppu) { char tmp = *alku; *alku = *loppu; *loppu = tmp; alku++; loppu--; } return s; } b) Tee pääohjelma, joka käyttäen a-kohdan funktiota kääntää syötteen jokaisen rivin päinvastaiseen järjestykseen. Rivien järjestys kuitenkin säilyy ennallaan. /* 2b, luettavan rivin pituudeksi oletetaan max. MAXPITUUS-1 merkkiä */ #include #include #define MAXPITUUS 81 char *reverse(char *); int main(void) { char puskuri[MAXPITUUS]; while (fgets(puskuri, MAXPITUUS, stdin) != NULL) { int viimeinen = strlen(puskuri)-1; if (viimeinen >= 0 && puskuri[viimeinen] == '\n') puskuri[viimeinen] = 0; puts(reverse(puskuri)); } return EXIT_SUCCESS; } /* 2b, luettavalle merkkijonolle varataan tarvittaessa muistia dynaamisesti lisää, rivinpituutta ei rajoitettu */ #include #include #define MAXPITUUS 80 char *reverse(char *); int main(void) { char *puskuri; int merkki, i=0, koko=16; if ((puskuri = malloc(koko)) == NULL) { perror("malloc"); return EXIT_FAILURE; } while ((merkki = getchar()) != EOF) { if (i >= koko) { if ((puskuri = realloc(puskuri, koko*=2)) == NULL) { perror("realloc"); return EXIT_FAILURE; } } if (merkki == '\n') { puskuri[i] = 0; puts(reverse(puskuri)); i = 0; } else { puskuri[i++] = merkki; } } return EXIT_SUCCESS; } Tehtävän 2 arvostelusta: ------------------------ Sekä a- että b-kohtaan päteviä seikkoja: * Osoittimien väärinkäyttö -1p - -2p * Sekalaiset C-syntaksivirheet useimmiten -1p / virhetyyppi (ei returnia, esittelemättömiä tai väärässä paikassa esiteltyjä muuttujia...) Erityisesti a-kohtaan liittyvää: * Lievästi virheellinen loopin lopetusehto -1p * Merkkijonoa käännettäessä lopettava '\0'-merkki käsitelty myös -1p * Merkkijonon loppuun oletettu '\n' -1p (Ei sakotettu, jos loppumerkiksi tunnistetaan myös '\0') * Eriasteiset loogiset virheet vakavuudesta riippuen (Ei pisteitä, jos funktio ei tee mitään tehtävänantoon viittaavaa) Erityisesti b-kohtaan liittyvää: * Ei varattu merkkijonolle muistia ollenkaan -2p * EOF-tarkastus puuttuu tai ei toimi (esim. getcharilla chariin lukeminen) -1p * Rivien määrä rajoitettu -1p Korjatessa papereihin on voitu merkitä suluttamalla tai eksplisiittisesti mainiten ylimääräisiä turhuuksia, joita ei kuitenkaan ole laskettu varsinaisiksi virheiksi. ========================================================================= Tehtävä 3 (Lauri Alanko): ========================= Merkkijono on talletettu yksisuuntaiseen linkitettyyn listaan niin, että listan jokaiseen alkioon on talletettu yksi merkki ja linkki seuraavaan alkioon. Kirjoita tarvittavat rakenteet (structures) listan ja sen alkioiden käsittelemiseen. Kirjoita funktio pali, joka kertoo, sisältääkö sille parametrina annettu lista palindromin vai ei. Palindromi on merkkijono, joka on sama luettuna etuperin ja takaperin. Tunnettu palindromi on 'saippuakauppias', mutta myös 'innostunut sonni' ja 'sinä ja jänis' ovat palindromeja, sillä välilyöntejä ei yleensä huomioida. #include #include typedef struct _List List; struct _List { char value; List* next; }; int pali(const List* head){ char *buffer, *a, *b; const List* l; int len; for (l = head, len = 0; l != NULL; l = l->next) if (!isspace(l->value)) len++; buffer = malloc(len); if (buffer == NULL) return -1; for(l = head, b = buffer; l != NULL; l = l->next) if (!isspace(l->value)) *b++ = l->value; for (a = buffer, --b; (a < b) && (*a == *b); ++a, --b); free(buffer); return !(a < b); } Pisteytysperusteita Kielenmukaisuus 1 - 3 Muistinkäsittely 1 - 2 Suorituslogiikka 2+ Listankäsittely 4+ Tyyppimäärittelyt 2 ============================================================================= Tehtävä 4(Jaakko Kyrö): ======================= Kirjoita ohjelma copy, joka kopioi tiedostosta toiseen kaikki muut rivit paitsi tietyillä merkeillä alkavat. Komentorivin argumentteina annetaan kaksi tiedostonimeä ja merkkijono, joka sisältää joukon merkkejä. Ensimmäisestä tiedostosta kopioidaan toiseen tiedostoon kaikki ne rivit, joiden ensimmäinen merkki ei ole argumenttina annetussa merkkijonossa. Esimerkiksi copy file1 file2 /# kopioi tiedostosta file1 tiedostoon file2 kaikki muut rivit paitsi ne, jotka alkavat merkillä / tai merkillä #. Jos merkkijono on tyhjä eli annetaan vain kaksi tiedostonimeä, niin ensimmäisen tiedoston kaikki rivit kopioidaan toiseen tiedostoon. copy file1 file2 Voit olettaa, että riveillä on jokin maksimipituus. Tarkista myös komentorivin parametrien oikeellisuus. #include #include int main (int argc, char **argv) { int merkki; /* lukumuuttuja */ FILE *in, *out; int i; printf("Argc: %d\n",argc); if (argc<3 || argc>4) { printf("Käyttö: kopioi lähde kohde [merkit]\n"); return 1; } if ((in=fopen(argv[1],"r"))==NULL) { printf("Tiedoston %s avaus epäonnistui!\n",argv[1]); return 1; } if ((out=fopen(argv[2],"w"))==NULL) { printf("Tiedoston %s avaus epäonnistui!\n", argv[2]); fclose(in); return 1; } if (argc==3) { while ((merkki=getc(in))!=EOF) putc(merkki, out); return 0; } merkki=getc(in); /* Kopiointisilmukka, rivi kerrallaan */ while (merkki!=EOF) { for (i=0;i