Göm meny

Mer om funktioner, namespaces och scope

Skriv lösningarna till uppgifterna i en och samma fil och testa koden själv innan du använder rättningsskriptet. Att kunna testa sin kod är en viktig del av att programmera!

Att lära dig från uppgifterna

  • Känna till begreppen vektor och matris.
  • Kunna utföra grundläggande beräkningar med vektorer i Python.
  • Kunna utföra grundläggande matrisoperationer i Python.

Man kan få max 50 poäng och för att få godkänt krävs 30 poäng (45 för väl godkänt). Försök dock att lösa alla uppgifter då inte alla fel upptäcks av rättningsskriptet. Om ni har lite marginal kan ni kanske bli godkända även om assistenten som rättar hittar något sådant fel.

Uppgift 14.1 (5p)

Skriv en funktion pprint_mat som skriver ut en matris representerad som en tupel av tupler på ett snyggare sätt som nedan. Funktionen ska hantera hantera matriser av godtycklig storlek men ska inte ta hänsyn till hur många siffror som behövs för att representera varje tal (se pprint_mat2 för att lösa det problemet).

OBS! Den här uppgiften visade sig vara mycket svårare än jag hade tänkt mig. Det viktigaste här är att ni får ihop något sätt att skriva ut matriser representerade som nästlade tupler med varje inre tupel på sin egen rad. Detta är viktigt så att ni kan använda denna när ni testar och eventuellt felsöker de andra uppgifterna. Lägg inte ner mer tid än nödvändigt för att få ihop exakt rätt utskrift, det är inte poängen här. Det är bättre att ni går vidare till nästa uppgift och kommer tillbaka hit senare om ni har tid över. Efter de utökade exemplen kommer tre olika lösningsmetoder som ni kan använda som inspiration. //Johan

Exempel

>>> pprint_mat(())
()
>>> pprint_mat(((1, 2), ))
( (1, 2) )
>>> pprint_mat(((1, 2), (3, 4)))
( (1, 2),
  (3, 4) )
>>> pprint_mat(((1, 2), (3, 4), (5, 6)))
( (1, 2),
  (3, 4),
  (5, 6) )

Lösningstips: Alternativ 1 — Fallanalys

Vi kan lösa detta genom att göra en så kallad fallanalys. Vi har pratat informellt om den här metoden tidigare men bara nämnt namnet i förbigående. Här kommer en praktisk tillämpning av den. Vi kan dela upp problemet i flera olika fall beroende på hur många rader matrisen har. Vi kan sedan skriva kod för varje fall. I slutet sätter vi ihop allt med hjälp av if/elif/else. Så vilka fall har vi?

  • Om matrisen är tom (dvs. har 0 rader) så ska vi skriva ut en tom tupel.
>>> pprint_mat(())
()
  • Om matrisen har 1 rad så ska vi skriva ut den raden omsluten av mellanslag och parenteser, t.ex.
>>> pprint_mat((1, 2))
( (1, 2) )
  • Om matrisen har 2 rader så ska vi skriva ut t.ex.:
>>> pprint_mat(((1, 2), (3, 4)))
( (1, 2), 
  (3, 4) )
  • Om matrisen har fler än 2 rader så ska vi skriva ut t.ex.:
>>> pprint_mat(((1, 2), (3, 4), (5, 6)))
( (1, 2),
  (3, 4),
  (5, 6) )

Tänker vi efter lite så inser vi snabbt att fallet med 2 rader egentligen är ett specialfall av fallet med fler än 2 rader. Vi kan alltså slå ihop de två fallen. Vi får då följande fall:

  • Om matrisen är tom (dvs. har 0 rader) så ska vi skriva ut ().
  • Om matrisen har 1 rad så ska vi skriva ut den raden omsluten av mellanslag och parenteser.
  • Om matrisen har fler än 1 rad så ska vi skriva ut:
    • Den första raden (matrix[0]) med en vänsterparentes ('(') och ett mellanslag innan och ett kommatecken och en radbrytning efter (radbrytningen får vi automatiskt av print).
    • Eventuella rader mellan första och sista raden (matrix[1:-1]) med två mellanslag innan och ett kommatecken och en radbrytning efter (radbrytningen får vi automatiskt av print).
    • Den sista raden med två mellanslag innan och ett mellanslag och en högerparentes (')') efter.

