Extra

Deze pagina bevat uitvoerbare code.

De extra heeft drie delen: Pijlenpad, Periodiek T-shirt en Caesar op orde. Elk deel staat op zichzelf: je hebt de code van een ander deel niet nodig.

Pijlenpad

Pijlenpad is een puzzel van Janko. Je krijgt een vierkant rooster van n bij n vakjes. In elk vakje behalve dat rechtsonder staat een pijl. Je vult de getallen 1 tot en met n² elk één keer in, zo dat je vanaf elk getal het volgende getal vindt door de pijl te volgen. Het volgende getal staat ergens in de richting van de pijl, niet per se in het vakje ernaast. Linksboven staat altijd 1 en rechtsonder n²; soms staan er al meer getallen.

In deze opgave laat je de computer de puzzel oplossen. Bij elk getal zijn er soms meer vakjes waar het volgende getal kan staan, en vooraf weet je niet welk vakje goed is. Je probeert dus elke keuze, en geeft het op zodra je vastloopt.

De puzzel als lijst van lijsten

Een puzzel is een lijst van rijen. Elke rij is een lijst van vakjes, en elk vakje is een lijst met twee elementen: het getal en de pijl. Een getal dat nog niet bekend is, is 0. De pijl is een windrichting: "N" is omhoog, "O" naar rechts, "ZW" schuin naar linksonder, enzovoort. Het vakje rechtsonder heeft geen pijl: "".

Een plek in het rooster schrijf je als [row, col], met de rij en de kolom, en beide tellen vanaf 0. [0, 0] is linksboven. Een pad is een lijst van plekken in de volgorde van de getallen: op de eerste plek staat 1, op de tweede 2, enzovoort.

Dit is de eerste puzzel, met zijn oplossing:

Puzzel 1: een rooster van 3 bij 3 met in elk vakje een pijl, linksboven 1 en rechtsonder 9

De oplossing van puzzel 1: alle getallen van 1 tot en met 9 ingevuld

Vanaf de 1 linksboven wijst de pijl schuin naar rechtsonder ("ZO"). Daar staan twee vakjes: het middelste en dat rechtsonder. Rechtsonder staat al 9, dus de 2 moet in het middelste vakje. Zo gaat het verder, tot de 8 rechtsboven: die wijst omlaag, naar de 9.

De tweede puzzel is groter:

Puzzel 2: een rooster van 5 bij 5 met pijlen, linksboven 1, links in de vierde rij 17 en rechtsonder 25

De oplossing van puzzel 2: alle getallen van 1 tot en met 25 ingevuld

De cel hieronder zet de windrichtingen, de twee puzzels en hun oplossingen klaar. Bij elke windrichting geeft DIRECTIONS hoeveel rijen en kolommen je per stap opschuift. Voer de cel eerst uit.

DIRECTIONS = {
    "N": [-1, 0],
    "NO": [-1, 1],
    "O": [0, 1],
    "ZO": [1, 1],
    "Z": [1, 0],
    "ZW": [1, -1],
    "W": [0, -1],
    "NW": [-1, -1],
}

PUZZLE_1 = [
    [[1, "ZO"], [0, "ZW"], [0, "Z"]],
    [[0, "Z"], [0, "N"], [0, "N"]],
    [[0, "O"], [0, "NO"], [9, ""]],
]

SOLUTION_1 = [
    [[1, "ZO"], [3, "ZW"], [8, "Z"]],
    [[4, "Z"], [2, "N"], [7, "N"]],
    [[5, "O"], [6, "NO"], [9, ""]],
]

PUZZLE_2 = [
    [[1, "Z"], [0, "O"], [0, "ZO"], [0, "W"], [0, "W"]],
    [[0, "NO"], [0, "ZO"], [0, "O"], [0, "Z"], [0, "ZW"]],
    [[0, "ZO"], [0, "ZW"], [0, "N"], [0, "ZW"], [0, "ZW"]],
    [[17, "N"], [0, "NO"], [0, "NW"], [0, "NW"], [0, "N"]],
    [[0, "O"], [0, "W"], [0, "W"], [0, "NO"], [25, ""]],
]

SOLUTION_2 = [
    [[1, "Z"], [3, "O"], [6, "ZO"], [5, "W"], [4, "W"]],
    [[2, "NO"], [24, "ZO"], [21, "O"], [22, "Z"], [13, "ZW"]],
    [[18, "ZO"], [16, "ZW"], [20, "N"], [14, "ZW"], [7, "ZW"]],
    [[17, "N"], [19, "NO"], [15, "NW"], [23, "NW"], [12, "N"]],
    [[10, "O"], [9, "W"], [8, "W"], [11, "NO"], [25, ""]],
]

