Frivillig reflektionsuppgift TDDC76 2016 Eric Elfving Detta moment i kursen har som uppgift att knyta samman projektet och era nyvunna kunskaper om vanliga datastrukturer och algoritmer. Förväntat omfång är 500-1000 ord (exklusive kodexempel) och redovisas genom att skicka er individuella lösning till er projektassistent via e-post i PDF-format. 1 Inbyggda datastrukturer i C++ I C++ har vi flera så kallade containrar. Varje container är en abstraktion av ett visst sätt att lagra data. Även om standarden inte anger exakt vilken datastruktur som ska ligga bakom respektive container ger de krav som ställs på implementationen att de flesta väljer en viss datastruktur för en given container. Nedan följer en lista över den vanligaste datastruktur som används för en specifik container. array Ett fält (array) med fast storlek. vector En ren linjär fältstruktur likt array, men det interna fältet är dynamiskt allokerat. Fältet har en fast storlek vilket innebär en omallokering och flytt av samtliga element vid insättning när fältet är fullt. deque En sekvens av flera fält som inte nödvändigtvis är i sekventiell ordning i minnet. Detta gör att borttagning och instättning i början och i slutet kan göras effektivt. Står för double-ended queue. forward_list En enkellänkad lista. list En dubbellänkad lista. map, set Ett (röd-svart) binärt sökträd. Dessa kräver att elementtypen är jämförbar med operator<. unordered_map, unordered_set Använder sig av en hashtabell där elementen sparas i en viss bucket baserat på dess hash. Detta kräver att vi anger en hash-funktion som ska användas för just denna container. stack, queue och priority_queue Dessa är så kallade contineradaptrar. Det betyder att de har normalt sett ingen egen datastruktur utan bygger på någon annan container. Anledningen att de finns är att göra användningen mer abstrakt. Vill jag exempelvis ha flera värden men endast är intresserad 1 av det senast instoppade värdet är en stack tydligare att använda sig av jämfört med exempelvis vector. Din uppgift är att analysera koden i ert projekt och hitta en användning av någon av ovanstående containrar. Du ska sedan visa på exempel på hur ni använt denna container och resonera kring valet av container. Frågor som kan vara bra att fundera kring kan vara “har det varit enkelt att använda sig av denna container”, “är det (teoretiskt) rätt val av container eller bör vi använt någon annan container (datastruktur) för ökad (algoritmisk) effektivitet” och “finns det någon annan datastruktur (utöver de inbyggda containrarna) som skulle passat bra för vårt användningsfall”. Utifrån din analys ovan ska du ge ett kort kodexempel på hur du skulle använt en annan container istället för den ni använt. Det räcker att visa något specifikt användningsfall. 2 Algoritmer Förutom containrar har standardbiblioteket implementationer av ett stort antal standardalgoritmer. Gemensamt för dessa är att de tar minst ett intervall av element som anges med så kallade iteratorer. En iterator är ett objekt som refererar till ett specifikt element i någon container och kan stega igenom den specifika container som användes för att skapa iteratorn. Koden i kodexempel 1 visar hur man skapar en iterator från en viss vector och sedan hur man kan använda iteratorn för att ta fram värdet på en viss position och stega till andra element. Kodexempel 1: Exempel på iteratorer v e c t o r <int> v a l s { 2 , 3 , 1 , 5 4 , 6 } ; auto i t = v a l s . b e g i n ( ) ; // b e g i n g e r en i t e r a t o r som // r e f e r e r a r t i l l f ö r s t a e l e m e n t e t c o u t << * i t << e n d l ; // s k r i v e r u t 2 ++i t ; // i t r e f e r e r a r nu t i l l andra e l e m e n t e t auto s t o p = v a l s . end ( ) ; // end g e r en i t e r a t o r som r e f e r e r a r t i l l // e t t t ä n k t e l e m e n t e f t e r d e t s i s t a ( 6 ) // värdena 3 t i l l 6 s k r i v s u t while ( i t != s t o p ) { c o u t << * i t << e n d l ; ++i t ; } Ett enkelt exempel på hur man använder algoritmerna är sort. För att sortera hela vectorn vals behöver vi endast skriva sort(vals.begin(), vals.end()) 2 Alla algoritmer går inte att använda till samtliga containrar. Detta kan låta som ett konstigt val, men om man tänker på den bakomliggande datastrukturen blir det ganska självklart - hur sorterar vi ett binärt sökträd eller en hashtabell? I standardbiblioteket har de valt att uttrycka kraven en algoritm ställer i form av iteratorkategorier där random access går att använda till allt (ges exempelvis från vector) och forward är svårast att använda sig av. Namnen kommer från att forward-iteratorer endast går att stega framlänges ett element i taget genom vald datastruktur medan random access stödjer hopp i datastrukturen det går alltså alltid att beräkna var i minnet ett specifikt element finns. Kodexempel 2: Exempel på random access-iteratorer v e c t o r <int> v a l s { 2 , 3 , 4 , 5 , 6 } ; auto i t { v a l s . b e g i n ( ) } ; c o u t << * ( i t + 4 ) << e n d l ; // s k r i v e r u t 6 f o r w a r d _ l i s t <int> f l { 1 , 2 , 3 , 4 } ; c o u t << * ( f l . b e g i n ( ) + 2 ) << e n d l ; // k o m p i l e r a r i n t e Med algoritmerna kan man lösa väldigt många problem på ett effektivt och lättläst sätt. I kodexempel 3 visas hur algoritmerna rotate och upper_bound kan användas för att implementera insertion sort. Kodexempel 3: Insertion sort med rotate s t d : : v e c t o r <int> v { 2 , 4 , 2 , 0 , 5 , 1 0 , 7 , 3 , 7 , 1 } ; fo r ( auto i = v . b e g i n ( ) ; i != v . end ( ) ; ++i ) { s t d : : r o t a t e ( s t d : : upper_bound ( v . b e g i n ( ) , i , * i ) , i , i +1); } Gå tillbaka till er projektkod och hitta minst två kodstycken som skulle gå att ersätta med ett anrop till en av standardalgoritmerna. För varje kodstycke ska du noggrant beskriva vad koden utför och reflektera kring läsbarheten av koden, både före och efter ändringen. Åtminstone en av algoritmerna bör vara under en av följande kategorier på cppreference1 : Partitioning, Binary search, Set, Heap. 1 http://en.cppreference.com/w/cpp/algorithm 3