Detta lämpar sig väl för att implementeras med if/elif/else med en for-loop för att hantera de mellersta raderna i else-klausulen.

Lösningstips: Alternativ 2 — Stegvis konstruktion

Vi kan också lösa problemet med en stegvis konstruktion av en sträng som vi sedan skriver ut. Vi börjar med en tom sträng (eller lista) som vi kallar result och lägger sedan till olika delar beroende på hur många rader matrisen har. Vi kan använda for-loopar för att hantera de olika raderna i matrisen. Här är en möjlig strategi:

  1. Börja med en tom sträng result.
  2. Lägg till en vänsterparentes ('(') i result.
  3. Använd en for-loop för att iterera över raderna i matrisen:
    • Om det är den första raden (matrix[0]), lägg till ett mellanslag (' ') i result, annars lägg till två mellanslag (' ').
    • Lägg till strängrepresentationen av raden i result (använd str för att få strängrepresentationen).
    • Om det är den sista raden (matrix[-1]), lägg till ett mellanslag i result, annars lägg till ett kommatecken och en radbrytning.
  4. Lägg till en högerparentes (')') i result.
  5. Skriv ut result. Använde ni en lista istället för en sträng så kan ni använda ''.join(result) för att konvertera listan till en sträng innan ni skriver ut den.

Lösningstips: Alternativ 3 — Omskrivning av en sträng