Wat je gaat maken

Stap

Functie

Doet

Wat je oefent

1

targets

alle plekken in de richting van de pijl

een while-lus die in de richting van de pijl door het rooster loopt

2

next_cells

de plekken waar het volgende getal kan staan

een list comprehension

3

extend_path

maak een pad af, of geef het op

een recursieve aanroep in een lus over de keuzes

4

fill

zet de getallen van een pad in de puzzel

een geneste list comprehension

5

arrow_path

los de puzzel op

stap 3 en 4 samen

Stap 1: targets(board, row, col)

Geeft een lijst met alle plekken die vanaf [row, col] in de richting van de pijl liggen, van dichtbij naar ver weg, tot de rand van het rooster. Heeft het vakje geen pijl, dan is de lijst leeg.

Aanroep

Resultaat

targets(PUZZLE_1, 0, 0)

[[1, 1], [2, 2]]

targets(PUZZLE_2, 3, 0)

[[2, 0], [1, 0], [0, 0]]

targets(PUZZLE_1, 2, 2)

[]

Hint

De pijl is board[row][col][1], en DIRECTIONS geeft de stap in rijen en kolommen. Tel die stap in een while-lus steeds op bij de rij en de kolom, zolang je binnen het rooster blijft: een rij of kolom van 0 tot en met len(board) - 1.

# jouw oplossing
assert targets(PUZZLE_1, 0, 0) == [[1, 1], [2, 2]]
assert targets(PUZZLE_1, 0, 2) == [[1, 2], [2, 2]]
assert targets(PUZZLE_2, 3, 0) == [[2, 0], [1, 0], [0, 0]]
assert targets(PUZZLE_1, 2, 2) == []

Stap 2: next_cells(board, path)

path is een pad dat tot nu toe klopt. Geeft een lijst met de plekken waar het volgende getal kan staan. Het volgende getal is len(path) + 1. Een plek komt in de lijst als ze in de richting van de pijl op de laatste plek van path ligt, en als ze past bij de getallen die al in de puzzel staan:

  • Staat het volgende getal al in de puzzel, dan komt alleen de plek in aanmerking waar het staat.

  • Anders komen alleen lege vakjes in aanmerking, die nog niet in path staan.

Aanroep

Resultaat

next_cells(PUZZLE_1, [[0, 0]])

[[1, 1]]

next_cells(PUZZLE_2, [[0, 0]])

[[1, 0], [2, 0], [4, 0]]

In de tweede puzzel valt [3, 0] af: daar staat 17 en niet 2. En staat de 3 op [4, 1], dan wijst de pijl daar naar [4, 0], waar de 2 al staat: dan is de lijst leeg.

Hint

Maak eerst een lijst met alle getallen die al in de puzzel staan, met een geneste list comprehension over de rijen en de vakjes. Filter daarna de plekken uit targets met een list comprehension. Het getal op plek [row, col] is board[row][col][0].

# jouw oplossing
assert next_cells(PUZZLE_1, [[0, 0]]) == [[1, 1]]
assert next_cells(PUZZLE_1, [[0, 0], [1, 1]]) == [[0, 1]]
assert next_cells(PUZZLE_2, [[0, 0]]) == [[1, 0], [2, 0], [4, 0]]
assert next_cells(PUZZLE_2, [[0, 0], [4, 0], [4, 1]]) == []

Stap 3: extend_path(board, path)

Maakt het pad path af tot een pad langs alle n² vakjes, en geeft dat pad terug. Kan het pad niet worden afgemaakt, dan geeft de functie [] terug.

Aanroep

Resultaat

extend_path(PUZZLE_1, [[0, 0]])

[[0, 0], [1, 1], [0, 1], [1, 0], [2, 0], [2, 1], [1, 2], [0, 2], [2, 2]]

extend_path(PUZZLE_2, [[0, 0], [2, 0]])

[]

In het tweede voorbeeld staat de 2 op een plek waarmee de puzzel niet af te maken is.

Hint

Het basisgeval is een pad dat al alle vakjes heeft: len(path) is len(board) in het kwadraat. Loop anders met een lus de plekken uit next_cells langs. Maak voor elke plek het pad path + [cell] af met een recursieve aanroep. Geeft die iets anders terug dan [], dan heb je de oplossing en geef je hem meteen terug. Na de lus weet je dat geen enkele keuze werkt.

# jouw oplossing
path = [[0, 0], [1, 1], [0, 1], [1, 0], [2, 0], [2, 1], [1, 2], [0, 2], [2, 2]]
assert extend_path(PUZZLE_1, [[0, 0]]) == path
assert extend_path(PUZZLE_2, [[0, 0], [2, 0]]) == []
assert len(extend_path(PUZZLE_2, [[0, 0]])) == 25

