Labori materjal
- Slaidid: Sõned
- Näide: mõningad lihtsamad tähemärkide omistamised
Esitamisele kuuluvad ülesanded
Selles tunnis on 2 ülesannet, millest esimeses õpime tundma string.h teeki ning teises manipuleerime tähemärke käsitsi.
Ülesanne 1: string.h teegiga tutvumine
Selle ülesande käigus loome ühe programmi, mille käigus tutvume peamiselt erinevate string.h teegis olevate funktsioonidega.
Nõuded
- Programmi koostamiseks liigu samm-sammult juhendis antud järjekorras.
- Programmi käivitades küsitakse kasutajalt parooli. Enne õige parooli sisestamist ei tohi programm edasi minna.
- Kasutajalt loetakse lause. Programm kuvab mitu tähemärki lauses oli (sh tühikud, kirjavahemärgid).
- Kasutajalt loetakse otsingufraas. Seejärel väljastatakse kas eelnevalt sisestatud lauses see fraas esines või mite (jah/ei vastus).
- Kasutajalt küsitakse 2 sõna, mida kasutatakse lause moodustamisel.
- Sõnade tüübid võid ise valida (nt nimi, ese, omadussõna, tegusõna, …)
- Kleebi kasutaja poolt sisestatud sõnad kokku, lisades enda poolt täiendavaid sõnu moodustamaks vähemalt neljast sõnast koosneva lihtlause.
- Üks kasutaja sisestatud sõnadest peab olema selle lauses esimene sõna.
- Moodustatav lause tuleb salvestada täiesti uude tähemärgimassiivi, mis peab mahutama selle lause ka siis, kui loetud 2 sõna olid maksimaalse lubatud pikkusega.
Abifunktsioon
Kui sul tekib ühel või teisel hetkel probleeme aru saamisega mis sul tekstimassiivi sisuna salvestatud on, siis võid seda funktsiooni kasutada. Antud funktsioon trükib välja tervikliku sõne, misjärel trükitakse kõik tähemärgid ja selle vastavad ASCII tabeli täisarvulised väärtused. Nii on hea lihtne tuvastada kui näiteks mõni reavahetus või muu imelik sümbol jääb massiivi sisse.
1 2 3 4 5 6 7 8 9 10 11 |
void DebugString(char str[]) { int i = 0; printf("String is: '%s'\n", str); while (str[i] != '\0') { printf("str[%d] = %3hhu %c\n", i, str[i], str[i]); i++; } printf("\n"); } |
Juhend
1. samm: kasutaja sisestuse lugemine
Esimese sammu teeme tunnis koos läbi! Selle käigus loome me 2 vajalikku funktsiooni oma programmi.
Alustame esimesest mida kasutame sõne lugemiseks. Lahenduses on oluline, et me suudaksime lugeda mitmetest sõnadest koosnevaid sõnesid ehk tühik ei tohiks meil lugemist ära lõpetada! Selleks on mitmeid viise, kuid meie läheneme ülesandele kasutades funktsiooni fgets() . Soovi korral võid ise teist teed minna.
Funktsiooni fgets() eripäradeks on, et ta on mõeldud lugemaks failist. Küll aga kõik asjad on failid, sh ka klaviatuurilt tulev andmevoog, seega saame kasutada failina stdin nimelist faili. Teine keerukus antud funktsiooni juures on see, et ta vajab endale pikkust palju tohib lugeda – see on tegelikult ohutuse tagamiseks, et puhvri pikkusest üle ei loetaks. Kolmas ja kõige probleemsem on see, et kui me vajutame klaviatuurilt enter klahvi, siis see reavahetus \n salvestatakse samuti sinna massiivi.
Lähenemises kasutame põhimõtet, et loome funktsioonidele wrapperid ehk ümbritseme need täiendavate lausetega, mis funktsiooni kasutamise meeldivamaks või mugavamaks muudavad meie jaoks.
Meie ümbrisel on vaja kahte sisendit – sõnet ehk tähemärgimassiivi kuhu loetava teksti salvestame ning kui suur see tekstimassiiv loomise hetkel oli, et me lubatud pikkusest üle ei kirjutaks ja programmi rünnata sedasi ei lubaks.
Antud lahenduses olen jätnud kolme kohta küsimärgid sisse, kus pead ise lüngad täitma! Vihjeks: kui loetud stringi pikkus on 10 tähemärki, siis indeksiga 8 on meie jaoks viimane oluline tähemärk mille kasutaja sisestas. Sellele järgneb meile ebasoovitav reavahetus, millest peame lahti saada, asendades selle sõne lõpu sümboliga.
Vajadusel kasuta varasemalt välja toodud abifunktsiooni!
Selleks, et funktsioon ka kompileeruks olen need 2 täiendavalt vajalikku rida välja kommenteerinud. Olles küsimärgid asendanud kommenteeri need sisse.
1 2 3 4 5 6 7 8 9 10 11 |
void GetString(char str[], int max) { // Read the string from keyboard fgets(str, max, stdin); // TODO: Find the length of the actual string we just read // size_t len = ???; // TODO: Write the string terminator in place of the newline to fix the string // str[ ??? ] = ???; } |
Kui lugemine on valmis, loome järgmise ümbrise oma vastloodud GetString() funktsioonile. Nii saame mugavalt sisestust küsida!
1 2 3 4 5 |
void PromptString(char str[], int max, char prompt[]) { printf("%s: ", prompt); GetString(str, max); } |
Nüüd kui soovime mõnda sõnet lugeda, saame oma väljakutse luua üsna mugavalt. Näiteks kui meil on tähemärgimassiiv sentence[] mille pikkus on defineeritud makroga MAX_STR , saame väljakutse luua
1 |
PromptString(sentence, STR_MAX, "Please enter a sentence"); |
2. samm: lause lugemine ja selle pikkus
Loe kasutajalt sisse lause. Seejärel leia ja väljasta sisestatud lause pikkus.
3. samm: fraasi otsimine
Lisa programmi funktsioon, milles küsitakse kasutajalt otsingufraas. Programm väljastab seepeale kas see fraas eksisteeris varasemalt sisestatud lauses või mitte.
Jah-ei vastuseks piisab kui kontrollida tagastust järgneval kujul
1 2 3 4 5 6 7 8 |
if (strstr() != NULL) { } else { } |
4. samm: parooli küsimine
Lisa programmi funktsioon, mis küsib kasutajalt parooli. Näiteks:
1 2 3 4 5 6 |
void PromptPassword(char correctPassword[]) { char userEnteredPassword[STR_MAX]; // Write your loop for password prompt here } |
Parooli küsimine peab olema tsüklis ja küsima kasutajalt parooli senikaua, kuniks kasutaja sisestab korrektse parooli. Parooli kontroll peab olema tõstutundlik (st suuri ja väiketähti ei võrdsustata). Soovi korral võid panna programmi vale parooli puhul vihjeid andma või end sulgema pärast korduvalt parooli valesti sisestamist.
5. samm: lause moodustamine
Lisa programmi funktsioon, mille käigus moodustad lihtlause. Kuna funktsioonile meil head sisendit ega tagastust anda ei ole, võiksid alustada funktsiooni sedasi (void-void funktsioonid on erandlikud ja enamasti tuleb neid vältida!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void FormulateSentence(void) { // String where the final sentence will be held char sentence[ ??? ]; // Strings for the two user-entered words // Prompt the user for the two words // Formulating the final sentence // Print the final formulated sentence printf("Result: %s\n", sentence); } |
Mõttekoht: kui pikk peaks olema sentence massiivi pikkus, et see mahutaks halvimal juhul ära mõlemad kasutaja sisestatud sõnad ning sinu lisatavad sõnad, tühikud ja kirjavahemärgid, et moodustada lauset? Suurus võib olla liigkaudne aga peab olema piisav!
Edasi mõtle välja millist lauset moodustada soovid. Oluline on, et selles lauses oleks kaks lünka, kuhu kasutaja sisestab enda soovitud sõnad (ise otsustad millised – nt inimese nimi, ese, nimisõna, tegusõna, …). Üks nendest sõnadest peab olema lause esimene sõna, teise asukoht on sinu enda otsustada. Näiteks <sõna1> on <sõna2> nimi! .
Olles kasutajalt sõnad pärinud ja programmi sisse lugenud tuleb sul need sõnad lauseks kokku kleepida. Kokku kleebitav lause peab olema salvestatud täiesti uude tühja tähemärgimassiivi. Oluline on arvestada, et mis iganes kasutaja sisestab (lubatud pikkuste raames) peab ära mahtuma sinna koostatavasse massiivi ka sellisel juhul, kui kasutaja otsustas maksimaalselt pikad sõnad sisestada.
Näide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Please enter a password: password Invalid password! Try again! Please enter a password: again Invalid password! Try again! Please enter a password: hunter2 Password accepted. Welcome AzureDiamond! Enter a sentence: I do wish we could chat longer, but I'm having an old friend for dinner. The length of the entered sentence is 72 Please enter a search phrase: old friend Your search phrase "old friend" exists in the originally entered sentence Enter a name: Pauline Enter an adjective: awesome Result: Pauline is an awesome person! |
Lisaülesanne: Tähtede loendus
Loo käsitsi uus loendamise funktsioon ning väljasta statistika
- Loenda ja kuva mitu tähte [a-zA-Z] oli lauses. Ära loenda kirjavahemärke, tühikuid jne.
- Leia ja kuva mitu protsenti kogu lausest moodustasid tühikud, kirjavahemärgid ja muud sümbolid.
- Näita protsent ühe komakohaga.
Näide
1 2 3 4 |
Sentence entered: Hi, Bob! Sentence length: 8 Alphabetical characters: 5 Percentage of other characters: 37,5% |
Ülesanne 2: CSV-st meiliaadresside genereerimine
Selle ülesande eesmärk on sulle tutvustada laialtlevinud andmeformaati CSV (comma separated value). Ülesande lahendamise käigus saad harjutada üksikute tähemärkide tuvastamist ja töötlemist.
Lae alla ülesande aluskood: 12_2_csv_starter.c
CSV formaat
CSV on struktuursete andmete hoiustamise formaat, kus iga andmeväli on eraldatud eelnevast ja järgnevast komaga. Tegu on tõenäoliselt kõige levinuma andmete varundamiseks ja hoiustamiseks kasutatava formaadiga väljaspool andmebaasisüsteeme. Tema peamisteks eelisteks on lihtne struktuur ning sellest tingitult on CSV toetatud praktiliselt kõigis rakendustes, mis vähekegi andmetega töötlevad.
Kõige lihtsamal kujul nagu öeldud on kõik väljad eraldatud üksteisest komaga. Näiteks:
1 2 |
Mari,Maasikas,112222IACB,49001013333 Toomas,Toomingas,111111MVEB,39002204444 |
Täpselt sellise keerukusega andmeid vaatame ka selles tunnitöös. Nägemaks keerulisemaid formaate ja reegleid kuidas hoiustada väljasid, mis peavad sisaldama komasid, jutumärke ning kuidas lisada pealkirju võid lugeda siit: https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules.
Nõuded
- Ülesande lahendus on ehitatud antud aluskoodile
- Programm loob igale aluskoodis antud inimesele e-postiaadressi.
- e-postiaadressi nimeosa koosneb kolmest eesnimetähest ja kolmest perenimetähest.
- Nimeosale järgneb sinu valitud domeen.
- E-postiaadressi nimi ja domeen peavad koosnema vaid väiketähtedest.
- E-postiaadress tuleb salvestada terviklikult tekstimassiivi (loo uus muutuja seal kus vaja) ja väljastada selle kaudu. Jooksvalt tähemärkhaaval väljastus pole lubatud.
- Programm väljastab:
- Inimese täisnime. Eesnime ja perenime osa peavad olema eraldatud tühikuga
- Genereeritud e-postiaadressi.
- Aluskoodis juba olevat koodi ülesande lahendamiseks muuta ei tohi ilma juhendaja poolse nõusolekuta. Sinu poolt kirjutatava lahenduse alguspunkt peaks asuma ProcessPerson() funktsiooni sees. Soovi korral võid julgelt funktsioone juurde lisada.
Näide
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Number of CSV lines: 3 Processing line: 'Maria,Kask' Name: Maria Kask E-mail: markas@ttu.ee Processing line: 'Johanna-Maria,Kask' Name: Johanna-Maria Kask E-mail: johkas@ttu.ee Processing line: 'Kalev Kristjan,Kuusk' Name: Kalev Kristjan Kuusk E-mail: kalkuu@ttu.ee |
Vihjed
- Teades koma asukohta, tead ka mis indeksilt algab perenime esimene täht
- ASCII suur- ja väiketäht erinevad üksteisest ühe biti võtta, mille järgu väärtuseks on 32 (nt A 65, a 97)
- Kõik toimingud peale e-mailiaadressi lõpu lisamise on kõige lihtsam teha selles ülesandes tähemärk-haaval. Saab ka kasutada string.h teegi funktsioone, kuid need võivad olla asjatult keerukad.
- Kõige tüüpilisem viga selles ülesandes on unustada sõnele null-baidi ehk terminaatori lõppu lisamist pärast nimeosa koostamist!
Lisaülesanne 1: lühikesed nimed
Muuda oma e-postiaadresside genereerimise algoritmi sedasi, et see tuleks toime ka lühemate nimede puhul.
Näiteks: Ly Kask -> lykask@ttu.ee
Lisaülesanne 2: unikaalsed e-postiaadressid
Muuda oma e-postiaadresside genereerimise algoritmi sedasi, et see genereeriks unikaalseid postiaadresse ka siis kui nimel on sarnane algus.
Muuda oma data massiiv järgnevaks:
1 2 3 4 5 6 7 |
char *data[] = {"Maria,Kask", "Johanna-Maria,Kask", "Kalev,Kristjan,Kuusk", "Margit,Kasemets", "Maris,Kase", "Marko,Kasvataja", "Margus,Kasevee"}; |
Nõuded
- Loodavad e-postiaadressid peavad olema unikaalsed
- Aadressi nimeosa peab jätkuvalt olema 6 tähemärki
- Aadressid peavad jätkuvalt viitama nime omanikule nii palju kui võimalik
- Täpne algoritm ja seega saavutatav nimekuju on sinu enda valida. Põhjenda valitud lahendust kaitsmisel.
Pärast tundi peaksid
- Teadma, et tähemärkide jaoks kasutatakse erinevaid kodeeringuid, muuhulgas ASCII ja Unicode
- Teadma mis on ASCII tabel ning kuidas seda kasutada.
- Teadma kuidas töötavad sõned C keeles.
- Teadma kuidas lõpetatakse sõnet C keeles (null-terminaator/null-bait).
- Seostama C keelseid sõnesid baidijadadega.
- Teadma mis on CSV.
- Oskama kasutada string.h teeki sõnede manipuleerimiseks.
- Oskama ka ise kirjutada sõnede manipulatsioone (tähemärkhaaval lähenemine).
- Teadma mis asi on puhvri ületäitumine ning selle kaudu tehtavatest rünnakutest.
Täiendav materjal
- Characters, Symbols and the Unicode Miracle – Computerphile
https://www.youtube.com/watch?v=MijmeoH9LT4 - A beginners guide away from scanf
https://www.sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html - ASCII
https://en.wikipedia.org/wiki/ASCII - ASCII table
https://www.rapidtables.com/code/text/ascii-table.html - Character encoding
https://en.wikipedia.org/wiki/Character_encoding - String.h library
https://www.cplusplus.com/reference/cstring/ - Strings in C
https://www.geeksforgeeks.org/strings-in-c-2/ - CSV
https://en.wikipedia.org/wiki/Comma-separated_values