<jupyter_start><jupyter_text>List comprehension ![Fixing problems](images/6/fixing_problems.jpg) We gaan door met recursie en functies ...<jupyter_code>"fun" in "functional"<jupyter_output><empty_output><jupyter_text>Functies en vooral recursie zijn lastige onderwerpen, toch hopen we dat je "lol" blijft houden! Terzijde, let op de `in`, deze syntax blijft handig voor het controleren of een element zich in een collectie bevindt! ![Escher handen](images/6/escher_handen.png) [Tekenende handen](https://www.rijksmuseum.nl/nl/collectie/RP-P-1949-188) van [M.C. Escher](https://nl.wikipedia.org/wiki/Maurits_Cornelis_Escher). Tekende de rechterhand eerst de linkerhand? Of tekende de linkerhand de rechterhand die vervolgens de linkerhand tekende?Verwarring alom met recursie... het is een lastig onderwerp!  Functioneel programmeren- representatie door middel van lijststructuren (data)- gebruik maken van zelfgelijkendheid (recursie)- gebruik maken van bouwstenen (functies)Met deze combinatie kunnen problemen worden verkend of opgelost. Je hebt nu kennisgemaakt met een drietal bouwstenen en het gecombineerd gebruik valt onder de noemer *functioneel programmeren*. Functioneel programmeren is een *programeerparadigma*, een manier van werken of aanpak voor het oplossen of verkennen van problemen. Je gaat later nog met twee andere programeerparadigmas kennis maken: imperatief- en object georiënteerd programmeren.  Rekenkundig compleetWe zijn rekenkundig compleet. Wat nu? - Python efficiënt laten werken- nieuwe bouwstenen! Met rekenkundig compleet bedoelen we dat met de combinatie van data, functies en recursie de computer data kunnen laten representeren en daar handelingen op uit kunnen laten voeren voor alle *oplosbare* problemen.  EfficiëntieFunctioneel programmeren is conceptueel beknopt, maar is het efficiënt voor de computer?> functioneel **versus** sequentieel of procedureel  Wat we met conceptueel beknopt bedoelen is dat weinig nodig is voor het definiëren van een oplossing. Een recursieve functie is hier het beste voorbeeld, de oplossing van het probleem ligt in een functie besloten. We kunnen op deze manier oplossingen heel precies en efficiënt beschrijven, maar voor een computer is het *niet* de meest efficiënt manier om uit te voeren!We komen hier op een ander moment nog op terug, maar denk aan hoe functies worden gestapeld op de stack. Het steeds plaatsen op en verwijderen van de stack zorgt voor een overhead in het programma, de computer moet extra handelingen verichten die de uitvoering langzamer maken.Een sequentiële- of procedurele- in plaats van functionele benadering is efficiënter voor de computer en dit gaan we nu verkennen met iets dat je al kent: lists! ![Meaning of comprehension](images/6/comprehension_meaning.png) Wat is *comprehension* in [list comprehension](https://english.stackexchange.com/questions/406684/what-is-the-meaning-of-comprehension-in-the-term-list-comprehension) is een goede vraag, een andere vraag is wat de beste vertaling in het Nederlands zou zijn. Wij weten het niet en daarom zullen wij blijven spreken over *list comprehension*... Wát het is en doet ga je nu zien!  Data en functies*De compositie van data en functies*Data```python[3, 4, 5, 6, 7, 8, 9]```en functies:```pythondef sum(L):    ...``````pythondef range(low, hi):    ...```  `sum(L)` ```pythondef sum(L):    """ input: L, a list of s        output: L's sum    """    if len(L) == 0:        return 0.0                 base case    else:        return L[0] + sum(L[1:])   recursive case``` Hier zie je een recursieve implementatie van de ingebouwde Python functie `sum`. Om ons recursieve geheugen op te frissen, laten we nu proberen een recursieve implementatie te maken van de functie `range`.  QuizEen recursive implementatie van de ingebouwde Python functie `range````pythonrange(3, 7) == [3, 4, 5, 6]``````pythonrange(3, 7, 2) == [3, 5]```  `range(low, hi)`Wat is de recursive case?```pythondef range(low, hi):    """ input: low and hi, integers    output: a list from low upto hi    """    if low >= hi:   base case        return []    else:           recursive case?        return ...```Bedenk dat je lists kan "optellen"!```python[3] + [4, 5, 6] == [3, 4, 5, 6]```  Oplossing ```pythondef range(low, hi):    """ input: low and hi, integers    output: a list from low upto hi    """    if low >= hi:                           base case        return []    else:                                   recursive case        return [low] + range(low + 1, hi)```  UitdagingDe Python functie `range` accepteert een derde parameter (`step`) dat het aantal stappen bepaalt dat per keer moet worden genomen (in deze oplossing is dit 1). Hoe zou je de recursieve functie `range` uitbreiden met een derde parameter `step`?  Oplossing```pythondef range(low, hi, step=1):    """ input: low and hi, integers    output: a list from low upto hi    """    if low >= hi:                                    base case        return []    else:                                            recursive case        return [low] + range(low + step, hi, step)``` Je ziet hier een andere syntax voor de parameter `step` (`step=1`)! Deze syntax voor `step` wordt een *named* of *keyword* parameter genoemd en met deze syntax zeg je dat het argument optioneel is: als het niet als *extra* argument wordt meegegeven naast de verplichte *positionele* argumenten (`low` en `hi`) dan is de waarde standaard 1. Named parameters moeten altijd na de positionele parameters volgen (bijvoorbeeld, `def range(step=1, low, hi)` zal een `SyntaxError` geven).  ResultaatOnze recursieve functies `sum` en `range` in actie<jupyter_code>sum(list(range(1, 101)))<jupyter_output><empty_output><jupyter_text>Data en functies combineren*Verdubbel alle waarden in een list*Data```python[8, 9, 10]```en functies```pythondef dbl(x):    """Verdubbel een getal    """    return x * 2``` combineren```python[dbl(x) for x in [8, 9, 10]]```  VariatiesVariaties op het toepassen van een functie op de elementen in een list zijn mogelijk```pythonL1 = list(map(dbl, range(6)))L2 = [x * 2 for x in range(6)]``` Vaak zul je zien (of zelf hebben bedacht) dat meerdere oplossingen mogelijk zijn. In het geval `L2` kan je je afvragen of een functie nodig is, omdat het hier om gaat om een heel eenvoudige expressie (`x * 2`) die niet de moeite is deze als functie te schrijven. `L1` gebruikt de ingebouwde functie [`map`](https://docs.python.org/3/library/functions.htmlmap), die een functie toepast op elk element in een collectie.  Terzijde, over namen```pythonx = 10def dbl(x):    """Verdubbel een getal    """    x = x * 2    return xy = dbl(x)print(x)print(y)```Welke waarde wordt geprint voor `x` en welke voor `y`?  In de voorgaande voorbeelden zag je vaak `x` worden gebruikt als naam voor een waarde in zowel de list comprehension als parameter in de functie `dbl`. Staan ze elkaar niet in de weg, zul je je misschien afvragen? Dit is niet het geval en dit heeft te maken met de *scope* (het bereik, of de context) van een naam.De `x` die wordt gebruikt wordt is een heel andere dan de `x` in de functie. Zie een functie dan ook als een kleine, op zichzelf staande wereld die maar beperkt van de buitenwereld kan weten.`x` zal in dit geval 10 printen en `y` (het resultaat van de functie) 20.  List comprehensionList```python[0, 1, 2, 3, 4, 5]```List *comprehension*```python[2 * x for x in [0, 1, 2, 3, 4, 5]]                                         ```Resultaat```python[0, 2, 4, 6, 8, 10]```  IteratieStap één voor één over waarden en pas een expressie toe> [ **expressie** voor elke **waarde** in **collectie** ]```python[2 * x for x in [0, 1, 2, 3, 4, 5]]                                         ``` `x` neemt elke waarde in de list aan waar de expressie `2 * x` op wordt toegepast en op deze manier wordt iteratief wordt een nieuwe list opgebouwd. Hier wordt geen recursie meer toegepast maar wordt *sequentieel* de data doorlopen en *handelingen* op toegepast.  ConditioneelStap *voorwaardelijk* één voor één over waarden en pas een expressie toe> [ **expressie** voor elke **waarde** in **collectie** als  ]```python[10 * x for x in [0, 1, 2, 3, 4, 5] if x % 2 == 0]``` Een list comprehension kan worden uitgebreid met een conditie waar de expressie zal worden toegepast alleen als de test slaagt voor de waarde van `x`.  QuizWat is het resultaat van de volgende list comprehensions  Vraag 1```python[n ** 2 for n in range(0, 5)]```  Antwoord```python[0, 1, 4, 9, 16]```  Vraag 2```python[42 for z in [0, 1, 2]]```  Antwoord```python[42, 42, 42]```  Vraag 3```python[z for z in [0, 1, 2]]```  Antwoord```python[0, 1, 2]```  Vraag 4```python[s[1::2] for s in ["elk", "ook", "vlo"]]```  Antwoord```python["l", "o", "l"]```  Vraag 5```python[a * (a - 1) for a in range(8) if a % 2 == 1]```  Antwoord```python[0, 6, 20, 42]```  Syntax!```python[x * 2 for x in [0, 1, 2, 3, 4, 5]]```Het lijkt op een wirwar van karakters en andere willekeurige tekens...  Maar de handeling die we willen uitvoeren is eenvoudig, namelijk het vermenigvuldigen van elke waarde in een lijst.We weten nu ook dat het op deze wijze sequentieel toepassen van een expressie op elk element van een collectie (bijvoorbeeld een list) efficiënter is dan een recursive oplossing (althans voor de computer!). Maar je zal je misschien afvragen of deze syntax wel efficiënter is voor *jou* want het lijkt op het eerste gezicht moelijk te begrijpen.We kunnen je verzekeren dat met oefening je deze notatie op een gegeven moment zal ook gaan waarderen al is het alleen maar omdat je complexe bewerkingen in een enkele regel kan schrijven.  Miljoen keer simulerenIteratie in plaats recursie? Kan je je nog herinneren dat we recursie hebben toegepast om simulaties uit te voeren? Lees anders het onderwerp [simulaties](5a_simulatie) nog een keer door. Voor het herhaald uitvoeren van functies  gebruikten we toen recursie, bijvoorbeeld om te tellen hoe vaak een gelijk aantal ogen wordt gegooid met twee dobbelstenen of om te tellen hoeveel pogingen gemiddeld nodig zijn om een getal te raden.We hebben nu met list comprehension een techniek gezien om een expressie (bijvoorbeeld een functie) iteratief toe te passen op elk elementen in een collectie, zouden we deze techniek kunnen toepassen om de herhaling in simulaties zonder recursie toe te passen?<jupyter_code>import sys
from random import *

def guess_np(hidden):
    """Raad een getal
    
    hidden: het te raden getal
    """
    comp_guess = choice(range(100))  # 0 tot en met 99.

    if comp_guess == hidden:         # base case
        return 1
    else:                            # recursive case
        return 1 + guess_np(hidden)<jupyter_output><empty_output><jupyter_text>Dit is dezelfde functie die je eerder hebt gezien om een getal te raden, maar zonder de print statements waar de computer ons enthousiast liet weten (printen!) dat het het getal had geraden. De functie geeft het aantal keer dat nodig is om een om een getal te raden terug op basis van steeds een *random* keus van de computer.  Combineren> [ **expressie** voor elke **waarde** in **collectie** ]<jupyter_code>LC = [guess_np(42) for x in range(1000)]<jupyter_output><empty_output><jupyter_text>Eén enkele regel voor het uitvoeren van 1000 simulaties, dat is best indrukwekkend! Recursie hebben we op deze manier vervangen door iteratie (1000 frames minder op de stack) en Python vindt dit bijzonder prettig en wij ook, want we hebben snelheidswinst kunnen boeken.Maar we liegen, want we hebben `range` net als oefening recursief geïmplementeerd! De ingebouwde versie `range` gebruikt geen recursie en daar zit de winst.Bedenk dat je niet verplicht bent de waarde `x` in een expressie te gebruiken. In dit geval is `x` een nutteloze variabele die je alleen maar nodig hebt omdat de *syntax* jou dit verplicht. Vaak zal je zien dat in dit soort gevallen `_` wordt gebruikt om aan te geven dat het een "wegwerp" variabele is, bijvoorbeeld in ons geval```python[guess_np(42) for _ in range(1000)]```<jupyter_code>LC[0:10]<jupyter_output><empty_output><jupyter_text>List slicing ken je nu ook en dit komt in dit geval goed van pas om eerste de 10 elementen (het resultaat van de eerste 10 simulaties) te inspecteren.<jupyter_code>print("Gemiddeld aantal keer raden", sum(LC) / len(LC))<jupyter_output>Gemiddeld aantal keer raden 103.836<jupyter_text>De functie `sum` hebben we eerder geschreven in combinatie me de ingebouwde functie `len` kan het gemiddelde aantal keer raden worden berekend van deze simulatie!  Dubbele ogen<jupyter_code>from random import *

def count_doubles(N):
    """Tel aantal dubbele ogen bij N worpen
    """
    if N == 0:    # base case
        return 0  # 0 worpen, 0 dubbele ogen...

    d1 = choice([1,2,3,4,5,6])  # eerste dobbelsteen
    d2 = choice([1,2,3,4,5,6])  # tweede dobbelsteen

    if d1 != d2:
        return 0 + count_doubles(N - 1)  # niet gelijk
    else:
        return 1 + count_doubles(N - 1)  # gelijk! tel 1 op
count_doubles(600)
LC = [count_doubles(600) for x in range(1000)]
LC[0:10]
print("Gemiddeld dubbele ogen (/600):", sum(LC)/len(LC))<jupyter_output>Gemiddeld dubbele ogen (/600): 99.941<jupyter_text>Wisselen van deur<jupyter_code>from random import *

def MCMH(init, sors, N):
    """Speel Let's Make a Deal N keer
    """
    if N == 0:  # base case
        return 0

    prz_door = choice([1, 2, 3])  # de deur met de prijs!

    if init == prz_door:
        if sors == "stay":
            result = "Spam!"
        else:
            result = "pmfp."
    else:
        if sors == "switch":
            result = "Spam!"
        else:
            result = "pmfp."

    if result == "Spam!":
        return 1 + MCMH(init, sors, N - 1)
    else:
        return 0 + MCMH(init, sors, N - 1)

MCMH(1, 'switch', 300)
LC = [MCMH(1, 'switch', 300) for x in range(1000)]
LC[0:10]
print("Gemiddeld aantal spam (/300)", sum(LC)/len(LC))<jupyter_output>Gemiddeld aantal spam (/300) 199.909<jupyter_text>De winnaar?![Python](images/6/python_logo.png) ![Excel](images/6/excel_logo.png) Je hebt gezien dat we met Python efficiënt en met verassend weinig code simulaties kunnen laten uitvoeren. Deze flexibiliteit is onder andere waarom Python zo populair is in data science.  LCs gebruikenScrabble score<jupyter_code>def letter_score(s):
    """Scrabble letter score
    """
    if s in "adeinorst":
        return 1
    elif s in "ghl":
        return 2
    elif s in "bcmp":
        return 3
    elif s in "jkuvw":
        return 4
    elif s == "f":
        return 5
    elif s == "z":
        return 6
    elif s in "xy":
        return 8
    elif s == "q":
        return 10
    else:
        return 0<jupyter_output><empty_output><jupyter_text>Het tellen van het aantal elementen in een lijst.<jupyter_code>def fun1(L):
    LC = [1 for x in L]
    return sum(LC)<jupyter_output><empty_output><jupyter_text>Kijk even goed naar wat hier gebeurt. Als `L` een list is dan zal in de functie `LC` een lijst opleveren met allemaal 1'en. Deze 1'en worden opgeteld door onze goede bekende `sum` en als resultaat teruggegeven.<jupyter_code>fun1([7,8,9])<jupyter_output><empty_output><jupyter_text>Optelling van Scrabble letter scores<jupyter_code>def fun2(S):
    LC = [letter_score(c) for c in S]
    return sum(LC)<jupyter_output><empty_output><jupyter_text>Je roept de functie `letter_score` aan voor elk karakter in de string `S`, en `sum` telt de scores bij elkaar op!<jupyter_code>fun2("quiz")<jupyter_output><empty_output><jupyter_text>Goede score!  Oneliners```pythondef len(L):    LC = [1 for x in L]    return sum(LC)``` ```pythondef len(L):    return sum([1 for x in L])``` List comprehesion maakt het mogelijk om met een heel compacte syntax oplossingen te schrijven. Bedenk wel dat dit niet altijd de leesbaarheid ten goede komt en het is geen probleem om het in stukjes uit te schrijven, bijvoorbeeld door het gebruik van een variabele (`LC` in de eerste variant).  ConditiesList comprehension met condities> [ **expressie** voor elke **waarde** in **collectie** als ] ```pythondef vwl(s):    """Tel het aantal klinkers    """    LC = [1 for x in s ...]    return sum(LC)``` De functie `vwl` telt klinkers in een woord. Dit is een mooi voorbeeld waar je een conditie moet gebruiken want niet ieder karakter is een klinker!<jupyter_code>def vwl(s):
    """Tel het aantal klinkers
    """
    LC = [1 for x in s if x in "aeiou"]
    return sum(LC)
vwl("autobandventieldopje")<jupyter_output><empty_output><jupyter_text>Alweer een goede score! Misschien kan jij een woord bedenken met nóg meer klinkers? ```pythondef count(e, L):    """Tel het aantal e in L    """    LC = [1 for x in L ...]    return sum(LC)``` Een ander voorbeeld, tel het aantal elementen in de list `L` die gelijk is aan `e`.<jupyter_code>def count(e, L):
    """Tel het aantal e in L
    """
    LC = [1 for x in L if x == e]
    return sum(LC)
count(42, [3, 42, 5, 7, 42])<jupyter_output><empty_output><jupyter_text>Quiz  Even getallen tellen```pythondef nodds(L):    LC = [... for x in L ...]    return sum(LC)```- input: `L`, een list met getallen- output: het aantal **oneven** getallen in `L`Voorbeeld:```pythonnodds([3, 4, 5, 7, 42]) == 3```  Oplossing<jupyter_code>def nodds(L):
    LC = [1 for x in L if x % 2 == 1]
    return sum(LC)
nodds([3, 4, 5, 7, 42]) == 3<jupyter_output><empty_output><jupyter_text>Zelfde waarden in twee lijsten```pythondef lingo(Y, W):    LC = [... for x in ... if ...]    return sum(LC)```- input: `Y` en `W`, twee lijsten met "loterij" getallen (integers)- output: het aantal getallen in `Y` dat ook in `W` voorkomtVoorbeeld:```pythonlingo([5, 7, 42, 47], [3, 4, 5, 7, 44, 47]) == 3```Tip: de `in` expressie kan hier handig zijn (`x in W`)  Oplossing<jupyter_code>def lingo(Y, W):
    LC = [1 for x in Y if x in W]
    return sum(LC)
lingo([5, 7, 42, 47], [3, 4, 5, 7, 44, 47]) == 3<jupyter_output><empty_output><jupyter_text>Positieve delers```pythondef ndivs(N):    LC = [... for x in ... if ...]    return sum(LC)```- input: `N`, een integer >= 2- output: het aantal positieve delers van `N`Voorbeeld:```pythonndivs(12) == 6```(de positieve delers van 12 zijn `[1, 2, 3, 4, 6, 12]`)Tip: gebruik `range` om een reeks getallen van 1 *tot en met* `N` te genereren  Oplossing<jupyter_code>def ndivs(N):
    LC = [1 for x in range(1, N + 1) if N % x == 0]
    return sum(LC)
ndivs(12) == 6<jupyter_output><empty_output><jupyter_text>PriemgetallenEen uitdaging!```pythondef primes_up_to(P):    LC = [... for x in ... if ...]    return LC```- input: `P` een integer >= 2- output: de lijst van priemgetallen tot en met `P`Voorbeeld:```pythonprimes_up_to(12) == [2, 3, 5, 7, 11]``` Ter herinnering, een priemgetal is een heel getal dat precies door twee getallen kan worden gedeeld en waarmee je met de deling een heel getal overhoudt. Een priemgetal is altijd deelbaar door 1 en deelbaar door zichzelf.Bijvoorbeeld het getal 5 heeft precies 2 delers, namelijk 1 en 5. Het getal 4 is geen priemgetal, 4 heeft namelijk 3 delers (1, 2 en 4). Tip: je kan in de conditie de functie `ndivs` gebruiken als in `ndivs(x) == 2`.  Oplossing<jupyter_code>def primes_up_to(P):
    LC = [x for x in range(2, P + 1) if ndivs(x) == 2]
    return LC
primes_up_to(12) == [2, 3, 5, 7, 11]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10364174762325315, 'fraction_numerical': 0.022598576549431138, 'mean_word_length': 3.905708460754332, 'pattern_counts': {'":': 11, '<': 74, '<?xml version=': 0, '>': 84, 'https://': 4, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '216408', 'license': 'MIT', 'n_tokens_mistral': 7133, 'n_tokens_neox': 6706, 'n_words': 2573, 'path': 'topics/6a_list_comprehension.ipynb', 'repo_name': 'misja/programmeren', 'size': '19249'}