Stap 4: fill(board, path)

path is een pad langs alle vakjes. Geeft een nieuwe puzzel terug, met op elke plek het getal uit het pad en dezelfde pijl als in board. board zelf verandert niet.

Hiervoor gebruik je de methode index van een lijst. L.index(x) geeft de index waarop x voor het eerst in L staat: ["a", "b", "c"].index("c") is 2, en [[0, 0], [1, 1]].index([1, 1]) is 1. Staat x niet in L, dan geeft Python een foutmelding. In een pad langs alle vakjes staat elke plek precies één keer, dus dat gebeurt hier niet.

Aanroep

Resultaat

fill(PUZZLE_1, [[0, 0], [1, 1], [0, 1], [1, 0], [2, 0], [2, 1], [1, 2], [0, 2], [2, 2]])

SOLUTION_1

Hint

Het getal op plek [row, col] is één meer dan de index van die plek in path: path.index([row, col]) + 1. Bouw de nieuwe puzzel met een geneste list comprehension, over range(len(board)) voor de rijen en voor de kolommen.

# jouw oplossing
path = [[0, 0], [1, 1], [0, 1], [1, 0], [2, 0], [2, 1], [1, 2], [0, 2], [2, 2]]
assert fill(PUZZLE_1, path) == SOLUTION_1
assert fill([[[1, ""]]], [[0, 0]]) == [[[1, ""]]]

Stap 5: arrow_path(board)

Geeft de opgeloste puzzel terug.

Aanroep

Resultaat

arrow_path(PUZZLE_1)

SOLUTION_1

arrow_path(PUZZLE_2)

SOLUTION_2

Hint

Elk pad begint linksboven, bij de 1: [[0, 0]].

# jouw oplossing
assert arrow_path(PUZZLE_1) == SOLUTION_1
assert arrow_path(PUZZLE_2) == SOLUTION_2

Tot slot

Beide puzzels hebben precies één oplossing, en arrow_path vindt die. Bij elk getal probeerde extend_path de plekken uit next_cells een voor een. Liep een keuze vast, dan kwam [] terug en was de volgende plek aan de beurt. Dat is de recursieve aanroep in een lus over de keuzes, zoals in het practicum van vorige week. Nieuw is dat een keuze kan mislukken, en dat de functie dan verder zoekt.

(Informatica Olympiade 2021)

Periodiek T-shirt

Er bestaan T-shirts met woorden die zijn samengesteld uit de symbolen van elementen uit het periodiek systeem. In deze opgave zoek je uit welke woorden je zo kunt maken, en op hoeveel manieren.

Een T-shirt met een woord dat is opgebouwd uit symbolen van het periodiek systeem

Het gegeven

Een element heeft een symbool van één of twee letters: "K" is kalium, "Kr" is krypton. Een woord maak je door symbolen achter elkaar te zetten. Hoofdletters tellen daarbij niet mee: het woord kranten is K + Ra + N + Te + N.

Soms moet je kiezen. kranten begint met de K van kalium of met de Kr van krypton. Kies je Kr, dan loop je vast: er is geen element A en geen element An. Met K lukt het wel. Of je één letter of twee moet nemen, weet je vooraf niet. Je probeert dus beide.

Het periodiek systeem van de elementen, met in elk vak het symbool

De cel hieronder zet de symbolen van de eerste 118 elementen klaar in de lijst ELEMENTS. De methode .split() knipt de lange string op bij de spaties. Voer de cel eerst uit.

ELEMENTS = (
    "H He Li Be B C N O F Ne Na Mg Al Si P S Cl Ar K Ca Sc Ti V Cr Mn Fe Co Ni "
    "Cu Zn Ga Ge As Se Br Kr Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I "
    "Xe Cs Ba La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu Hf Ta W Re Os Ir Pt "
    "Au Hg Tl Pb Bi Po At Rn Fr Ra Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr "
    "Rf Db Sg Bh Hs Mt Ds Rg Cn Nh Fl Mc Lv Ts Og"
).split()

Wat je gaat maken

Stap

Functie

Doet

Hoe je combineert

6

is_element

is een stukje tekst het symbool van een element?

geen recursie

7

can_spell

kun je een woord maken met symbolen?

één letter or twee letters

8

count_spellings

op hoeveel manieren kun je een woord maken?

één letter + twee letters

Stap 6: is_element(s)

s is een string van kleine letters. Geeft True als s het symbool van een element is, als je hoofdletters niet meetelt, en anders False.

Aanroep

