LINKÖPINGS TEKNISKA HÖGSKOLA Institutionen för datavetenskap Avdelningen för programvara och system (SaS) Tommy Olsson 2006-08-31 TDIU06 Programmering g.k. Laborationer LAB2 Vt1, 2007 Dessa laborationer ingår i examinationsmomentet LAB2 och ska göras i läsperiod Vt1. Innehåll 9. Containrar, algoritmer, iteratorer... 3 10. Infixkalkylator... 4 11. Associativa containrar och funktionsobjekt... 6 12. Map, sortering, funktionsobjekt... 7 13. Funktionsobjekt... 8
2 Laborationshandledning TDIU06
TDIU06 Laborationshandledning 3 Laboration 9 Containrar, algoritmer, iteratorer I den här laborationen ska du prova på att arbeta med container-objekt (vector och list) i kombination med iteratorer och algoritmer. De tre första labtillfällena (varav ett reservtillfälle är avsedda att disponeras för denna uppgift). Skriv ett program som gör följande: 1. Öppna en textfil som innehåller heltal i textformat. Textfilens namn ska anges på kommandoraden och vanliga kontroller av kommandoraden och öppning av textfilen ska göras av programmet. 2. Läs in heltal från textfilen och lagra talen i en std::vector. 3. Skriv ut de inlästa talen på cout. Använd for-sats och indexering för att stega igenom vectorn. 4. Sortera talen i stigande ordning med algoritmen std::sort. 5. Skriv ut de sorterade talen på cout. Använd for-sats och iterator för att stega igenom vectorn. 6. Ta bort alla dubbletter som eventuellt finns. Detta görs i två steg: Använd först algoritmen std::unique. Observera att unique endast flyttar om värdena så att alla unika värden återfinns i början av vectorn. unique returnerar en iterator som anger slutet för de unika värdena, i form av positionen efter sista unika värdet. Ta sedan bort de värden som finns efter det sista unika värdet i vectorn med medlemsfunktionen erase. Undersök dock gärna vad som egentligen finns i vectorn efter det sista unika värdet. 7. Skriv ut talen i vectorn (som nu ska innehålla enbart unika värden i stigande ordning) på cout. Använd algoritmen std::copy och std::ostream_iterator. 8. Kopiera värdena i vectorn till en annan vector så att de kommer i omvänd ordning (dvs i sjunkande ordning). Använd algoritmen std::reverse_copy. 9. Skriv ut talen i kopian på cout. 10. Kopiera värdena från kopian till en std::list med hjälp av std::copy och std::back_inserter. 11. Skriv ut innehållet i listan på cout (värdena ska komma i sjunkande ordning). 12. Sortera listan. 13. Skriv ut den sorterade listan på cout (värdena ska alltså komma i stigande ordning). Nivå 2 (frivillig) Utskrifter har gjorts på flera olika sätt i uppgifterna ovan. Ytterligare en möjlighet är att överlagra operator<< för vector, så att man kan skriva ut en vectors, t.ex., innehåll med ett uttryck på formen: cout << vec << endl; Nivå 3 (frivillig) Prova ytterligare algoritmer på datastrukturena vector och list om tiden medger. Redovisning Lämna in programkoden på papper till din assistent (använd IDAs omslag för laborationsredovisning). Skicka även koden via e-post. Läsanvisningar C++ direkt, kapitel 12 och Appendix C.
4 Laborationshandledning TDIU06 Laboration 10 Infixkalkylator Uppgifter är att skriva ett program som kan läsa enkla aritmetiska uttryck och beräkna deras värde. Ett uttryck i taget ska kunna matas i på en rad och programmet ska beräkna och skriva ut resultatet. Använd standardbibliotekskomponenter i så hög grad som möjligt (strömmar, containrar, iteratorer, algoritmer, etc. utan att gå till överdrift:). Försök välja den mest lämpade komponenten då det finns flera tänkbara alternativ. Om man behöver en sekventiell datastruktur kan ju t.ex. vector, list eller deque vara möjliga alternativ men någon av dem kanske är att föredra, beroende på hur man ska sätta in och ta bort element i strukturen. Diskutera gärna lösningsförslag med assistenten innan du kodar. Det kommer en forsättning på denna uppgift i kursen TDDI10 Objektorienterad programmering. Ni som ska gå den kursen bör se till att lösningen, speciellt då det gäller hanteringen av prioritet och associativitet, följer anvisningarna nedan.. Annars kan detta behöva göras om i TDDI10! Addition, subtraktion, multiplikation och division ska kunna utföras på heltalsvärden (innehåller enbart siffror 0-9) och reella värden (innehåller enbart siffror och en decimalpunkt). När programmet körs kan det se ut enligt följande (inmatning med fet stil): Välkommen till räknedosan! Skriv ett uttryck i taget på en egen rad: 4711 + 17 4711 + 17 = 4728 7.5 / 2 + 1 7.5 / 2 + 1 = 4.75 21 + 3 * 12-1.5 21 + 3 * 12-1.5 = 55.5 Ett inläst infixuttryck ska först omvandlas till motsvarande postfixuttryck med den s.k. järnvägsalgoritmen. De tre infixuttrycken i exemplet ovan motsvaras av följande postfixuttryck (till höger): 4711 + 17 4711 17 + 7.5 / 2 + 1 7.5 2 / 1 + 21 + 3 * 12-1.5 21 3 12 * + 1.5 - I postfixuttryck (även kallad omvänd polsk notation) kommer operatorerna i den ordning de ska utföras och efter sina operander. Det gör att postfix lämpar sig väl för maskinell bearbetning. Ingen kunskap om vad som följer längre fram i uttrycket behövs för att kunna ta beslut om åtgärd i olika lägen. Om man t.ex. läser det tredje uttrycket ovan (21 3 12 * + 1.5 -) och kommer till *, kan multiplikationen utföras eftersom 3 och 12 redan har lästs. Man behöver alltså spara operander man läser, så att man lätt kan återfinna dem när de ska ingå i en beräkning. När man läser vidare och kommer till +, är det redan lästa värdet 21 och resultatet av den föregående multiplikationen som ska adderas. Man behöver alltså även spara resultat av beräknade deluttryck. Både järnvägsalgoritmen och postfixberäkningsalgoritmen kan förefalla komplicerade, men de är enkla genom att en stack kan användas för att spara saker som ska användas senare. I järnvägsalgoritmen sparas operatorer på en stack, enligt regler som baseras på operatorernas prioritet och associativitet. Vid postfixberäkningen sparas operander och beräknade deluttryck enligt mycket enkla regler. Båda algoritmerna gås igenom på den andra lektionen i läsperioden. Använd std::map för att hantera operatorernas prioritet, t.ex. en map för operatorernas inkommande prioritet och en map för deras stackprioritet. Att använda två prioriteter för varje operator är egentligen ett sätt att någorlunda enkelt hantera prioritet och associativitet. I ett sammansatt uttryck som 1+2-3 ska 1+2 beräknas först, eftersom addition och subtraktion har samma prioritet och är vänsterassociativa. Även detta kommer att gås igenom på den andra lektionen. Två funktioner ska finnas, en för att omvandla ett inläst infixuttryck till motsvarande postfixuttryck och en funktion för att beräkna värdet av ett postfixuttryck.
TDIU06 Laborationshandledning 5 Det är tillåtet att förutsätta att det alltid finns ett mellanrum mellan operander och operatorer i indata (det förenklar inläsningen avsevärt) och att inmatade infixuttryck alltid är korrekta (inga felkontroller krävs alltså). Nivå 2 (frivillig) Lägg in felkontroller, så att felaktiga uttryck upptäcks och felmeddelanden skrivs ut, i stället för det beräknade värdet. Det kan t.ex. se ut enligt följande: 1 1 + Felaktigt uttryck: operand där operator förväntades + 1 Felaktigt uttryck: operator där operand förväntades 1 + 1 + Felaktigt uttryck: operator avslutar 1 / 0 Felaktigt uttryck: division med 0 foo + 1 Felaktigt uttryck: otillåten symbol Felkontroller kan tänkas läggas in vid inläsningen av ett infixuttryck och/eller vid beräkningen av ett postfixuttryck. Det ingår i uppgiften att försöka komma på så många (alla) felmöjligheter som möjligt. Om man inte matar in något på en rad ska inte det betraktas som ett fel, utan programmet ska då hoppa över postfixberäkningen och vänta på en ny inmatning. Använd gärna undantag (exception) för att hantera fel. För att göra det någorlunda enkelt kan man kasta undantag av typen const char* (t.ex. throw "division med 0"). Nivå 3 (frivillig) Utöka syntaxen så att även parenteser kan ingå i infixuttrycken, t.ex. (postfix till höger) ( 21 + 3 ) * ( 12-1.5 ) 21 3 + 12 1.5 - * ( 1 ) 1 ( ) tomt parentesuttryck enbart tillåts (eller så förbjuder man) Lägg in ytterligare felkontroller som behövs när nu syntaxen utökats med parenteser, för att upptäcka fel som t.ex. följande: ( 1 + 2 1 + 2 ) ( 1 + 2 ) 3 1 ( + 2 ) 1 ( ) + 2 Nivå 4 (frivillig) Gör det möjligt att skriva infixuttryck utan att behöva ha mellanrum mellan elementen. Detta komplicerar inläsningen en hel del. Redovisning Lämna in programkoden på papper till din assistent (använd IDAs omslag för laborationsredovisning). Skicka även koden via e-post. Läsanvisningar C++ direkt, kapitel 12 och Appendix C.
6 Laborationshandledning TDIU06 Laboration 11 Associativa containrar och funktionsobjekt Denna laborationsuppgift består av ett två mindre uppgifter, som främst gäller associativa containrar och funktionsobjekt. Övning på användning av std::priority_queue och std::pair. Skriv ett program som skapar en "att-göra-lista". Först ska programmet läsa in ett antal uppgifter som ska utföras med tillhörande prioritet (heltal 1-9, högt värde hög prioritet). Inmatningen till programmet kan se ut enligt följande: Ange på varje rad prioritet och uppgift (avsluta med Control-D): 3 Mata fågeln 4 Klippa gräset 1 Städa rummet 5 Mata hunden 4 Mata katten 2 Diska 2 Tvätta Utskriften kan se ut enligt följande: Att göra: 5: Mata hunden 4: Mata katten 4: Klippa gräset 3: Mata fågeln 2: Tvätta 2: Diska 1: Städa rummet Använd std::priority_queue och lagra prioritet och uppgift i std::pair. Nivå 2 (frivillig) Ändra i programmet så att lågt prioritetsvärde i stället betyder hög prioritet. Deklarationen för priority_queue är (något förenklad ): template <class T, class Container = vector<t>, class Compare = less<t> > class priority_queue; T är alltså den typ av element som ska lagras, Container är den containertyp som används för att lagra elementen (förval är std::vector) och Compare är den jämförelse som ska användas för att ordna element i prioritetskön (funktionsobjektsklassen std::less är förvald). Här kan man, genom att uttryckligen ange en annat funktionsobjektsklass än std::less<t>, styra hur prioritetsjämförelsen ska göras. Standardvalen motsvarar att man uttryckligen deklarerar ett priority_queue-objekt för T lika med int på följande sätt: priority_queue<int, vector<int>, less<int> > pq; Denna deklaration kan vi alltså göra betydligt enklare genom: priority_queue<int> pq; I vårt fall vill vi alltså ha något annat än less<int>, och måste då också ange argumentet vector<int>. Finns det något standardfunktionsobjekt som kan användas?
TDIU06 Laborationshandledning 7 Laboration 12 Map, sortering, funktionsobjekt Övning på std::map, std::sort och funktionsobjekt. Skriv ett program som läser en text från en textfil och räknar hur många gånger varje bokstav förekommer i texten. Det är tillåtet att anta att endast bokstäver i det engelska alfabetet (A-Z) förekommer. Ingen skillnad mellan stora och små bokstäver ska göras. Använd std::map. När hela texten är läst ska programmet skriva ut en tabell som (i bokstavsordning) visar hur många gånger varje bokstav förekom i texten. Bokstäver i alfabetet som inte alls förekommer i texten kommer inte heller att ingå i tabellen. Nivå 2 (obligatorisk) Ändra i (en kopia) av programmet så att tabellen skrivs ut i frekvensordning, dvs de vanligast förekommande bokstäverna ska komma först och de mindre vanligt förekommande sist. Detta kan man göra genom att kopiera innehållet i mappen till en std::vector och sortera vectorn med avseende på bokstävernas frekvensen. Algoritmen std::sort finns i två varianter, en som (underförstått) använder operator <. För std::pair, som vi alltså har i mappen och kopierat till vectorn, finns två jämförelseoperatorer definierade, operator == och operator <. Operator == returnerar: x.first == y.first && x.second == y.second Operator < (som är den som bestämmer hur elementen i mappen ordnas) returnerar: x.first < y.first (!(x.first < y.first) && x.second < y.second) Algoritmen std::sort finns också i en variant som antingen kan ges en vanlig funktion eller ett funktionsobjekt som ska användas vid jämförelser. För att sortera element med avseende på frekvens måste vi alltså skriva en funktion eller konstruera ett funktionsobjektstyp, vilka i första hand jämför med avseende på komponenten second i paren (där antalet lagras), eventuellt i andra hand på komponenten first. Skriv en vanlig funktion, t.ex. med namnet descending, som kan jämföra två pair med avseende på komponenten second (eventuellt i andra hand på komponenten first). Använd den som tredje argument till sort: sort(v.begin(), v.end(), descending); Nivå 3 (frivillig) Definiera ett funktionsobjekt för att göra jämförelsen. Ska man göra det helt efter konstens alla regler ska man härleda från hjälpklassen std::binary_function men i detta fall kan vi definiera en struct, t.ex. med namnet descending som har medlemsfunktionen operator(). Ett objekt av typen descending kan ges till sort som tredje argument: sort(v.begin(), v.end(), descending()); Ett objekt av typ descending skapas alltså av descending().
8 Laborationshandledning TDIU06 Laboration 13 Funktionsobjekt Detta är främst en övning på funktionsobjekt. Uppgiften går ut på att skriva ett program som läser en text och sparar alla vanliga ord i texten i en std::vector. När hela texten har lästs ska orden sorteras och alla dubbletter ska tas bort. Sedan ska programmet skriva ut orden, separerade med mellanrum. Utskriftsraderna ska ha en maxlängd (t.ex. 72 tecken). Så länge som det finns plats för nästa ord och ett mellanrum till på den aktuella utskriftsraden, skrivs ordet och mellanrummet ut, annars skrivs först ett nyradstecken ut och sedan ordet och mellanrummet. Enbart ord som innehåller minst två bokstäver ska sparas i vectorn. Stora och små bokstäver är distinkta, till exempel ska ord, Ord och ORD ska behandlas som tre olika ord. Indata kan bestå av strängar som antingen är riktiga ord eller namn (innehåller enbart bokstäver och enkla bindestreck inuti orden, t.ex. hej, Nisse och Karl-Jan ), eller innehåller annat än bokstäver och enkla, inneslutna bindestreck (t.ex. X15, -foo, foo--bar och bar-, som alltså inte är "ord"). Interpunktionstecken (komma, punkt, kolon, semikolon, frågetecken, utropstecken, citattecken) kan förekomma. Sådana sitter först eller sist i ett ord, dvs inget mellanrum före till exempel ett kommatecken men däremot efter, om inte radslut följer direkt. Apostrof för att ange genitiv när subjektet slutar på s, t.ex. Anders bil kan förekomma i en svensk text. I en engelsk text kan den engelska konventionen ( s) för att ange genitiv förekomma, t.ex. Andy s car. Parenteser kan förekomma. För varje läst sträng kan man göra följande för att hitta ett riktigt ord: Ta bort alla inledanden citattecken och vänsterparenteser. Ta bort alla avslutande interpunktionstecken, citattecken, apostrofer och högerparenteser. Ta bort engelsk genitivändelse ( s). Kontrollera att strängen endast innehåller bokstäver och bindestreck. Om det finns bindestreck, kontrollera att endast enskilda, inneslutna sådana finns. Om en sträng klarar den sista kontrollen och har minst längd 2, sparas den som ett riktigt ord. Programmet ska läsa strängar från indatatexten och hitta de "riktiga" ord som finns i texten. Efter att vectorn med orden har sorterats och alla dubbletter tagits bort, ska orden skrivas ut. Använd algortimen std::for_each och en egen funktion print_word för att skriva ut orden. Funktionen print_word anropas av for_each för varje ord i vectorn och har i uppgift, förutom att skriva ut varje ord följt av ett mellanrum, även att kontrollera när det är dags att generera en ny rad i utskriften. Utskrift ska göras på std::cout. Nivå 2 (obligatorisk) Byt funktionen print_word mot en funktionsobjektsklass print_word av struct-typ. Variabler för att hålla reda på maximal radlängd (konstant) och aktuell radlängd kan vara statiska variabler i operator(), för att undvika datamedlemmar i klassen i detta skede. Utskrift ska göras på std::cout. Nivå 3 (frivillig) Gör om struct:en till class. Maximal radlängd och aktuell radlängd ska vara datamedlemmar i klassen och ska initieras av en konstruktor, vilken ska ha den maximala radlängden som parameter (med förvalt argument 72). Utskrift ska göras på std::cout. Nivå 4 (frivillig) Lägg till möjligheten att även binda utströmmen via ett konstruktorargument (typ ostream&) med std:.cout som förvalt argument. Nu ska man alltså kunna skapa funktionsobjekt av typen print_word på tre olika sätt, dels utan argument (då ska utströmmen vara cout och maximal radlängd ska vara 72), del genom att ange ett heltalsvärde som anger den maximala radlängden (utströmmen ska vara cout), eller genom att ange både en utström och en maximal radlängd.