När du nu gjort både lab 0 och lab 1 så kan det vara dags att ta sig en uppgift för att testa sina kunskaper i lite skarpare läge. Observera att uppgiften nedan inte är samma som på det kommande diagnostiska testet.
Skriv ett program som låter användaren mata in ett positivt heltal
N. Programmet skall sedan rita ut en kvadrat vars storlek beror på
heltalet N. Programmet skall fungera för godtyckligt stora N (även om
utskriften kanske inte blir så snygg för jättestora N).
Det är viktigt att du försöker så mycket du kan själv på denna
uppgift. Var envis! Tänk på att det kan ta lång tid att lösa även
"enkla" uppgifter när man är nybörjare. Det är inte ovanligt att det
tar mer än 1 timme att lösa detta. Du kan också behöva gå tillbaka
till tidigare laborationer eller teorikapitel för att repetera
tidigare material. Om du trots allt har kört helt fast och behöver en
vägledning på hur man skall tänka så kan du klicka här
för en grundligare genomgång.
Genomgång kvadraten
Det svåraste när man får ett nytt problem framför sig är att komma igång. Ofta känns problemet stort och ohanterligt. Detta är inte något som bara gäller för nybörjare, det gäller alla programmerare. Skillnaden är att vana programmerare har lärt sig en teknik för att hantera detta:
Dela upp problemet
Hur applicerar man då detta på kvadratuppgiften? Skall vi bara
skriva ut halva kvadraten? Ja, det är faktiskt inte en sådan dum
idé. Exakt hur man vill dela upp det är ju upp till en själv, men
att ta allt på en gång fixar ingen.
Vi börjar med att bryta av en liten bit av uppgiften. Det kanske
finns någon del som vi vet direkt hur man kan
göra. T.ex. inmatningen, det har vi ju övat på mycket. Att få till
inmatningen gör vi lätt med funktionen input:
N = int(input('Mata in N: '))
Nu har vi åtminstone fått in lite data till vårt program. Vad är nästa
steg? Ja, nu skall kvadraten ritas. Det var ju det krångliga. Okej,
men då delar vi upp det också. Vi tar bara första raden på kvadraten,
det borde vara lättare än att ta hela. Men varför stanna där? Vi kan
ju ta bara första tecknet! Då blev det plötsligt lätt, det skall ju
alltid vara samma (beror inte ens av N).
print('+')
Nu är det faktiskt dags att köra vår kod. Även om vi förväntar oss att
resultatet inte är så mycket att hänga i julgranen så är det bra att
köra nu ändå. Varför? Jo, för att vi kan ju ha gjort fel. Vi
kan ha stavat fel på print eller skrivit fel på många andra
ställen. Om det skulle vara något fel på detta lilla som vi nu har
skrivit så vill vi ha reda på detta direkt istället för att få många
fler fel senare!
Mata in N: 2
+
Är man petig så ser man att det faktiskt inte är helt rätt nu. Det
skulle vara en blank rad mellan "Mata in..." och plusset. Vi
modifierar koden och kör sedan igen:
N = int(input('Mata in N: '))
print('')
print('+')
Nu tar vi resten av första raden. Det är nu det blir riktigt
krångligt. Det skall ju vara ett antal "-":tecken här, och det beror
ju av N. Hmmm.
Här kör man lätt fast. Det gör inte bara nybörjare utan alla
programmerare kör fast. Skillnaden är att vana programmerare har
lärt sig en teknik att hantera komplexa problem. Vi kan förenkla
problemet genom att:
Lösa ett mindre generellt problem först
För vår det innebär det att vi kan låtsas som att användaren alltid
matar in t.ex. 2. Då jobbar vi i en specifikare värld än att N kan
vara vad som helst. Om vi kan utgå ifrån att N är 2 så blir
problemet plötsligt lätt igen. Vi bara tittar i körexemplet ovan och
ser att det då skall vara fyra bindestreck på översta raden. Vi kan
då skriva (efter print('+')):
print('----')
Nu kör vi igen och inser att det inte blir jättesnyggt. Plusset och
strecken kommer på egna rader. Detta är ju inte konstigt eftersom
print alltid skriver ut med radslut (enter) om man inte explicit
säger något annat. För att lösa detta så måste vi ju lägga till
end="" som parameter till print. Tänker man till ett extra steg så
inser man att detta även gäller för bindestrecken Den korrekta
koden borde alltså vara:
N = int(input('Mata in N: '))
print('')
print('+', end='')
print('----', end='')
Nu kan man lätt bli frestad att fortsätta rita ut resten av
kvadraten för N=2, och detta kanske inte är fel att göra i ett
första steg, men det kommer leda till problem när vi sedan vill
generalisera. Istället borde vi nu fundera på det vi just har
gjort. Finns det andra sätt att få ut dessa streck? Här är två alternativ:
print('--', end='')
print('--', end='')
print('-', end='')
print('-', end='')
print('-', end='')
print('-', end='')
Nu när vi ser att det är samma kommando flera gånger (upprepning) så kommer även två andra alternativ in på banan:
for i in range(2):
print('--', end='')
for i in range(4):
print('-', end='')
Dessa är ju precis ekvivalenta med de tidigare två. Men vi slipper
skriva lika många satser (i alla fall i det sista fallet).
Om man nu skall på något sätt avgöra vilket av dessa som är "bäst"
så måste man försöka avgöra vilken av dem som är enklast att ändra
om man vill ha ett annat beteende. Loop-varianterna har ju fördelen
att man bara behöver ändra siffran som anger hur många gånger loopen
skall "gå". Vill vi ha ut 10000 streck så kan vi ju nu bara skriva:
for i in range(10000):
print('-', end='')
Jämför detta med att skriva 10000 rader kod, eller göra en ca 10000
tecken lång print-sats! Loopen är alltså att föredra.
Dessutom behöver man inte använda konstanter i range-satsen, vi
kan använda variabler:
for i in range(N):
print('-', end='')
Då får man ut 2 streck om användaren matar in 2, och 3 om man matar
in 3 och 1000 om man matar in 1000 o.s.v.. Häftigt! Nu börjar vi gå
mot ett mer generellt program. Detta kanske inte är exakt det vi
ville ha dock. Vi ville ju ha 4 streck om man matar in 2, och 6 om
man matar in 3. Lyckligtvis går det också att skriva (aritmetiska)
uttryck i range-satsen.
for i in range(2*N):
print('-', end='')
Observera att vi lika gärna skulle kunna använda den andra
loop-varianten sedan tidigare, då får vi istället:
for i in range(N):
print('--', end='')
Detta går ju precis lika bra. Men vi kan ha kvar 2*N-loopen eftersom
det var den vi kom på först. Om vi även lägger till det avslutande
'+':et så har vi alltså:
N = int(input('Mata in N: '))
print('')
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
Notera att vi inte har med end='' på sista printen eftersom vi ju
behöver ett radslut (entertecken) i slutet av första
utskriftsraden. Nu är det också hög tid att köra programmet igen, och
testar med lite olika indata:
Mata in N: 4
+--------+
Mata in N: 10
+--------------------+
Nu känner vi oss ganska nöjda med oss själva. Då tar vi en kort paus och firar lite.
Nu skall vi ta oss an nästa uppgift. Vid första anblicken kanske det
är att få ut mellanraderna (d.v.s. de som ser ut så här: "| |"). Men
egentligen är det inget som säger att vi inte kan ta en annan del av
problemet. Bara för att mellanraderna är det som kommer ut härnäst i
utskriften så betyder inte det att vi som programmerare behöver skriva
koden i den ordningen. Att man har flera (mindre) uppgifter att göra är alltså inget ovanligt. En god tumregel är att:
Ta den lättaste deluppgiften först
... även om detta kanske inte är nästa sak som skall hända när man kör
programmet. Man kan nästan alltid testa att den lilla del man gjort
fungerar oberoende av det som kommer innan ändå.
För vår del finns det ju en del av kvadraten som nu är mycket enkel
att fixa. Den som har ett skarpt öga ser att den understa raden är
exakt lika dan som den översta raden! Då är det en smal sak att fixa
den koden!
N = int(input('Mata in N: '))
print('')
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
# Här skall vi senare lägga till kod som fixar "mellanraderna"
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
Om vi nu kör så får vi:
Mata in N: 3
+------+
+------+
Därefter fortsätter vi med mellanraderna. Åter igen bryter vi ner
problemet och tar en lite bit i taget. Vi tar inte alla mellanrader på
en gång utan nöjer oss med att bara få ut en till att börja med. Att
få ut ett vertikalt streck är lika enkelt så att få ut det där första
plusset. Därefter skall vi få ut ett antal blanksteg (som beror av N,
närmare bestämt exakt 2 gånger N) och sist ytterligare ett vertikalt
streck. Vi inser att detta är ju precis likvärdigt med den kod som
gäller för topp-/bottenraden, fast det är andra tecken som skall
printas. Vi "tjuvkikar" lite på det vi redan gjort och ordnar till
följande:
# Inmatning
N = int(input('Mata in N: '))
print('')
# Kod som fixar toppraden
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
# Kod som fixar en mellanrad
print('|', end='')
for i in range(2*N):
print(' ', end='')
print('|')
# Kod som fixar bottenrader
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
Nu kanske det även är dags att lägga till lite kommentarer (som i
koden ovan), eftersom det börjar bli lite mycket kod nu. I senare
teorikapitel (underprogram) kommer vi att lära oss hur vi undviker
detta på ett trevligare sätt. Nu börjar vi närma oss. Vi kör igen:
Mata in N: 4
+--------+
| |
+--------+
Supersnyggt! Matar in man in 1 så fungerar faktiskt programmet exakt
som det beskrivs i uppgiften. Men för större N så blir det mer som en
rektangel än en kvadrat. Hur får vi nu till resten, så att det blir
fler mellanrader när N är större? Detta kan kännas tungt, då får man
gå tillbaka till de tekniker som vi har lärt oss på resans gång. Vi
löser ett mindre generellt problem och låtsats som att N alltid är
4. Hur skulle koden se ut då? Om vi förkortar och skriver lite
stilistiskt med bara kommentarerna blir det något i stil med:
# Inmatning
N = int(input('Mata in N: '))
print('')
# Kod som fixar toppraden
...
# Kod som fixar en mellanrad
...
# Kod som fixar en mellanrad
...
# Kod som fixar en mellanrad
...
# Kod som fixar en mellanrad
...
# Kod som fixar bottenrader
...
Ett annat sätt att skriva precis samma kod är ju:
# Inmatning
N = int(input('Mata in N: '))
print('')
# Kod som fixar toppraden
...
for i in range(4):
# Kod som fixar en mellanrad
...
# Kod som fixar bottenrader
...
Nu är det inga problem att modifiera programmet så att vi får ut ett
annat antal mellanrader. Vi kan t.ex. skriva 3 istället för 4 om vi
vill ha 3 rader (och då få ut kvadraten av storlek 3). Precis som
tidigare gäller att vi kan byta talet i range-satsen mot en variabel
och det råkar ju vara 4 i N om vi vill ha 4 mellanrader, 3 i N om vi
vill ha 3 mellan rader o.s.v. Alltså borde man kunna skriva:
for i in range(N):
# Kod som fixar en mellanrad
...
Om vi ny "fyller i" den kod som vi hade sedan tidigare har vi alltså:
# Inmatning
N = int(input('Mata in N: '))
print('')
# Kod som fixar toppraden
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
# Kod som fixar ALLA mellanrader
for i in range(N):
# Kod som fixar en mellanrad
print('|', end='')
for i in range(2*N):
print(' ', end='')
print('|')
# Kod som fixar bottenrader
print('+', end='')
for i in range(2*N):
print('-', end='')
print('+')
Och det är faktiskt hela programmet! Ett par kommentarer som nu tåls att betrakta:
- Jag använder i som styrvariabel i alla loopar. Det går bra. Om man har "dubbelloopar" så bör man dock tänka till om detta om man vill använda värdet av variabeln i någonstans inuti looparna. Då kanske det är bättre att döpa en av variablerna till j eller något annat.
- Som tidigare nämnt så hade underprogram kunnat hjälpa oss här. Metoden som vi använde ovan där vi "stilistiskt" bara skrev kommentarerna är något som man vanligen inte behöver om man har lärt sig att använda underprogram rätt.
- I python kan man multiplicera strängar med tal. Det är inte
rätt väg för att lösa uppgiften. I alla fall inte det sätt som väl
testar om man har förstått innehållet i kursens första
delar. Multiplikation mellan strängar och tal är en speciell sak
python och är mycket mindre användbar att kunna än hur loopar
fungerar.
- Vi nämnde tidigare att man kunde råka illa ut om man kör allt för länge på det icke-generella spåret. Det man ofta hamnar då i är fullständig uppräkning. Se den här filen på ett exempel på hur man inte bör lösa uppgiften men som inte är en ovanlig fälla
Slutligen kan vi sammanfatta de tumregler/riktlinjer som vi stötte
på och använde oss av i denna genomgång. Vi kan kalla dem för Generella principer för god problemlösningssed.