Resultaat

is_element("k")

True

is_element("kr")

True

is_element("an")

False

Hint

De methode .lower() geeft een string in kleine letters: "Kr".lower() is "kr". Maak met een list comprehension de lijst van alle symbolen in kleine letters, en vraag met in of s daarin staat.

# jouw oplossing
assert is_element("k") == True
assert is_element("kr") == True
assert is_element("a") == False
assert is_element("an") == False

Stap 7: can_spell(word)

word is een string van kleine letters. Geeft True als je word kunt maken door symbolen van elementen achter elkaar te zetten, en anders False. Een lege string kun je altijd maken: met nul symbolen.

Aanroep

Resultaat

can_spell("kranten")

True

can_spell("uitdaging")

False

can_spell("")

True

Hint

Er zijn twee keuzes, en elke keuze is een recursieve aanroep:

  • de eerste letter is een symbool: is_element(word[:1]), en de rest, word[1:], kun je maken;

  • de eerste twee letters zijn een symbool: is_element(word[:2]), en de rest, word[2:], kun je maken.

Lukt een van de twee, dan kun je het woord maken. Een woord van één letter heeft geen twee eerste letters; let op dat word[:2] dan het hele woord is.

# jouw oplossing
assert can_spell("kranten") == True
assert can_spell("spuwers") == True
assert can_spell("caesar") == True
assert can_spell("uitdaging") == False
assert can_spell("kaas") == False
assert can_spell("") == True

Stap 8: count_spellings(word)

Geeft terug op hoeveel manieren je word kunt maken met symbolen van elementen. spuwers kan op twee manieren: S + P + U + W + Er + S, en S + Pu

  • W + Er + S.

Aanroep

Resultaat

count_spellings("spuwers")

2

count_spellings("kranten")

1

count_spellings("uitdaging")

0

count_spellings("")

1

Hint

De keuzes zijn dezelfde als in stap 7. Tel nu het aantal manieren van beide keuzes op. Een keuze waarvan het begin geen symbool is, levert 0 manieren op. Let weer op een woord van één letter: daar is maar één keuze, anders tel je "k" twee keer.

# jouw oplossing
assert count_spellings("kranten") == 1
assert count_spellings("spuwers") == 2
assert count_spellings("basis") == 3
assert count_spellings("sinas") == 5
assert count_spellings("uitdaging") == 0
assert count_spellings("") == 1

Tot slot

caesar kan op één manier: Ca + Es + Ar. sinas kan zelfs op vijf manieren. Bij elke letter probeerde je twee dingen: een symbool van één letter, of een van twee. Net als bij use it or lose it in het college zijn dat twee recursieve aanroepen, en de manier van combineren hangt af van de vraag: or als je wilt weten of het kan, + als je telt.

(Informatica Olympiade 2020-2021)

Caesar op orde

In deze opgave ga je een aantal functies schrijven met behulp van functioneel programmeren, dat wil zeggen, conditionele statements, recursie en/of list comprehensions. Je herhaalt er de stof van week 3 en 4: recursie, use it or lose it en key=.

Zorg bij elke opdracht dat je de naam precies zo schrijft zoals dit wordt opgegeven, inclusief hoofd- en kleine letters. Voeg verder aan elke functie een docstring toe waar je kort uitlegt wat de argumenten van de functie zijn en wat de functie doet.

Zet de voorbeelden om in assertions, zodat je je werk kunt controleren. Zegt een opdracht iets anders over een voorbeeld, dan volg je de opdracht. Bij lcs en make_change zijn meer antwoorden goed; controleer daar of je antwoord aan de eisen voldoet.

Opdracht 1: encipher(s, n)

Schrijf een functie encipher(s, n) die als argumenten een string s en een (niet negatieve) integer n met een waarde tussen 0 en 25 verwacht. De functie encipher moet een nieuwe string teruggeven waarin de letters in s met n letters voorwaarts zijn “geroteerd” in het alfabet, en terug naar het begin van het alfabet als dat nodig is.

Bij dit probleem mag je ervan uitgaan dat hoofdletters “geroteerd” worden naar hoofdletters, kleine letters naar kleine letters en dat alle andere tekens niet veranderd worden. Als we bijvoorbeeld de letter 'y' 3 posities willen verschuiven, krijgen we de 'b' en als we de letter 'Y' 3 posities willen verschuiven krijgen we 'B'.

Karakters in het alfabet

In Python kan je de test if "a" <= c <= "z": gebruiken om te controleren of een karakter c tussen de 'a' en 'z' in het alfabet zit.

Je mag encipher schrijven hoe je wilt, mits je functioneel programmeren gebruikt. Je mag dus elke combinatie van conditionele statements, recursie en list comprehensions gebruiken.