Vi kan också lösa problemet genom att först skapa en enkel strängrepresentation av matrisen med str(matrix) och sedan göra en omskrivning av den strängen med str.replace-metoden för att få rätt format. Här är en möjlig strategi:

  1. Skapa en strängrepresentation av matrisen med str(matrix).
  2. Ersätt alla förekomster av (( med ( ( (ett mellanslag mellan vänsterparenteserna i början).
  3. Ersätt alla förekomster av )) och ),) med ) ) (ett mellanslag mellan högerparenteserna på sista raden).
  4. Ersätt alla förekomster av , ( med ,\n ( (en radbrytning och två mellanslag mellan varje inre tupel).
  5. Skriv ut den resulterande strängen.

Uppgift 14.2 (0p)

Funktionen pprint_mat fungerar bra när alla tal har samma antal siffror, men blir mer otydlig när när matrisen innehåller element med olika antal siffror. Skriv en funktion pprint_mat2 som skriver ut en matris representerad som en tupel av tupler på ett ännu snyggare sätt, som nedan.

Alla kolumner ska alltså skrivas ut med samma bredd, där bredden bestäms av det element i matrisen som kräver mest utrymme. Funktionen ska hantera hantera matriser av godtycklig storlek.

(Vill du istället att varje kolumn ska ha olika bredd beroende på det element i kolumnen som kräver mest utrymme? Lös då uppgiften pprint_mat3.)

Exempel

# Första versionen:
>>> pprint_mat(((-1, 2, 0), (83, -42, 6), (1, 6, 9)))
( (-1, 2, 0), 
  (83, -42, 6), 
  (1, 6, 9) )
# Nya versionen, notera att varje element har 3 tecken utrymme eftersom -42 är det element som kräver mest utrymme och är just 3 tecken långt:
>>> pprint_mat2(((-1, 2, 0), (83, -42, 6), (1, 6, 9)))
( ( -1,   2,   0),
  ( 83, -42,   6),
  (  1,   6,   9) )

Uppgift 14.3 (0p)

Vi har sett två sätt att skriva ut matriser. Det första fick problem när elementen i matrisen hade olika antal siffror, och det andra löste det problemet men gjorde att alla kolumner fick samma bredd, vilket blir svåröverskådat om bara någon enstaka kolumn behöver den bredden.

Skriv en funktion pprint_mat3 som skriver ut en matris representerad som en tupel av tupler på ett tredje sätt, som nedan. Här ska varje kolumn anpassas efter det bredaste elementet i den kolumnen.

Exempel

# Första versionen:
>>> pprint_mat(((-1, 2, 0), (83, -42, 6), (1, 6, 9)))
( (-1, 2, 0), 
  (83, -42, 6), 
  (1, 6, 9) )
# Andra versionen:
>>> pprint_mat2(((-1, 2, 0), (83, -42, 6), (1, 6, 9)))
( ( -1,   2,   0),
  ( 83, -42,   6),
  (  1,   6,   9) )
# Tredje versionen, notera att alla kolumner har olika bredd, baserat på det element i kolumnen som kräver mest utrymme:
>>> pprint_mat3(((-1, 2, 0), (83, -42, 6), (1, 6, 9)))
( (-1,   2, 0),
  (83, -42, 6),
  ( 1,   6, 9) )

Uppgift 14.4 (5p)

Skriv funktionen get_first_column(matrix) som tar in en nästlad tupel som representerar en matris. Varje element i matrix är alltså en tupel som representerar en rad i matrisen. Alla inre tupler är lika långa. Se exemplet nedan.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# nedan är en nästlad tupel som representerar en 3x3-matris
m1 = ((1, 2, 4),
      (3, 0, 6),
      (0, 5, 1))

# nedan är en nästlad tupel som representerar en 3x4-matris
m2 = ((1, 2, 4, 5),
      (3, 0, 6, 5),
      (0, 5, 1, 5))

# nedan är en nästlad tupel som representerar en 4x2-matris
m3 = ((1, 2),
      (3, 0),
      (0, 5),
      (4, 7))

Funktionen get_first_column(matrix) ska returna en tupel som innehåller värdena i den första kolumnen i matrisen som en tupel. För m1 ska alltså tupeln (1, 3, 0) returneras. För m2 ska (1, 3, 0) returnaeras. För m3 ska (1, 3, 0, 4) returneras.

Tips: För att komma åt det första elementet i den första tupeln i m definierad ovan, skriver man m[0][0]. För att komma åt det tredje elementet i den andra tupeln skriver man m[1][2].

Exempel

>>> get_first_column(m1)
(1, 3, 0)
>>> get_first_column(m2)
(1, 3, 0)
>>> get_first_column(m3)
(1, 3, 0, 4)

Uppgift 14.5 (5p)

Skriv funktionen get_nth_column(n, matrix) som tar in en nästlad tupel som representerar en matris. Varje element i matrix är alltså en tupel som representerar en rad i matrisen. Alla inre tupler är lika långa.

Funktionen get_nth_column(n, matrix) ska returnera kolumnen n, där den första kolumnen har n == 1.

Exempel

Här återanvänds matriserna m1, m2 och m3 från uppgiften get_first_column-uppgiften.

>>> get_nth_column(2, m1)
(2, 0, 5)
>>> get_nth_column(4, m2)
(5, 5, 5)
>>> get_nth_column(2, m3)
(2, 0, 5, 7)

Uppgift 14.6 (10p)

Skriv funktionen transpose(matrix) som tar in en nästlad tupel som representerar en matris. Varje element i matrix är alltså en tupel som representerar en radvektor i matrisen. Alla inre tupler är lika långa.

Funktionen transpose(matrix) ska returnera transponatet, $M^T$, av matrisen matrix. Dvs. en tupel där varje element är en kolumnvektor i matrix.

Exempel

>>> mat = ((1, 2), (3, 4), (5, 6))
>>> pprint_matrix(mat)
( (1, 2),
  (3, 4),
  (5, 6) )
>>> pprint_matrix(transpose(mat))
( (1, 3, 5),
  (2, 4, 6) )
>>> mat2 = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
>>> pprint_matrix(mat2)
( (1, 2, 3),
  (4, 5, 6),
  (7, 8, 9) )
>>> pprint_matrix(transpose(mat2))
( (1, 4, 7),
  (2, 5, 8),
  (3, 6, 9) )

Uppgift 14.7 (5p)

Skriv en funktion vector_add(v1, v2) som tar två vektorer, representerade som tupler, av samma dimension och returnerar en tupel som representerar den vektor man får om man adderar dem.

Exempel

>>> vector_add((4,1,7), (5,2,8))
(9, 3, 15)
>>> vector_add((1,2), (3,4))
(4, 6)
>>> vector_add((1,2,3,4), (5,6,7,8))
(6, 8, 10, 12)
>>> vector_add((1,), (2,))
(3,)
>>> vector_add((1, -2, 3), (-1, 2, -3))
(0, 0, 0)

Uppgift 14.8 (5p)

Skriv funktionen dot_product(vec1, vec2) som tar in två vektorer representerade som tupler och returnerar skalärprodukten av vektorerna. Antag att båda tuplerna är lika långa.

Skalärprodukten räknas ut enligt nedan för 3-vektorer, dot_product ska dock kunna hantera vektorer av godtycklig dimension:

$(1, 2, 4) \cdot (1, 3, 0) = 1 \times 1 + 2 \times 3 + 4 \times 0 = 7$

Exempel

>>> dot_product((1, 1, 1), (2, 2, 2))
6
>>> dot_product((1, 2, 3), (4, 5, 6))
32
>>> dot_product([-6, -2], [-9, -3])
60
>>> dot_product([-7, -8, -3], [9, -6, -3])
-6
>>> dot_product([-6, -4], [2, -8])
20

Uppgift 14.9 (15p)

Skriv funktionen matrix_square(matrix) som ska returnera kvadraten av matrisen matrix som representeras av nästlade tupler. Varje element $a_{ij}$ i den nya matrisen fås av skalärprodukten av rad $i$ med kolumn $j$.

Om vi använder oss av samma matris om i exemplet i uppgift 3.2.4

1
2
3
4
# nedan är en nästlad tupel som representerar en 3x3-matris
m = ((1, 2, 4),
     (3, 0, 6),
     (0, 5, 1))

så är elementet på den första raden i den första kolumnen i den nya matrisen skalärprodukten av den första raden i m och den första kolumnen i m.

Elementet på första raden, andra kolumnen är $(1, 2, 4) \cdot (2, 0, 5) = 22$ och så vidare. Den nya matrisen som matrix_square(matrix) ska returnera givet m som input är alltså

1
2
3
((7, 22, 20),
 (3, 36, 18),
 (15, 5, 31))

Uppgift 14.10 (0p)

Skriv en funktion make_vector(elements) som returnerar en closure som representerar en vektor. Funktionen ska ta en tupel elements, av heltal och/eller flyttal, som argument och returnera en closure som kan ta emot följande meddelanden:

  • "dimension": Returnerar dimensionen (antalet element) i vektorn.
  • "get": Returnerar en kopia av elementen i vektorn som en tupel.
  • "length": Returnerar längden (normen) av vektorn, beräknad som kvadratroten av summan av kvadraterna av elementen.
  • "add": Tar emot en annan vektor (skapad med make_vector) och returnerar en ny vektor som är summan av de två vektorerna. Om vektorerna har olika dimensioner ska strängen "ValueError: Vectors must have the same dimension" returneras.
  • "dot": Tar emot en annan vektor (skapad med make_vector) och returnerar skalärprodukten av de två vektorerna. Om vektorerna har olika dimensioner ska strängen "ValueError: Vectors must have the same dimension" returneras.
  • "scale": Tar emot ett tal (heltal eller flyttal) och returnerar en ny vektor som är den ursprungliga vektorn skalad med det talet.
  • "cos": Tar emot en annan vektor (skapad med make_vector) och returnerar cosinusvärdet av vinkeln mellan de två vektorerna. Om vektorerna har olika dimensioner ska strängen "ValueError: Vectors must have the same dimension" returneras. Om någon av vektorerna är nollvektorn (dvs. har längden 0) ska strängen "ValueError: Cannot compute angle with zero-length vector" returneras. Cosinus mellan två vektorer $\mathbf{a}$ och $\mathbf{b}$ kan beräknas med formeln: $$ \cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{|\mathbf{a}| |\mathbf{b}|} $$
  • "type": Returnerar strängen "vector".
  • "methods": Returnerar en tupel med alla tillgängliga meddelanden som vektorn kan hantera.

Tips: Kvadratroten av ett tal x kan beräknas antingen med hjälp av math.sqrt(x) eller genom att beräkna x ** 0.5.

Exempel

>>> vec1 = make_vector((1, 2, 3))
>>> vec1('dimension')
3
>>> vec1('get')
(1, 2, 3)
>>> vec1('length')
3.7416573867739413
>>> vec2 = make_vector((4, 5, 6))
>>> vec3 = vec1('add', (vec2,))
>>> vec3('get')
(5, 7, 9)
>>> vec1('dot', (vec2,))
32
>>> vec4 = vec1('scale', (2,))
>>> vec4('get')
(2, 4, 6)
>>> vec1('cos', (vec2,))
0.9746318461970762
>>> vec1('type')
'vector'
>>> vec1('methods')
('dimension', 'get', 'length', 'add', 'dot', 'scale', 'type', 'methods')

Uppgift 14.11

Vi ska nu stegvis bygga upp en closure som representerar en matris som kan bearbetas med hjälp av olika metoder.

14.11.1 (0p)

Skriv en funktion make_matrix(elements) som returnerar en closure som representerar en matris. Funktionen ska ta en nästlad tupel elements, av heltal och/eller flyttal, som representerar en matris som argument. Antag att elements är en icke-tom nästlad tupel där alla rader har samma längd. Funktionen make_matrix ska returnera en closure som kan ta emot följande meddelanden:

  • "dimension": Returnerar dimensionen (antalet element) i matrisen som en tupel med två element.
  • "get": Returnerar en kopia av elementen i matrisen som en nästlad tupel.
  • "type": Returnerar strängen "matrix".

Om ett okänt kommando ges ska en sträng med ett felmeddelande returneras: "MethodError: 'matrix' object has no method 'unknown_command'" där unknown_command är det kommando som gavs. Om fel antal argument skickas med till ett kommando ska en sträng med ett felmeddelande returneras: "ArgumentError: method 'command' takes exactly n arguments (m given)" där command är kommandot, n är det förväntade antalet argument och m är det faktiska antalet argument som gavs.

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> mat1('dimension')
(2, 2)
>>> mat1('get')
((1, 2), (3, 4))
>>> mat1('type')
'matrix'
>>> mat1('determinant')
"MethodError: 'matrix' object has no method 'determinant'"
>>> mat1('get', (1,))
"ArgumentError: method 'get' takes exactly 0 arguments (1 given)"

14.11.2 (0p)

Utöka make_matrix så att den closure som returneras kan hantera följande meddelanden:

  • "str": Returnerar en strängrepresentation av matrisen enligt formatet i pprint_mat3 (skall inte skriva ut något, bara returnera en sträng).

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> print(mat1('str'))
( (1, 2),
  (3, 4) )

14.11.3 (0p)

Utöka make_matrix så att den closure som returneras kan hantera följande meddelanden:

  • "row": Tar emot ett heltal i och returnerar radvektorn i rad i som en vektor (skapad med make_vector). Om i är utanför intervallet för antalet rader i matrisen ska strängen "IndexError: Row index out of range" returneras. Radindexering börjar på 1.
  • "column": Tar emot ett heltal j och returnerar kolumnvektorn i kolumn j som en vektor (skapad med make_vector). Om j är utanför intervallet för antalet kolumner i matrisen ska strängen "IndexError: Column index out of range" returneras. Kolumnindexering börjar på 1.

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> mat1('row', (3,))
'IndexError: Row index out of range'
>>> vec1 = mat1('row', (1,))
>>> vec1('get')
(1, 2)
>>> mat1('column', (3,))
'IndexError: Column index out of range'
>>> vec2 = mat1('column', (2,))
>>> vec2('get')
(2, 4)

14.11.4 (0p)

Utöka make_matrix så att den closure som returneras kan hantera följande meddelanden:

  • "add": Tar emot en annan matris (skapad med make_matrix) och returnerar en ny matris som är summan av de två matriserna. Om matriserna har olika dimensioner ska strängen "ValueError: Matrices must have the same dimension" returneras.

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> mat2 = mat1('add', (mat1,))
>>> print(mat2('str'))
( (2, 4),
  (6, 8) )
>>> mat3 = make_matrix(((2, 3), (5, 7)))
>>> print(mat1('add', (mat3,))('str')) # Obs! att det går bra att kedja anrop på det här sättet, dvs. anropa 'str' på resultatet av 'add'
( (3,  5),
  (8, 11) )
>>> mat4 = make_matrix(((1, 2, 3), (4, 5, 6)))
>>> mat1('add', (mat4,))
'ValueError: Matrices must have the same dimension'

14.11.5 (0p)

Utöka make_matrix så att den closure som returneras kan hantera följande meddelanden:

  • "scale": Tar emot ett tal (heltal eller flyttal) och returnerar en ny matris som är den ursprungliga matrisen skalad med det talet.
  • "multiply": Tar emot en annan matris (skapad med make_matrix) och returnerar en ny matris som är produkten av de två matriserna. Om matriserna inte kan multipliceras (dvs. antalet kolumner i den första matrisen är inte lika med antalet rader i den andra matrisen) ska strängen "ValueError: Incompatible matrix dimensions for multiplication" returneras.

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> print(mat1('scale', (0.7,))('str')) # Obs! att precisionsbrist i flyttal kan ge något olika resultat men skall vara nära nedanstående
( (               0.7, 1.4),
  (2.0999999999999996, 2.8) )
>>> mat2 = make_matrix(((5, 6, 7), (8, 9, 10)))
>>> print(mat1('multiply', (mat2,))('str'))
( (21, 24, 27),
  (47, 54, 61) )
>>> mat2('multiply', (mat1,))
'ValueError: Incompatible matrix dimensions for multiplication'

14.11.6 (0p)

Utöka make_matrix så att den closure som returneras kan hantera följande meddelanden:

  • "square": Returnerar en ny matris som är kvadraten av den ursprungliga matrisen (dvs. matrisen multiplicerad med sig själv). Om matrisen inte är kvadratisk ska strängen "ValueError: Matrix must be square to compute square" returneras.

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> print(mat1('square'))('str'))
SyntaxError: unmatched ')' (<string>, line 1)
>>> mat2 = make_matrix(((5, 6, 7), (8, 9, 10)))
>>> mat2('square')
'ValueError: Matrix must be square to compute square'

14.11.7 (0p)

Utöka make_matrix så att den closure som returneras kan hantera följande meddelanden:

  • "transpose": Returnerar en ny matris som är transponatet av den ursprungliga matrisen.

Exempel

>>> mat1 = make_matrix(((1, 2), (3, 4)))
>>> print(mat1('transpose')('str'))
( (1, 3),
  (2, 4) )
>>> mat2 = make_matrix(((5, 6, 7), (8, 9, 10)))
>>> print(mat2('transpose')('str'))
( (5,  8),
  (6,  9),
  (7, 10) )

Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2025-08-05