Uke 11
Innlevering, og Krav for å bestå
- Du kan levere så mange ganger du vil. Siste innlevering teller.
- For å få bestått, må du få 100% riktig av CodeGrade på alle 3 oppgavene.
Generelt tips: les alltid gjennom kursnotatene før du begynner på lab’en!
- Oppgave 1: egen modul for data normalisering
- Oppgave 2: beregn π med tilfeldige tall
- Oppgave 3: Produserer en kvittering fra en butikk.
Oppgave 1: egen modul
DEL A
I denne oppgaven skal vi lage vår egen modul data_normalization
i filen data_normalization.py, samt et eksempel-program i filen uke_11_oppg_1.py.
La innholdet i filen uke_11_oppg_1.py være nøyaktig:
import data_normalization
data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Normalizing the data to [0, 1] range
a = data_normalization.norm_0_1(data)
# Normalizing the data to [-1, 1] range
b = data_normalization.norm_neg1_1(data)
assert a == [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0]
assert b == [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]
Funksjonen norm_0_1(x)
normaliserer verdiene i listen x
til å ligge i området mellom 0 og 1. Funksjonen først finner minimums- og maksimumsverdien i listen, og deretter normaliserer den hvert element i listen til å være i området mellom 0 og 1. Dette gjøres ved å trekke fra minimumsverdien, og deretter dividere med differansen mellom maksimums- og minimumsverdien.
$$ \frac{x_i - \text{min}(x)}{\text{max}(x) - \text{min}(x)} $$
Funksjonen norm_neg_1_1(x)
normaliserer verdiene i listen x til å ligge i området mellom -1 og 1. Funksjonen finner også minimums- og maksimumsverdien i listen, men deretter normaliserer den hvert element i listen til å være i området mellom -1 og 1. Dette gjøres ved å trekke fra minimumsverdien, multiplisere med 2 og deretter dividere med differansen mellom maksimums- og minimumsverdien. Til slutt trekkes det fra 1, slik at verdiene ligger i området mellom -1 og 1.
$$ \frac{2(x_i - \text{min}(x))}{\text{max}(x) - \text{min}(x)} - 1 $$
Oppgaven går ut på å skrive modulen data_normalization
slik at funksjonskallene over fungerer.
DEL B
I tillegg til at modulen skal kunne importeres uten at det blir noen utskrift til skjermen, skal det også være mulig å kjøre data_normalization.py direkte for å få en interaktiv modus hvor brukeren kan skrive inn tallene selv. Her kan vi bruke __name__
.
En eksempelkjøring av data_normalization.py (fet/grønn tekst skrevet av brukeren):
Normaliser data.
Oppgi data du vil normalisere, separert av mellomrom:
1 2 3 4 5 6 7 8 9
norm_0_1: [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0]
norm_neg1_1: [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]
Ytterligere krav:
- Alle funksjonene skal ha en liste med flyttall som parameter (lister med int er også OK, siden int er en undertype av float).
- Alle funksjonene skal være ikke-destruktive.
- Alle tall skal behandles som flyttall, og returverdien skal alltid være et liste med flyttall.
Oppgave 2: beregn π med tilfeldige tall
I denne oppgaven skal vi bruke modulen random
fra python sitt standardbibliotek for å beregne verdien av \(\pi\). Selvfølgelig finnes en tilnærming for denne verdien allerede (f. eks. math.pi
), men la oss forestille oss at vi nå ikke hadde denne beregningen fra før.[^1]
Den beste tilnærmingen vi har til π i skrivende stund inneholder over \(10^{12}\) siffer. I python er tilnærmingen av gitt ved
math.pi
begrenset til de første 15 siffrene av pi, men det finnes også andre moduler som gir en bedre tilnærming; men da må vi slutte å brukefloat
, og heller bruke en annen datatype som er i stand til å gi oss høyere presisjon.
π er definert som en sirkels omkrets delt på sin diameter. Uten å vite nøyaktig hvilken verdi π har, kan matematikere bevise at denne verdien, om vi en vakker dag skulle få vite hva den er, kan brukes for å regne ut en sirkel sitt areal:
$$ A = \pi \cdot r^2 $$
I formelen over er \(r\) er sirkelens radius.
Tenk deg nå at vi har en sirkel med sentrum i (0, 0) og radius \(r = 1\). Vi har også et kvadrat med sidelengde \(s = 2\), som går mellom punktene (-1, -1) og (1, 1), som illustrert i bildet under.
Vi legger verdiene for r og s inn i formelene for areal av henholdsvis sirkler og kvadrat, og får følgende:
$$A_{\bigcirc} = \pi \cdot r^2 = \pi \cdot 1^2 = \pi$$ $$A_{\square} = s^2 = 2^2 = 4$$
Siden arealet av sirkelen tydelig er mindre enn arealet av kvadratet, vet vi allerede nå at π må være mindre enn 4. Men vi kan gjøre en bedre tilnærming enn som så.
Dersom vi velger et helt tilfeldig punkt inne i kvadratet, vil punktet med viss sannsynlighet \(p\) havne inne i sirkelen. Merk at denne sannsynligheten \(p\) er gitt ved forholdet mellom arealet av sirkelen og kvadratet:
$$ p = \frac{A_{\bigcirc}}{A_{\square}} = \frac{\pi}{4}$$
Det følger at hvis vi klarer å beregne hva \(p\) er for noe, kan vi bare gange tallet med 4 for å finne π. Det leder oss til følgende algoritme:
- Gjett et tilfeldig punkt mellom (-1, -1) og (1, 1)
- Sjekk om punktet er innenfor eller utenfor sirkelen
- Gjenta steg 1-2 tilstrekkelig mange ganger, og tell opp hvor ofte det tilfeldige tallet havnet inne i eller utenfor sirkelen. Da bli forholdet mellom antall treff i sirkel og totalt antall genererte punkter tilnærmet lik \(p\).
- Gang opp \(p\) med 4 for å finne en tilnærmet verdi for \(\pi\).
I uke_11_oppg_2.py skriv en funksjon find_pi(n)
med en parameter n
som angir hvor mange tilfeldige punkter som skal genereres. La funksjonen beregne \(\pi\) ved å generere n
tilfeldige koordinater mellom (-1, -1) og (1, 1) og telle opp hvor mange av dem som havnet innefor sirkelen og hvor mange som havnet utenfor.
Oppgave 3: Produserer en kvittering fra en butikk.
I filen uke_11_oppg_3.py skal du bruke modulen decimals til å skrive et program som produserer en kvittering fra en butikk.
Koden din vil sannsynligvis ikke fungere hvis du ikke bruker decimals-modulen for å lagre tall, siden avrundingsfeil er veldig viktig å unngå, samtidig som vi skal bruke desimal-tall.
Ta utgangspunt i koden nedenfor.
def receipt_content(prices_filename, cash_register_filename):
"""Construct contents of a receipt of the cash register events,
given the store prices."""
# din kode her
def receipt(prices_filename, cash_register_filename):
"""Construct a receipt of the cash register events,
given the store prices."""
# get receipt content from receipt_content()
purcases_returns, total, vat, payment, change = receipt_content(
prices_filename, cash_register_filename
)
# the formatted lines of the receipt
receipt_lines = [f"{'Nr.':>4} {'Item':18} {'Price':>10}"]
for nr, item, price in purcases_returns:
receipt_lines.append(f"{nr:4d} {item:18} {price:10.2f}")
receipt_lines.append(f"Total: {total}")
receipt_lines.append(f"Of which VAT: {vat:.2f}")
receipt_lines.append(f"Payment: {payment}")
receipt_lines.append(f"Change {change}")
# add some dividers
max_len = max(len(line) for line in receipt_lines)
divider = "-" * max_len
receipt_lines.insert(1, divider)
receipt_lines.insert(-4, divider)
receipt_lines.insert(-2, divider)
return "\n".join(receipt_lines)
Du skal skrive koden til funksjonen receipt_content()
som beregner inneholdet i kvitteringen utifra en fil med butikkens priser og en fil med hendelsene ved kassen. Argumentet prices_filename
er en streng med navnet til filen som inneholder prisene. Argumentet cash_register_filename
er en streng med navnet til filen som inneholder hendelsene ved kassen.
Funksjonen receipt_content()
skal returnere en tupel som inneholder følgende (i denne rekkefølgen):
- En liste med tupler som inneholder (i følgende rekkefølge) antall, produkt og total pris, først for alle ting som har blitt kjøpt, i alfabetisk orden, og så for alle ting som har blitt returnert, i alfabetisk orden (se eksempelkvittering nedenfor). For de returnerte produktene blir det negative tall.
- Den totale prisen.
- Hvor mye av den totale prisen som er mva.
- Hvor mye som har blitt betalt.
- Hvor mye som blir betalt tilbake (her blir det ikke-positive tall).
Filen med butikkens priser er formattert slik som eksempelfilen prices.txt (som du kan bruke til å teste programmet ditt). Først står produkt, så semikolon (;
), og så prisen. Dette er egentlig en CSV-fil som bruker semikolon som skilletegn.
Filen med hendelsene ved kassen er formattert slik som eksempelfilen cash_register.txt (som du kan bruke til å teste programmet ditt). Først står hva som skjer (kjøp, retur eller betaling), så semikolon (;
), og så produkten/verdien. Dette er også egentlig en CSV-fil som bruker semikolon som skilletegn.
Funksjonen receipt()
skal du ikke endre. Den bruker receipt_content()
til å beregne inneholdet i kvitteringen og så produserer receipt()
en pen kvittering utifra det innholdet. Du skal bare skrive kode til receipt_content()
.
Om din kode til receipt_content()
er riktig så skal denne testen passere:
print("Tester receipt... ", end="")
expected_value = """\
Nr. Item Price
------------------------------------
2 apple 10.00
1 chips 24.30
1 dish soap 26.20
1 frozen pizza 54.40
1 peanuts 18.50
1 toilet paper 34.00
3 tomato 30.00
-1 pocket book -149.00
-1 toothpaste -13.70
------------------------------------
Total: 34.70
Of which VAT: 6.94
------------------------------------
Payment: 100.00
Change -65.30"""
assert(expected_value == receipt("prices.txt", "cash_register.txt"))
print("OK")
Du må bruke biblioteket decimals for at beregningene skal bli riktige (prøv hvordan kvitteringen blir om du bruker float).
Decimals aksepterer både float og string for å lage decimals. Prøv hvordan kvitteringen blir om du gir decimals en float. Hvorfor skjer dette?
Det er mulig at betaling skjer flere ganger underveis ved kassen.
Du kan anta at alle betalinger ved kassen har positiv verdi.
Du kan anta at det ikke er noen feil i filene som inneholder prisene og hendelsene ved kassen.
Det er mulig at den totale prisen blir negativt (om kunden for eksempel bare returnerer ting).
Gitt en pris inklusive mva så multipliserer du prisen med 0.2 for å finne ut hvor mye av prisen som er mva.
Det kan være lurt å bruke dictionaries til prislisten og det som blitt kjøpt og det som blitt returnert.
Du må ikke skrive all kode innen funksjonen receipt_content(). Du kan dele opp koden i flere funksjoner på en måte du synes føles naturlig.