We raden je aan een hulpfunctie te schrijven die een enkel karakter n plaatsen roteert, terug naar het begin als dat nodig is. Deze hulpfunctie zou je kunnen gebruiken om de karakters in de string één voor één te versleutelen. Je moet zelf bepalen hoe je dat doet!

Een hulpfunctie rot(c, n)

Schrijf een functie rot(c, n) die een enkel karakter c n plaatsen voorwaarts roteert in het alfabet.

Bedenk dat je soms terug naar het begin van het alfabet moet en dat tekens die geen letter zijn niet veranderen. Controleer vervolgens met assert statements of de functie rot(c, n) werkt:

assert rot("a", 2) == "c"
assert rot("y", 2) == "a"
assert rot("A", 3) == "D"
assert rot("Y", 3) == "B"
assert rot(" ", 4) == " "

Bedenk verder dat je de ingebouwde functies ord en chr die een string met één karakter omzetten naar een integer en omgekeerd kan gebruiken bij het roteren:

  • ord('a') geeft bijvoorbeeld 97 terug

  • … en chr(97) geeft 'a' terug.

Als je de tip volgt en een hulpfunctie rot(c, n) schrijft, dan heb je twee mogelijkheden:

  • je kan met behulp van rot(c, n) een string letter voor letter recursief doorlopen, met de lege string als basisgeval.

  • je kan ook list comprehensions gebruiken om rot(c, n) meerdere keren toe te passen.

    [rot(c, n) for c in ...]
    

    Als je een list comprehension gebruikt, kan je onderstaande functie list_to_str gebruiken om er weer een string van te maken!

    def list_to_str(L):
        """Geeft de tekens uit de lijst L aan elkaar geplakt als één string."""
        if len(L) == 0:
            return ""
        return L[0] + list_to_str(L[1:])
    
    
    assert list_to_str(["h", "a", "n", "z", "e"]) == "hanze"
    

    Zet list_to_str(L) in je notebook, dan kun je hem gebruiken in encipher.

Hoe je encipher ook schrijft, zorg dat je de functie test! Hier is een begin:

assert encipher("xyza", 1) == "yzab"
assert encipher("Z A", 1) == "A B"

Hier zijn wat meer voorbeelden, we raden je aan deze om te zetten in assert statements en aan jouw tests toe te voegen:

In [1]: encipher('xyza', 1)
Out[1]: 'yzab'

In [2]: encipher('Z A', 1)
Out[2]: 'A B'

In [3]: encipher('*ab?', 1)
Out[3]: '*bc?'

In [4]: encipher('Dit is een string!', 1)
Out[4]: 'Eju jt ffo tusjoh!'

In [5]: encipher('Caesarcijfer? Ik heb liever Caesarsalade.', 25)
Out[5]: 'Bzdrzqbhiedq? Hj gda khdudq Bzdrzqrzkzcd.'

Tot slot, onthoud dat:

  • Hoofdletters altijd hoofdletters blijven.

  • Kleine letters altijd kleine letters blijven.

  • Andere tekens helemaal niet veranderen!

# jouw oplossing

Opdracht 2: decipher(s)

Omgekeerd krijgt decipher(s) een string s met een (Nederlandstalige) tekst die al versleuteld is. De functie decipher moet, voor zover mogelijk, de originele tekst teruggeven, dit zal een rotatie (mogelijk ook 0) zijn van het argument s.

Let erop dat sommige strings meer dan één “ontsleuteling” kunnen hebben. Bovendien is het moeilijk of soms zelfs onmogelijk om hele korte strings goed te ontsleutelen. De functie decipher hoeft dus ook niet perfect te zijn, maar moet wel vrijwel altijd werken met langere stukken Nederlandse tekst, bijvoorbeeld zinnen van 8 of meer woorden. Het is dus geen probleem als een enkel woord of een korte zin niet goed worden ontsleuteld.

Alle rotaties, en dan de beste

Het is handig om te beginnen met het genereren van alle mogelijke ontsleutelingen, bijvoorbeeld via:

L = [... for n in range(26)]

Geef daarna elke rotatie in L een score die zegt hoe Nederlands ze eruitziet, en kies de rotatie met de hoogste score. Dat kan in één regel met max en key=, zoals in het tweede college van deze week:

max(L, key=...)

Achter key= zet je een functie die bij een string zijn score teruggeeft. Dat lijkt op best_word uit de basisopgave van week 3, die het woord met de hoogste Scrabblescore zocht.

