Note til Programmeringsteknologi Akademiuddannelsen i Informationsteknologi Abstrakte datatyper C#-version Finn Nordbjerg 1/9
Abstrakte Datatyper Denne note introducerer kort begrebet abstrakt datatype (ADT) og beskriver den abstrakte datatype sekvens eller (ordnet) liste. Datastrukturer En datastruktur er en systematisk måde at organisere en mængde af data på. Datastrukturer kan være persistente, dvs. lagret på disk eller interne, dvs. lagret i memory. Vi betragter her kun interne datastrukturer. Endvidere skelner man mellem statiske og dynamiske datastrukturer. En statisk datastruktur har en fast størrelse under hele programafviklingen, mens en dynamisk datastruktur kan ændre størrelse efter behov under programudførelsen. En datastruktur er en fysisk organisering af en datamængde. Klassiske datastrukturer er bl.a.: Array statisk Kædet liste dynamisk Træstruktur oftest dynamisk Hashtabel kan være statisk eller dynamisk Vi ser i første omgang kun på arrays. Et array er et sammenhængende segment af memory, hvor elementerne er indekserede, dvs. kan tilgås udfra deres position. Elementerne er af samme type. Datastrukturer har brug for en række algoritmer til indsættelse, sletning, søgning af data. En datastruktur samt algoritmer til at operere på dataene er en realisering af en abstrakt datatype. Datatyper Generelt er en datatype givet ved en værdimængde og et sæt af tilhørende operationer. Endvidere er der behov for en datastruktur til at repræsentere datatypens værdimængde: Værdimængde (Hvilke værdier kan typen indeholde?) Operationer (Hvilke operationer er lovlige at udføre på elementer af datatypen?) Datarepræsentation (Hvordan repræsenteres datatypen i hukommelsen) 2/9
Alle programmeringssprog tilbyder indbyggede datatyper værdier, datarepræsentation og lovlige operationer defineres af sproget. Fx. har C# indbygget standardtyper som bool med værdierne true og false, og operationer som and ( && ), or ( ) og not (! ) de såkaldte logiske operationer. Denne type anvendes meget i forbindelse med programmering af kontrolstrukturer (selektioner og iterationer). En anden standardtype er int, hvis værdier er heltal, og operationerne er regneoperationer, sammenligninger mm. Alle variable erklæres af programmøren som tilhørende en type (som regel ikke i Scriptsprog). Compileren til de fleste sprog undersøger, at man kun bruger lovlige operationer. Udover de indbyggede typer giver C# mulighed for, at man kan definere sine egne typer. Disse typer kaldes ofte abstrakte datatyper. En vigtig gruppe adt er er de såkaldte Collections, som indkapsler en datastruktur. Formålet hermed er, at skjule datastrukturens implementation, så denne kan ændres, uden at det påvirker resten af systemet. Man opnår det, der kaldes dataabstraktion. Sagt på en anden måde, så adskiller man hvad fra hvordan. Nedenstående figur (fra Carrano ea.: Data Abstraction and Problem Solving with Java, Addison-Wesley) illustrerer princippet: Figure 3.7 ADT operations provide access to a data structure adt Datastruktur+ algoritmer 3/9
Princippet er, at programmer kan anvende adt en alene udfra kendskab til operationernes specifikationer. Man behøver altså ikke at bekymre sig datastrukturen og de algoritmer, som realiserer operationerne på datastrukturen. ADT en sekvens En meget udbredt adt er sekvens eller ordnet liste ofte blot liste. Den findes i de fleste moderne programmeringssprogs bibliotek, således også i C#, hvor den hedder Ilist, og har en realisering, som hedder ArrayList. En sekvens er en samling af værdier, som står i en eller anden rækkefølge. Man kan altså tale om første værdi, sidste værdi eller syvende værdi. Ligeledes er det muligt at indsætte og slette værdier på en bestemt plads. Praktiske varianter af sekvenser er utallige, fx. en sekvens af studerende ved et erhvervsakademi, en sekvens af bøger i et bibliotek, en sekvens af tegn (kaldes ofte en streng) i et program, der arbejder med tekster. Vi vil i første omgang koncentrere os om et simpelt eksempel, nemlig sekvenser af heltal. Vi har kaldt typen SeqInt. Værdimængden for SeqInt Værdimængden for datatypen SeqInt er følger af heltal. Her er en række forskellige eksempler: [1, 3, 5] [1, 5, 3] [-1, 34, 56] [1] [2, 65, 8, 999, 434, 0, 12, 1, -5, 78, 9] [] (en tom sekvens, men dog stadig en sekvens). Elementerne i en sekvens refereres via deres index. Første element har index 0. I det følgende vil vi realisere SeqInt vha. C# s ArrayList: Operationer på ArrayList Følgende operationer kan udføres på en variabel s af typen ArrayList: Definerer en sekvens s. Nulstiller s. ArrayList s= new ArrayList(); s.clear(); 4/9
s.insert(i,e); Indsætter elementet e på pladsen med index i og rykker eventuelle efterfølgende elementer. s.removeat(i); Fjerner elementet med index i og rykker eventuelle efterfølgende elementer tilbage. s.add(e); Tilføjer elementet e som det sidste element. Udover disse operationer, som er implementeret som metoder, har ArrayList et par operationer mere: s.count; Returnerer en int indeholdende antallet af elementer i s. s[i] tilgår det i te element, således at int x= s[i]; placerer værdien af det i te element i x, og s[i]= x; ændrer værdien af det i te element til x. Det skal slutteligt nævnes, at ArrayList har en lang række andre operationer, som vi ikke vil fordybe os i her. Se dokumentationen. Eksempel på brug af SeqInt På næste side ses et lille program, som opretter en SeqInt, sætter nogle tal ind i sekvensen, udskriver sekvensen, bruger et par operationer og udskriver sekvensen igen. 5/9
using System; using System.Collections; class TestSeqInt { private static ArrayList sekvens = new ArrayList(); static void Main(string[] args) { for(int i= 0; i<10; i++) sekvens.add(i); Udskriv(sekvens); Console.ReadLine(); sekvens[4]=1111; Console.WriteLine(sekvens[4]); Console.ReadLine(); sekvens.insert(4,44); Udskriv(sekvens); Console.ReadLine(); } public static void Udskriv(ArrayList s) { for(int i= 0; i<s.count; i++) Console.WriteLine(s[i]); } } Output fra dette program bliver: Oprindelig sekvens Ændring af element 4 Efter indsættelse af 44 på plads 4 6/9
Vi vil prøve at lave en metode mere, som arbejder på sekvenser. Metoden skal tælle antal store tal i en sekvens: Vi giver den navnet countbigones. Metoden skal returnere et heltal - nemlig antallet af store tal - og have en sekvens som parameter. Vi vedtager, at i denne sammenhæng er et tal stort, hvis det er større end 20. En algoritme, som løser opgaven, kan udvikles udfra følgende idé: Kik på hvert element i sekvensen, hvis tallet er større end 20, så tæl en variabel op med én. Alle elementer skal undersøges, så der er tydeligvis tal om en sweep-algoritme. Elementerne skal ikke ændres, så vi kan bruge foreach-løkken i C#: public static int CountBigOnes(ArrayList s) { int antal= 0; foreach(int x in s) if(x>20) antal++; return antal; } Placeres denne metode i klassen TestSeqInt kan den kaldes fra Main() med: Console.WriteLine(CountBigOnes(sekvens); Andre abstrakte datatyper I det følgende vil vi kort kikke på en række andre hyppigt anvendte abstrakte datatyper. Stak Stakken er en abstrakt datatype, som gemmer data efter LIFO (Last In First Out)-princippet. De almindelige operationer er: Push(e), som placerer elementet e øverst på stakken Top(), som returner det seneste placerede element fra stakken uden at ændre stakken Pop(), som fjerner (og evt. returnerer) det seneste placerede element fra stakken. 7/9
Endvidere vil stakken som regel have operationer, som kan oplyse, om den er tom, og hvor mange elementer den indeholder. Stakke implementeres ofte ved at anvende en sekvens eller en kædet liste. Kø End kø fungerer efter FIFO (First In Last Out)-princippet. Operationer er bl.a.: Enqueue(e), som placerer elementet e sidst i køen Front(), som returnerer det forreste element (det element, som har været i køen længst) uden at ændre køen Dequeue(), som fjerner (og evt. returnerer) det forreste element (det element, som har været i køen længst) Som stakken har køen som regel også operationer, som kan oplyse, om den er tom, og hvor mange elementer den indeholder. Også køer implementeres ofte ved at anvende en sekvens eller en kædet liste. Dictionary Sekvenser, stakke og køer er eksempler på det, man kalder positionsbasere ADT er, idet elementer lagres og hentes ud igen i en eller anden rækkefølge. Dicionary eller Map, som ADT en også kaldes, er der imod værdibaseret, idet elementerne kendes ud fra deres værdi. Dictionary lagrer par af nøgler og værdier: (key, value). Fx kan nøglen være et telefonnummer og værdien et objekt med kontaktoplysninger (navn, adresse, email etc.). Almindelige operationer på et dictionary er: Insert(key, value), indsætter et nyt element i dictionaryet (ofte med pre-betingelse, at key ikke findes i forvejen) Contains(key), returnerer sand, hvis dictionaryet indeholder et element par med den angivne key. Ændrer ikke dictionaryet. Retreive(key), som returner værdien svarende til den angivne key (ofte med prebetingelse, at key findes). Ændrer ikke dictionaryet. Delete(key), sletter elementparret med den angivne key. Som stakken og køen har dictionary som regel også operationer, som kan oplyse, om den er tom, og hvor mange elementer den indeholder. Endvidere kan der være mulighed for at iterere gennem dictionaryet i nøgleorden. 8/9
De fleste moderne programmeringssprog (også C#/.NET) tilbyder en række standardklasser og interfaces, som realiserer disse ADT er. I.NET findes de i namespacet System.Collections og i.net2 i en forbedret og udvidet version i namespacet System.Collections.Generics. Opgave Undersøg hvilke ADT er, der tilbydes i System.Collections og/eller System.Collections.Generics. 9/9