Föreläsning 3.2: Fält

Föreläsning 3.2: Fält
Antag att man har ett antal variabler med heltal som skall innehålla data som hör väldigt intimt
ihop. T.ex. om man skall hålla reda på hur många slag man slagit på respektive bana i golf. Det som
krävs för detta är 18 stycken variabler och om vi vill anropa ett underprogram för att beräkna totalt
antal slag för denna omgång måste man skicka 18 parametrar för att kunna utföra beräkningen.
Rita en figur som visar 18 heltalsvariabler som vi döper till A1, A2, ..., A18. Programkoden som
motsvarar deklarationen av dessa 18 variabler:
with ...;
use ...;
procedure Xxx is
A1, A2, ..., A18 : Integer;
begin
...
end Xxx;
Utöka sen programmet med satser så att alla varablerna skall få värdet 4. Kan göras vid
deklarationen med EN tildelning (alla variablerna får då samma värde), men antag att man senare
skall tilldela alla variablerna värdet 5. Vad göra då? Jo, man måste göra 18 tilldelningar.
Det vore bättre om man kunde "klumpa ihop" dessa 18 variabler till en, men ändå kunna bearbeta
varje data var för sig om det behövs. I matematiken har vi något som kallas vektorer och matriser
för att hålla ihop data som hör intimt ihop. Rita en figur som visar hur det skulle kunna se ut.
A
1
2
18
I programspråk inför man oftast någon motsvarighet till detta med matriser (vektorn är ju bara en
endimesionell matris) och i Ada har man något som kallas för "fält" (eng. "array"). Vi ser detta med
fält som en ny datatyp och för att deklarera en variabel som den ovan gör vi på följande sätt.
with ...;
use ...;
procedure Xxx is
A, B : array (1..18) of Integer;
begin
...
end Xxx;
I matematiken använder man variabelns värde och ett "index" för att ange vilken del av variabeln
som refereras. Vi gör på samma sätt, men för att ange ett index markerar vi detta genom att sätta det
inom parentes efter variabeln. Antag att vi vill tilldela position (eller index) 1 i fältet värdet 4. I Ada
ser det ut på följande sätt.
begin
A(1) := 4;
end Xxx;
Vi kan alltså göra exakt samma saker som med de 18 variablerna A1-A18 i första exemplet, men vi
måste skriva parenteser runt det som markerar index. Detta kanske bara låter som om vi får skriva
mer kod utan att tjäna något på det, men det finns en del andra saker som vi kan utföra som är
omöjliga att göra om man har flera variabler.
Antag att vi vill tilldela alla 18 variablerna i exempel 1 värdet 0. För att göra detta måste vi skriva
18 tilldelningar. Någon kanske ser att det går att göra direkt i deklarationen genom att ge ett
initialvärde till alla variablerna samtidigt. Jo, det är sant att det går, men antag att man senare vill
sätta värdet 2 i alla variabler. Då är vi tillbaka till tilldelningarna igen.
Det finns flera sätt att tilldela alla positioner i ett fält samma värde (eller till och med olika värden i
vissa fall) utan att behöva skriva 18 tilldelningar. Vi tar några olika varianter (inklusive 18
tilldelningar).
Alt 1: Precis som med 18 variabler.
A(1) := 0;
A(2) := 0;
A(3) := 0;
...
A(18) := 0;
Alt 2: Tilldela hela vektorn specifika värden.
A := (0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
A := (1, 2, 3, 8, 4, 2, 3, 54, ­2, ­9);
Alt 3: Index 1 och 3 får värdet -1 och index 4 t.o.m.
6 får värdet 100 samt resten får värdet 0.
A := (1 | 3 => ­1, 4 .. 6 => 100, others => 0);
Alt 4: Alla index får värdet 0.
A := (1 .. 18 => 0);
A := (others => 0);
Alt 5: En slinga som gör arbetet.
for I in 1..18 loop
A(I) := 0;
end loop;
Alternativ 4 och 5 är de alternativ som är vanligast.
Vi tittar på vilka operationer som är tillåtna att utföra på fälten vi deklarerat (A och B).
begin
A(1) := 4;
Get(A(1));
B(1) := A(3);
Put(A);
­­ ERROR!!! Finns ingen Put för fält.
B := A;
end ...;
­­ ERROR!!! Ej samma datatyp.
Man kan fundera på varför man får kompileringsfelet när man försöker kopiera A till B ovan. Det
beror på att man inte anser att A och B har samma datatyp. A och B anses i detta fall ha lika
datatyper, men ej samma. För att ha samma datatyp på två variabler måste de deklareras med en
enda identifierare (inte med en komplex beskrivning).
Man brukar normalt sett inte skriva deklarationen av variabeln A och B på ovanstående sätt. När
man använder mer komplexa datatyper (t.ex. "array (...) of ...") brukar man oftast deklarera (kallas
ibland definiera) en datatyp som man själv namnger. Det ger dessutom bättre läsbarhet av koden om
man ger typen ett relevant namn. Vi tittar endast på deklarationen.
type Golf_Result_Type is
array (1..18) of Integer;
A, B : Golf_Result_Type;
Deklarationen av datatypen betyder bara att när man använder identifieraren Golf_Result_Type är
det en "array (1..18) of Integer" man menar. Det reserverade ordet "type" används för att ange att
det som följer är ett av programmeraren påhittat namn på en datatyp. Efter det reserverade ordet "is"
kommer definitionen av vad den nya typen egentligen är.
På detta sätt kan man direkt se att variabeln A innehåller golfresultat och kan då lättare förstå koden
som utför saker på variabeln (dessutom vore det ännu bättre om man namngav variabeln med något
som talar om vad den innehåller). Dessutom bli det tillåtet att direkt kopiera (tilldela) A till B för nu
har de samma datatyp.
Strängar är också en typ av fält även om de behandlas lite speciellt. Här följer en deklaration av en
strängvariabel och några enkla tilldelningar för att visa hur strängar fungerar.
procedure Xxx is
Str : String(1..5);
begin
Str := "Kalle"; ­­ Måste vara lika långa strängar!
Str(1) := 'N'; ­­ Ersätt 'K' med 'N'.
end Xxx;
Rita även figur som visar strängen (med fem tecken) ser ut. Vi ritar den horisontellt då strängar
normalt används för lagring av ord och meningar.
Str: 'N' 'a'
1
2
'l'
3
'e'
'l'
4
5
Det är viktigt att veta skillnaden mellan 'N' (tecknet N) och "N" (strängen N). En sträng lagras på
lite annat sätt i minnet än ett tecken skulle man kunna säga. En sträng lagras i vissa språk med
information om hur många tecken som ingår i strängen och i andra språk med ett avslutande tecken.
Däremot ett tecken är alltid bara ett tecken.
Man kan fundera varför deklarationen av variabeln Str inte ser ut som deklarationen av variabeln A
tidigare. Det beror på att man i Ada har fördefinierat en datatyp speciellt för strängar beroende på att
dessa används ofta och är lite speciella. Hur gör man då en deklaration (eller kanske man skall säga
en definition) av hur en datatyp ser ut? Vi visar definitionen för typen String för att visa detta.
type String is
array (Positive range <>) of Character;
Str : String(1..5);
Detta betyder att vi har en datatyp som heter String och den består av ett antal tecken. Hur många
tecken strängen skall innehålla krävs att man anger när man deklarerar sin variabel. I
typedeklarationen står det "Positive range <>". Detta betyder att man när man senare skall deklarera
sin variabel måste ange de gränser som fältet har och gränserna måste dessutom vara av typen
"Positive", d.v.s. vara positiva heltal större än noll. Man brukar kalla "<>" för "Ada-box".
Vi kan göra motsvarande sak med heltal om vi vill. Vi använder samma exempel som tidigare.
type Integer_Array is
array (Positive range <>) of Integer;
A : Integer_Array(1..18);
Observera att man inte får deklarera en variabel att ha en odefinierad storlek i Ada. Det går alltså
inte att göra följande deklaration:
A : Integer_Array;
­­ Ger kompileringsfel!
Däremot kan man i ett underprogram deklarera en parameter av en icke fullständigt specificerad
datatyp och sen ta emot data som är fullständigt specificerade av motsvarande datatyp.
Exempel på detta följer här.
with ...;
use ...;
procedure Xxx is
type Integer_Array is
array (Positive range <>) of Integer;
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
procedure Yyy(A : in Integer_Array) is
begin
...
end Yyy;
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
A : Integer_Array(1..18);
begin
Yyy(A);
end Xxx;
Detta var lite om variabler som motsvarar vektorer. Nu kan man också representera matriser av
godtycklig dimension med hjälp av fält. Antag att vi vill spela luffarschack med tre gånger tre rutor
(Tic-Tac-Toe) och att vi vill att datorn skall agera spelbräde. Hur gör vi för att representera själva
brädet?
X
O
X
X
O
O
Man ser direkt är att det ser ut som en matris. Varför inte lagra detta i ett tvådimensionellt fält?
Skulle det gå att lagra i en vektor? Javisst, men det blir mer komplext att hantera för den som
programmerar. Vi väljer en 3x3-matris.
Vi måste nu bestämma oss för vilket index som motsvarar x- respektive y-led. Inte för att det spelar
någon roll för den som spelar utan enbart för att den eller de som programmerar skall veta vad som
menas med första respektive andra index.
Vi antar att första index är y-led och andra index är x-led (det visar sig vara ganska praktiskt
senare). Hur skriver vi deklarationen av ett bräde (hur ser datatypen ut)?
type Matrix_Type is
array (1..3, 1..3) of _____;
Ja, vad skall vi ha för datatyp på respektive ruta? Förslag som ofta brukar dyka upp är Boolean
(verkar ju bara vara två olika värden) och Integer (0=tom ruta, 1=den ena spelaren, 2=den andra
spelaren), men ibland kommer någon på att det kanske vore "smart" med Character då detta ju exakt
motsvarar det som skall visas på skärmen. Ta gärna diskussionen om att man behöver en datatyp
som har tillräckligt många värden.
Vi väljer Character som datatyp för respektive ruta.
type Matrix_Type is
array (1..3, 1..3) of Character;
M1, M2 : Matrix_Type;
Vilka operationer kan man utföra på ett fält av denna typ?
begin
M1(1, 1) := 'X';
M2 := M1;
Put(M2(1, 1));
Put(M2);
­­ ERROR!!! Finns fortfarande ingen
­­ Put för fält.
M2(1) := M1(1); ­­ ERROR!!! Går ej att kopiera en rad.
­­ p.g.a. att deklarationen anger att
­­ det måste vara två index.
end ...;
Det sista felet kanske vi skulle vilja slippa. Antag att man skulle vilja kopiera en rad från den ena
variabeln till den andra. Hur gör man då? Ja, det bästa är att göra en annorlunda deklaration av
typen. Så som typdeklarationen ser ut nu måste båda index anges (om man vill ange något index).
type Row_Type is
array (1..3) of Character;
type Matrix_Type is
array (1..3) of Row_Type;
M1, M2 : Matrix_Type;
Nu är det tillåtet att göra samma operationer som tidigare på variablerna, men dessutom går det att
behandla en rad separat. En rad har ju en egen typ. Det har dock blivit lite annorlunda hur man
skriver indexen. Man måste nu ha ett parentespar till respektive index.
begin
M1(1)(1) := 'X';
M2 := M1;
Put(M2(1)(1));
M2(1) := M1(1); ­­ Tillåtet nu!
Antag att vi har spelat en stund och vi vill se hur brädet ser ut. Vi antar att brädet representeras av
M1 ovan. Vi gör ett anrop till en egendefinierad procedur som vi kan kalla Put och ser hur vi
deklarerar denna procedur.
procedure Xxx is
type Matrix_Type is
array (1..3)(1..3) of Character;
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
procedure Put(Item : in Matrix_Type) is
begin
­­ Hur gör vi???
end Put;
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
M1 : Matrix_Type;
begin
...
Put(M1);
...
end Xxx;
Frågan är nu hur man gör för att skriva ut brädet. Det kan man fundera på en stund och är man inte
van programmerare kan man kanske tycka att detta är alldeles för komplext. Vi tar det steg för steg
och vi börjar med ett specialfall. Hur skriver man ut brädets första ruta? Vi gör en kodsnutt som är
ganska klumpig, men som vi sen kan förbättra.
procedure Put(Item : in Matrix_Type) is
begin
Put(Item(1)(1));
Put(Item(1)(2));
Put(Item(1)(3));
New_Line;
Put(Item(2)(1));
Put(Item(2)(2));
Put(Item(2)(3));
New_Line;
Put(Item(3)(1));
Put(Item(3)(2));
Put(Item(3)(3));
New_Line;
end Put;
Det verkar som om vi kan göra tre slingor som kan skriva ut de olika raderna. Vi förbättrar koden
lite.
procedure Put(Item : in Matrix_Type) is
begin
for X in 1..3 loop
Put(Item(1)(X));
end loop;
New_Line;
for X in 1..3 loop
Put(Item(2)(X));
end loop;
New_Line;
for X in 1..3 loop
Put(Item(3)(X));
end loop;
New_Line;
end Put;
Om man tittar lite närmare på koden ser man att slingorna egentligen ser väldigt lika ut. Kanske
man kan ha ytterligare en slinga utanför och få bara en uppsättning? Tycker man att detta är konstigt
skulle man också kunna säga att man gör ett underprogram för att skriva ut en rad givet att man
skicka med vilken rad det är samt själva brädet. Vi modifierar koden.
procedure Put(Item : in Matrix_Type) is
begin
­­ Skriv ut hela brädet.
for Y in 1..3 loop
­­ Skriv ut en rad.
for X in 1..3 loop
­­ Skriv ut en position på en rad.
Put(Item(Y)(X));
end loop;
­­ Ny rad så att inte allt kommer på en lång rad.
New_Line;
end loop;
end Put;
Samma igen, men med underprogram (gör att man inte blandar ihop vilken slinga som gör vad).
procedure Put(Item : in Row_Type) is
begin
­­ Skriv ut en rad.
for X in 1..3 loop
­­ Skriv ut en position på en rad.
Put(Item(X));
end loop;
­­ Ny rad så att inte allt kommer på en lång rad.
New_Line;
end Put;
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
procedure Put(Item : in Matrix_Type) is
begin
­­ Skriv ut hela brädet.
for Y in 1..3 loop
Put(Item(Y));
end loop;
end Put;
Observera att man inte kan skriva "M1(1, 1)" om man deklarerat typen på det andra sättet och det
inte heller går att skriva "M1(1)(1)" om man deklarerat enligt den första modellen.
För att klara av laborationen behöver vi också ta upp lite om hur man gör för att sortera data i ett
fält. Det finns massor av algoritmer som är olika effektiva på olika datamängder, men vi tar en
enkel modell bara för att ha något att börja med. Det är tillåtet att använda olika algoritmer i
laborationen.
Algoritmen för "Bubble Sort" (antag ett fält med 20 positioner
fyllt med heltal):
1. Bubbla upp det minsta värdet till position 1 (toppen).
Att bubbla upp ett värde går till så att man börjar
längst ner (index 20) och jämför intilliggande index.
Om det data som ligger på det högre indexet är mindre
än det ovanför skall man byta plats på dessa. Fortsätt
hela vägen upp.
2. Bubbla upp det näst minsta värdet till position 2.
3. O.s.v.
För att lösa detta (som antagligen är lite komplext för en nybörjare) bör man börja med att lösa ett
specialfall. T.ex. kontrollera och (eventuellt) byt plats på data i de två högsta indexen. Detta löser vi
på lektion. så att alla får vara med och fundera på lösningen.
För att principen skall vara glasklar kan man avslutningsvis ta fram fem personer som skall sorteras
i längdordning eller i alfabetsordning på förnamn eller liknande. Det skall dock vara något som alla
i auditoriet lätt kan uppfatta (fungerar inte bra med alfabetsordning i stora grupper alltså).
Om man får tid över kan man alltid ta upp att det i standarden för Ada står att det går att jämföra två
fält (av samma längd) utan att behöva gå igenom alla index var för sig. Detta gäller dock endast om
varje data i fältet är av enkel datatyp. Det går alltså att jämföra två strängar med varandra och se
vilken som innehåller det "minsta" datat på följande sätt:
if Str1 < Str2 then
...
end if;
Effekten blir att jämförelseoperatorn "<" går letar efter första tecknet som inte är lika i de båda
strängarna och då avgör vilken sträng som är "minst". T.ex. är "Kalle" < "Nisse" och "AAA" <
"AAB".
På samma sätt går det alltså att jämföra två fält med t.ex. heltal eller flyttal, men inte två fält där
datat i respektive index är av någon komplex typ (t.ex. ett annat fält).
Övning på lektion
Uppgift 1: Läs in 5 heltal, summera dessa och skriv sedan ut
summan på skärmen. Lös uppgiften med underprogram
och fält.
Låt gruppen hjälpa till att lösa detta problem och vara bara den som styr hela
programmeringsarbetet. Låt olika personer hjälpa till och diskutera och ifrågasätt varför man väljer
procedurer respektive funktioner.
Skriv ett fullständigt program.Var börjar vi och hur gör vi för att slippa så många fel som möjligt?
Observera att vi från början inte behöver bry oss om vilka paket vi måste inkludera. Det är först när
vi behöver ett paket som vi tar med det.
procedure Summera_version_3 is
begin
­­ Här skall det in mer kod.
end Summera_version_3;
Vi fyller i med några satser. Antag att vi har tre rader på oss att klara av hela uppgiften. Hur gör vi?
procedure Summera_version_3 is
begin
Las_In(Talen);
Summera(Talen, Summan);
Skriv_Ut(Summan);
end Summera_version_3;
Vi måste deklarera variabler, men då måste vi nog göra en typ också. Man kan inte skicka fält som
parametrar utan att först deklarera en datatyp för fältet (ses inte som samma datatyp i anrop och
parameterdefinition annars).
procedure Summera_version_3 is
type Arr5 is
array (1..5) of Integer;
Talen : Arr5;
Summan : Integer;
begin
Las_In(Talen);
Summera(Talen, Summan);
Skriv_Ut(Summan);
end Summera_version_3;
Nu kommer vi till definitionerna av underprogrammen. Skall det vara funktioner eller procedurer?
Det bör studenterna komma fram till. Vissa kanske tror att "Summera" är en funktion därför att den
beräknar ett värde som skickas tillbaka. Detta är en intressant synpunkt och tillfället bör tas till vara.
Ge förslaget att vi skriver en procedur nu och senare skriver vi om den till en funktion för att se vad
som skiljer dem åt.
Vilken procedur skall vi börja med? Vissa kanske tror att man måste programmera i samma ordning
som man senare skall utföra programmet, men man kan här ta upp att det kan vara olika
programmerare som gör olika delar. Vi kan börja med "Las_In" i vilket fall.
Vi markerar vart i programmet underprogrammen skall skrivas. Observera att det är behändigt att
lägga underprogrammen ovanför variablerna då detta ger att man måste skicka data via
parametrarna och på det viset slipper "konstiga sidoeffekter". Det är dock tillåtet att skriva dem
nedanför variablerna om man bara gör på "rätt sätt" med parametrarna.
Låt någon student hjälpa till med procedurhuvudet. Vad skall proceduren heta och hur många
parametrar skall det vara och vad skall dessa heta samt skall dessa vara "in", "out" eller "in out"?
Någon kanske tror att det skall vara "in" därför att vi läser in data från tangentbordet. Rita figur som
visar att vi bara tittar på gränssnittet gentemot den som anropar oss (i detta fall huvudprogrammet).
procedure Las_In(Talen : out Arr5) is
begin
­­ Här skall det in mer kod.
end Las_In;
Går programmet att kompilera? Nej, vi saknar två underprogram och dessutom måste det stå minst
en sats mellan "begin" och "end". Vi skulle kunna skriva satsen "null;" och sen göra de två andra
procedurerna också för att kunna kompilera och se att vi är på rätt väg. Kan vara bra när man sitter
själv och programmerar på laborationerna. Vi fortsätter utan detta på lektionen och låter studenterna
fylla i satserna i proceduren.
procedure Las_In(Talen : out Arr5) is
begin
for I in 1..5 loop
Get(Talen(I));
end loop;
end Las_In;
Frågor som kan ställas är om I skall deklareras och det är något paket vi behöver till vårt program
(Ada.Integer_Text_IO behövs för att kunna göra Get på ett heltal). Paketen skall inkluderas ovanför
huvudprogrammet. Vissa undrar om man måste ha samma namn på parametrarna som variabeln i
huvudprogrammet. Svaret är nej, endast ordning, antal och datatyp är relevant.
Det kan vara studenter som tycker att man kan låta bli att skicka parametrar. Det fungerar ju ändå.
Kan motiveras genom att man normalt sett skriver stora (jättestora) system som gör att man inte kan
ha alla variabler i huvudprogrammet och dessutom är det så att genom att skicka data via parametrar
är det möjligt att kopiera in kod i ett helt annat program och det fungerar fortfarande. Skulle det inte
göra om man inte råkade ha samma variabler i det huvudprogrammet.
Vi fortsätter med summeringen. Återigen en student som får hjälpa till med procedurhuvudet och
sen gäller det att få fram hur satserna skall se ut.
procedure Summera(Talen : in out Arr5;
Summan : out Integer) is
begin
Summan := 0;
for I in 1..5 loop
Summan := Summan + Talen(I);
end loop;
end Summera;
Det blev inga extra lokala variabler här heller. Vi ger oss på utskriftsproceduren. Samma upplägg
igen. Studenterna arbetar.
procedure Skriv_Ut(Summan : in Integer) is
begin
Put("Summan blev: ");
Put(Summan, Width => 0);
New_Line;
end Skriv_Ut;
Här kommer "Ada.Text_IO" att behövas. Vad händer om man inte tar med "Width => 0"? Vi
markerar återigen var alla procedurer skall skrivas så att inget missförstånd uppstår.
Man kan lägga till att "Get" är beroende av att användaren matar in vettiga värden. Annars kanske
man skulle ha gjort en separat "säker" inläsningsprocedur.
Nu ger vi oss på att göra om proceduren som summerar till en funktion. Vad kommer att ändras
(fråga studenterna). Vi börjar i huvudprogrammet. Anropet till funktionen måste se ut på följande
sätt:
Summan := Summera(Talen);
Vi skickar in hela fältet med tal och funktionen beräknar ett värde (summan) som returneras och
tilldelas till variabeln "Summan". Observera att funktioner måste anropas i "uttryck" och att de inte
får anropas som en egen sats.
Proceduren måste också modifieras. Vi skriver om hela från början för att låta studenterna jobba lite
med parametrar m.m. igen. Funktionshuvudet, parametrar och "in", "out" ...?
function Summera(Talen : in Arr5)
return Integer is
begin
Summan := 0;
for I in 1..5 loop
Summan := Summan + Talen(I);
end loop;
end Summera;
Mellan "begin" och "end" har vi nu fått in samma kod som i proceduren sen tidigare. Är det något
so minte stämmer (eller kanske saknas)? Ja, man måste returnerna resultatet till den som anropar.
Hur gör man detta?
function Summera(Talen : in Arr5)
return Integer is
begin
Summan := 0;
for I in 1..5 loop
Summan := Summan + Talen(I);
end loop;
return Summan;
end Summera;
Är allt ok nu? Nej, vi saknar en lokal variabel i funktionen. Nu har vi inte suman som parameter
utan måste hantera den med en extra variabel i funktionen. Observera att den lokala variabeln är en
annan variabel än den i huvudprogrammet trots att de har samma namn. Vi rättar till detta och efter
en kort diskussion kan man nog räkna med att man kan tilldela "Summan" värdet 0 redan i
deklarationen.
function Summera(Talen : in Arr5)
return Integer is
Summan : Integer := 0;
begin
for I in 1..5 loop
Summan := Summan + Talen(I);
end loop;
return Summan;
end Summera;
För att klara av laborationen behöver vi nog göra ett exempel som visar hur man sorterar data i ett
fält. Principen för "Bubble Sort" är genomgången på föreläsning och studenterna bör kunna tala om
hur man skall göra (även om de kanske inte kan lösa det med programkod). Rita en figur som visar
ett fält med 5 heltal (samma som ovan kanske) där index 1 är överst.
Vi börjar med ett specialfall och förbättrar koden efter hand. Antag att vi bara skall sortera värdena i
index 4 och index 5 i vårt fält. Hur gör vi?
if Talen(5) < Talen(4) then
Swap(Talen(5), Talen(4));
end if;
Vi upprepar detta för index 3 och 4, index 2 och 3 och sista gången index 1 och 2 och då kanske vi
ser att koden blir väldigt lika för alla dessa fall.
if Talen(4) < Talen(3) then
Swap(Talen(4), Talen(3));
end if;
if Talen(3) < Talen(2) then
Swap(Talen(3), Talen(2));
end if;
if Talen(2) < Talen(1) then
Swap(Talen(2), Talen(1));
end if;
Vi kanske skulle kunna ersätta talen 5, 4, 3, 2 med en variabel och låta variabeln få värdena i rätt
ordning samt göra en slinga som upprepar koden 4 gånger. Vilken typ av slinga? Man bör komma
fram till att "for"-satsen (t.ex. med styrvariabel "J") är den lämpligaste samt att man måste gå
igenom den baklänges. Man bör dessutom från studenterna kunna få fram att "J - 1" motsvarar det
andra indexet man testar.
for J in reverse 2..5 loop
if Talen(J) < Talen(J ­ 1) then
Swap(Talen(J), Talen(J ­ 1));
end if;
end loop;
Det vi gjort nu motsvarar en "bubbling" och vi vet nu att det första talet ligger på rätt plats. Vi får
göra ytterligare tre bubblingar för att få de resterande på rätt plats. Vi skriver koden igen (minst en
gång för att se sambandet).
for J in reverse 3..5 loop
if Talen(J) < Talen(J ­ 1) then
Swap(Talen(J), Talen(J ­ 1));
end if;
end loop;
Det räcker att gå från 5 till 3 denna gång (det översta talet var ju redan klart). Sen räcker det med 5
till 4 och sista gången 5 till 5 (annars kan de två sista talen ligga i fel). Borde man inte kunna göra
ytterligare en slinga utanför den andra där styrvariabeln (t.ex. I) får ersätta startvärdet i den inre
slingan?
for I in 2..5 loop
for J in reverse I..5 loop
if Talen(J) < Talen(J ­ 1) then
Swap(Talen(J), Talen(J ­ 1));
end if;
end loop;
end loop;
Ja, då har vi faktiskt löst hela problemet. Hur gjorde vi? Jo, vi tog ett specialfall och skrev en
klumpig lösning och sen förbättrade vi den allt eftersom. Man skall eftersträva att göra lösningar
som på något sätt bygger på en algoritm och inte är en fullständig uppräkning av olika fall. Annars
har man ett stycke kod som bara fungerar för detta fall och inte enkelt går att modifiera till ett mer
generellt problem.
Koden ovan skulle kunna göras bättre genom att ersätta de två talen 2 och 5 med konstantnamn som
enkelt kan modifieras i programmet. Dessa motsvarar dessutom i princip start- och slutindex i fältet.
Kanske man skulle fundera på.
I Ada finns det möjlighet att ta fram start och slutindex samt några andra saker för ett fält. Antag
följande deklaration av ett fält:
type Arr is
array (3..7) of Integer;
A : Arr;
Här följer en kort lista på "bra-att-ha-saker". De kallas "attribut" och gäller i detta fall för alla typer
av fält.
* Första index i fältet:
A'First
=3
* Sista index i fältet:
A'Last
=7
* Fältets indexintervall:
A'Range
= 3..7
* Längden på fältet:
A'Length
=5
Man kan använda sig av dessa t.ex. för att göra slingor som går över generella fält. Exempel:
for I in A'Range loop
Put(A(I));
end loop;
for I in A'First..A'Last loop
Put(A(I));
end loop;
Observera att följande slinga inte alltid är så lyckad.
for I in 1..A'Length loop
Put(A(I));
end loop;
Om föreläsaren inte hunnit gå igenom hur "<" fungerar på fält bör detta gås igenom på lektionen. Se
anteckningarna i slutet av föreläsningsdelen om detta isåfall.