Een mogelijke aanpak is het gebruik van letterfrequenties, hieronder is een functie gegeven met de frequenties per letter en deze kan je gebruiken in de opgave. Je zou ook Scrabblescores kunnen proberen (want deze zijn ook gebaseerd op letterfrequenties). Letters die vaak voorkomen zijn in Scrabble weinig waard, dus dan zoek je de rotatie met de laagste score. Je mag zelf extra heuristieken (“vuistregels”) bedenken en verder mag je ook kleine hulpfuncties toevoegen om te helpen met het schrijven van decipher.

Hoe je het ook aanpakt, onthoud dat je de strategie die je gebruikt bij het schrijven van de functie decipher moet documenteren in een kort stukje commentaar boven de functie decipher.

Enkele voorbeelden van decipher:

In [1]: decipher('Bzdrzqbhiedq? Hj gda khdudq Bzdrzqrzkzcd.')
Out[1]: 'Caesarcijfer? Ik heb liever Caesarsalade.'

In [2]: decipher('Aadxas ue exqotfe pq haadflqffuzs hmz baxufuqw yqf mzpqdq yuppqxqz.')
Out[2]: 'Oorlog is slechts de voortzetting van politiek met andere middelen.'

In [3]: decipher('Lvkeg lvyon')
Out[3]: 'Tdsmo tdgwv'  # Dit is fout! Maar is hier geen probleem...

Ook hier raden we jou aan om de eerste twee voorbeelden om te zetten naar assert statements. Het derde voorbeeld niet: welke foute uitkomst je bij zo’n korte zin krijgt, hangt af van hoe je de score berekent. Met een andere score kan daar iets anders uitkomen, en ook dat is geen probleem.

Merk verder op dat het laatste voorbeeld laat zien dat onze ontsleutelaar het niet goed doet bij sommige korte zinnen. Dit is geen probleem! Naarmate de zinnen langer worden, moet de ontsleutelaar er steeds meer goed hebben, maar hij hoeft enkele woorden of korte zinnen niet goed te hebben, want voor korte strings zijn er zeer waarschijnlijk rotaties met meer “Nederlandsige” letters dan het origineel!

Hier is een functie om de kans per letter te bepalen:

# tabel met kansen per letter
def letter_prob(c):
    """Geeft de kans op letter c in Nederlandse tekst, of 1.0 als c geen letter is.

    Hoofdletters tellen als kleine letters. Bewerkt naar
    https://www.sttmedia.com/characterfrequency-nederlands
    """
    if c == "e" or c == "E":
        return 0.1909
    if c == "n" or c == "N":
        return 0.0991
    if c == "a" or c == "A":
        return 0.0769
    if c == "t" or c == "T":
        return 0.0642
    if c == "i" or c == "I":
        return 0.0630
    if c == "o" or c == "O":
        return 0.0581
    if c == "r" or c == "R":
        return 0.0562
    if c == "d" or c == "D":
        return 0.0541
    if c == "s" or c == "S":
        return 0.0384
    if c == "l" or c == "L":
        return 0.0380
    if c == "h" or c == "H":
        return 0.0312
    if c == "g" or c == "G":
        return 0.0312
    if c == "k" or c == "K":
        return 0.0279
    if c == "m" or c == "M":
        return 0.0256
    if c == "v" or c == "V":
        return 0.0224
    if c == "u" or c == "U":
        return 0.0212
    if c == "j" or c == "J":
        return 0.0182
    if c == "w" or c == "W":
        return 0.0172
    if c == "z" or c == "Z":
        return 0.0160
    if c == "p" or c == "P":
        return 0.0149
    if c == "b" or c == "B":
        return 0.0136
    if c == "c" or c == "C":
        return 0.0130
    if c == "f" or c == "F":
        return 0.0073
    if c == "y" or c == "Y":
        return 0.0006
    if c == "x" or c == "X":
        return 0.0005
    if c == "q" or c == "Q":
        return 0.0001
    return 1.0

Zet letter_prob(c) in je notebook, dan kun je hem gebruiken in decipher.

Beslissingen

Voor decipher zal jouw programma alle 26 mogelijke rotaties van de string s die als argument is meegegeven moeten “bekijken” en dan beslissen welke de “beste” is…

# jouw oplossing

Opdracht 3: blsort(L)

Schrijf een functie blsort(L) die als argument een lijst L accepteert en een lijst teruggeeft met dezelfde elementen als L, maar in oplopende volgorde. (Let op: het tweede teken van de functienaam is een “el” voor “lijst”, niet een 1 (één) of een “i”!)

blsort hoeft alleen maar lijsten met binaire cijfers te sorteren, dat wil zeggen, deze functie mag en moet ervan uitgaan dat L altijd een lijst met alleen 0’en en 1’en is.

Je mag de ingebouwde sorteerfuncties sorted(L) en L.sort() niet gebruiken om dit probleem op te lossen! Je mag ook je zelfgeschreven sorteerfunctie (deze wordt later gevraagd) niet gebruiken.

Verder mag je elke andere functionele techniek gebruiken om blsort te implementeren. In het bijzonder is het handig te bedenken hoe je gebruik kan maken van de beperking dat het argument L een binaire lijst is, dit is een belangrijke beperking!

Een hulpfunctie die sommigen handig vinden is count(e, L). Die telt hoe vaak e voorkomt in L:

def count(e, L):
    """Geeft hoe vaak e voorkomt in L."""
    return sum([1 for x in L if x == e])


assert count(1, [1, 0, 1]) == 2

Zet count(e, L) in je notebook, dan kun je hem gebruiken in blsort.

Hier zijn een paar voorbeelden:

In [1]: blsort([1, 0, 1])
Out[1]: [0, 1, 1]

In [2]: L = [1, 0, 1, 0, 1, 0, 1]

In [3]: blsort(L)
Out[3]: [0, 0, 0, 1, 1, 1, 1]

Binaire eenvoud

Dit probleem is veel makkelijker dan sorteren in het algemeen! Maak gebruik van het feit dat de lijst L alleen maar 0 of 1 kan bevatten.

# jouw oplossing

Opdracht 4: gensort(L)

Gebruik recursie om een algemene sorteerfunctie gensort(L) te schrijven die een lijst L accepteert en een lijst teruggeeft met dezelfde elementen als L, maar in oplopende volgorde. Je kan hiervoor de ingebouwde Python-functie max (of min, als je deze liever hebt) gebruiken, en de functie rem_one hieronder. Recursie, dat wil zeggen het sorteren van de rest van de lijst, is hier handig.

rem_one(e, L) haalt e één keer uit de lijst L: de eerste keer dat hij voorkomt.

def rem_one(e, L):
    """Geeft L zonder de eerste e; staat e niet in L, dan L zelf."""
    if L == []:  # basisgeval
        return []
    elif L[0] == e:  # basisgeval: de eerste e gevonden
        return L[1:]
    else:  # recursief geval
        return [L[0]] + rem_one(e, L[1:])


assert rem_one(8, [7, 8, 9, 8]) == [7, 9, 8]
assert rem_one(5, [7, 8]) == [7, 8]

Zet rem_one(e, L) in je notebook, dan kun je hem gebruiken in gensort.

Je ziet hier een paar voorbeelden:

In [1]: gensort([42, 1, 3.14])
Out[1]: [1, 3.14, 42]

In [2]: L = [7, 9, 4, 3, 0, 5, 2, 6, 1, 8]

In [3]: gensort(L)
Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Bij deze opgave mag je de in Python ingebouwde sorteerfuncties zoals sorted(L) en L.sort() niet gebruiken. Je moet zelf een aanpak bedenken en implementeren! Let verder op dat gensort(L) moet werken voor lijsten L, het hoeft niet te werken voor strings.

# jouw oplossing

Opdracht 5: lingo(s, t)

Schrijf een functie lingo(s, t) die twee strings s en t accepteert en een integer teruggeeft, de zogenaamde Lingo-telling, of -score, waar s wordt vergeleken met t.

De Lingo-score is het aantal tekens in s dat ook voorkomt in t. Herhaalde letters tellen vaker, mits ze vaker voorkomen in beide strings, hoe dit werkt wordt duidelijker in de onderstaande voorbeelden. Je hoeft er bij deze functie geen rekening mee te houden of de letters op dezelfde plek staan. Merk op dat alhoewel Lingo traditioneel gespeeld wordt met woorden van 5 (of 6) letters, de lengte van de woorden in deze functie niet beperkt is!

Er zijn meerdere manieren om dit probleem op te lossen, een aantal daarvan gebruikt kleinere hulpfuncties. Het staat je vrij om zulke hulpfuncties toe te voegen als je dat handig vindt. Een versie van rem_one uit gensort die een teken uit een string haalt, kan hier bijvoorbeeld van pas komen.

Merk op dat als s of t een lege string is, de Lingo-score 0 moet zijn!

Wel of niet

Op enig moment zul je moeten testen of een karakter zich wel of niet in een string bevindt. De regel if s[0] in t: zou een handige test kunnen zijn!

Een paar voorbeelden:

In [1]: lingo('diner', 'proza')  # alleen de 'r'
Out[1]: 1

In [2]: lingo('beeft', 'euvel')  # twee 'e's zijn gedeeld
Out[2]: 2

In [3]: lingo('gattaca', 'aggtccaggcgc') # 2 'a's, 1 't', 1 'c', 1 'g'
Out[3]: 5

In [4]: lingo('gattaca', '') # geef 0 terug bij een lege string
Out[4]: 0
# jouw oplossing

Opdracht 6: lcs(s, t)

Deze algoritmische uitdaging heeft alles met DNA-matching te maken!

Schrijf een functie lcs(s, t) die twee strings s en t accepteert. De functie geeft een string terug die de langste gemeenschappelijke deelrij (LCS, longest common subsequence) is die s en t met elkaar delen.

De LCS is een string waarvan de letters een deelrij van s en een deelrij van t zijn (ze moeten in dezelfde volgorde, maar niet noodzakelijkerwijs na elkaar in de twee argumenten voorkomen).

Let op dat als s of t een lege string is, het resultaat ook een lege string is!

Een paar voorbeelden:

In [1]: lcs('mens', 'chimpansee')
Out[1]: 'mns'

In [2]: lcs('gattaca', 'tacgaacta')
Out[2]: 'gaaca'

In [3]: lcs('wow', 'wauw')
Out[3]: 'ww'

In [4]: lcs('', 'wauw')     # eerste argument is de lege string
Out[4]: ''

In [5]: lcs('abcdefgh', 'efghabcd')
Out[5]: 'abcd'

Als meerdere resultaten van gelijke lengte mogelijk zijn dan maakt het niet uit welke je teruggeeft, in het laatste voorbeeld hierboven zou 'efgh' ook een goed antwoord zijn geweest.

Use it or lose it or lose it

Gebruik de volgende strategie:

  • Als de eerste karakters van s en t gelijk zijn, dus s[0] en t[0], gebruik ze dan!

  • Als s[0] en t[0] niet gelijk zijn, pas dan twee keer recursie toe: je zou dit use it or lose it or lose it kunnen noemen!

  • Gebruik voor de eerste “lose it” recursie om de eerste letter van het ene argument weg te gooien:

    result1 = lcs(s[1:], t)
    
  • Gebruik voor de tweede “lose it” recursie om de eerste letter van het andere argument weg te gooien:

    result2 = lcs(..., ...)
    

    Hier moet je nog wat details invullen…

  • Geef ten slotte de betere van de twee resultaten terug. Bedenk zelf wat “beter” in dit geval betekent. Met max(result1, result2, key=...) uit het tweede college van deze week kies je de betere in één regel.

# jouw oplossing

Opdracht 7: make_change(amount, coins)

De laatste functie bouwt voort op exact_change uit het eerste college van deze week, maar is moeilijker omdat:

  • ze de gebruikte munten moet teruggeven;

  • ze ook False kan teruggeven, en je een aantal gevallen moet afhandelen na de recursie.

Schrijf de functie make_change(amount, coins), met dezelfde argumenten als exact_change. De functie moet bepalen welke munten uit coins gebruikt kunnen worden om amount te betalen.

In plaats van True of False terug te geven zoals bij exact_change moet de functie make_change een lijst teruggeven van munten uit coins die samen optellen tot amount. Als geen lijst kan worden samengesteld zal de functie False teruggeven. Als meer dan één lijst met munten uit coins mogelijk is mag de functie elk geldig antwoord teruggeven.

De volgorde van de waarden in het antwoord maakt niet uit, maar het is logisch om dezelfde volgorde aan te houden als van de oorspronkelijke lijst.

Je hoeft exact_change niet als hulpfunctie te gebruiken, maar het mag wel!

De voorbeelden hieronder tonen hoe make_change moet werken. Let op dat je 0 altijd precies kan betalen, en dat je een negatief bedrag nooit precies kan betalen. Waar het resultaat een niet-lege lijst is, staat er sorted omheen, zodat het resultaat een duidelijke volgorde heeft:

In [1]: sorted(make_change(42, [25, 1, 25, 10, 5, 1]))
Out[1]: [1, 1, 5, 10, 25]

In [2]: make_change(42, [25, 1, 25, 10, 5])
Out[2]: False

In [3]: make_change(42, [23, 1, 23, 100])
Out[3]: False

In [4]: sorted(make_change(42, [23, 17, 2, 100]))
Out[4]: [2, 17, 23]

In [5]: sorted(make_change(42, [25, 16, 2, 15]))
Out[5]: [2, 15, 25]

In [6]: make_change(0, [4, 5, 6])
Out[6]: []

In [7]: make_change(-47, [4, 5, 6])
Out[7]: False

In [8]: make_change(0, [])
Out[8]: []

In [9]: make_change(42, [])
Out[9]: False
# jouw oplossing