# ProgrammeursleerlingExtra.py
#
# Delen inhoud met uitvoerbare source code overgenomen uit het boek
#   De Programmeursleerling: Leren coderen met Python 3
#   door Pieter Spronck
#   Version 1.1.1
#   17-06-2024
# Downloads van het boek en de code op
#   https://www.spronck.net/pythonbook/dutchindex.xhtml
# Copyright © 2016, 2017, 2023 Pieter Spronck
#   met toestemming om dit document te kopiëren, verspreiden,
#   en wijzigen volgens de regels van de
# Creative Commons Attribution-NonCommercial 3.0 Unported License
#   https://creativecommons.org/licenses/by-nc/3.0/
#
# Met aanvullingen en opmerkingen voor gebruik tijdens de
#   HCC!programmeren online Python sessies op donderdagavond
#     https://programmeren.hcc.nl
#   zie ook downloads op
#     https://programmeren.hcc.nl/ontwikkel/python/python-cursus-voor-beginners
#   versie 0.169 - 2026-09-04
# Copyright © 2023-2026 Anthony Vogelaar,
#                       Meindert Meindertsma,
#                       Theo van Haandel  py39@x4a.nl
#   met dezelfde toestemming als hierboven volgens de regels van de
# Creative Commons Attribution-NonCommercial 3.0 Unported License
#   https://creativecommons.org/licenses/by-nc/3.0/
#   De meeste bestanden in PythonCursusXxx.zip hebben deze licentie.
#   Enkele bestanden hebben een andere licentie,
#     dat staat dan vermeld in het bestand of in de map,
#       zoals bijvoorbeeld de MIT licentie in de map
#       extra_starter_kit\2_lcd\driver\
# Met dank aan de deelnemers van de Python cursus voor beginners.

# de meeste onderdelen zijn eenvoudig te runnen in IDLE
#   door 'if 0:' te wijzigen naar 'if 1:'
#   en daarna weer terug
# enkele eventuele ontbrekende .py en .txt bestanden
#   kunnen op die manier gemaakt worden
#   zie Appendix C, D en E hieronder
# met alternatieve antwoorden
#   op de opgaven aan het einde van een hoofdstuk
#   zie Appendix X hieronder
# met aanvullende onderwerpen die niet in het boek staan
#   zie vanaf Aanvulling 1 hieronder
# echte beginners die niet eerder hebben geprogrammeerd
#   kunnen eerst Hedy doornemen
#   zie Aanvulling 0
###############################################################
# Titel en versie

# Voorwoord

# Wijzigingen in Versie 1.1

# Dankwoord

# Bijdragen

###############################################################
# Inhoudsopgave

# Voorwoord

# 1 Introductie

# 1.1 Hoe dit boek te gebruiken
# 1.2 Aannames en veronderstellingen
# 1.3 Waarom Python?
# 1.4 Python's beperkingen als programmeertaal
# 1.5 Wat betekent "denken als een programmeur?" 1.5
# 1.6 De kunst van het programmeren
# 1.7 Groei van klein naar groot
# 1.8 Python 2 of Python 3?
# 1.9 Oefening

# 2 Python Gebruiken

# 2.1 Python installeren
# 2.2 Python programma's creëren
# 2.3 Python programma's uitvoeren
# 2.4 Aanvullend materiaal

# 3 Expressies

# 3.1 Resultaten tonen
# 3.2 Data types
# 3.3 Expressies
# 3.4 Stijl

# 4 Variabelen

# 4.1 Variables en waardes
# 4.2 Variabele namen
# 4.3 Debuggen met variabelen
# 4.4 Soft typing
# 4.5 Verkorte operatoren
# 4.6 Commentaar

# 5 Eenvoudige Functies

# 5.1 Elementen van een functie
# 5.2 Basis functies
# 5.3 Modules

# 6 Condities

# 6.1 Boolean expressies
# 6.2 Conditionele statements
# 6.3 Vroegtijdig afbreken
# 6.4 Matchen van cases

# 7 Iteraties

# 7.1 while loop
# 7.2 for loop
# 7.3 Loop controle
# 7.4 Geneste loops
# 7.5 De loop-en-een-half
# 7.6 Slim gebruik van loops
# 7.7 Over het ontwerpen van algoritmes

# 8 Functies

# 8.1 Het nut van functies
# 8.2 Het creëren van functies
# 8.3 Scope en levensduur
# 8.4 Grip krijgen op complexiteit
# 8.5 Modules
# 8.6 Anonieme functies

# 9 Recursie

# 9.1 Wat is recursie?
# 9.2 Recursieve definities

# 10 Strings

# 10.1 Herhaling
# 10.2 Strings over meerdere regels
# 10.3 Speciale tekens
# 10.4 Tekens in een string
# 10.5 Strings zijn onveranderbaar
# 10.6 string methodes
# 10.7 Codering van tekens

# 11 Tuples

# 11.1 Gebruik van tuples
# 11.2 Tuples zijn onveranderbaar
# 11.3 Toepassingen van tuples

# 12 Lists

# 12.1 Basis van lists
# 12.2 Lists zijn veranderbaar
# 12.3 Lists en operatoren
# 12.4 List methodes
# 12.5 Alias
# 12.6 Geneste lists
# 12.7 List casting
# 12.8 List comprehensions

# 13 Dictionaries

# 13.1 Dictionary basis
# 13.2 Dictionary methodes
# 13.3 Keys
# 13.4 Opslaan van complexe waardes
# 13.5 Snelheid

# 14 Sets

# 14.1 Basis van sets
# 14.2 Set methodes
# 14.3 Frozensets

# 15 Besturingssysteem

# 15.1 Computers en besturingssystemen
# 15.2 Command prompt
# 15.3 Bestandssysteem
# 15.4 os functies

# 16 Tekstbestanden

# 16.1 Platte tekstbestanden
# 16.2 Lezen van tekstbestanden
# 16.3 Schrijven in tekstbestanden
# 16.4 Toevoegen aan tekstbestanden
# 16.5 os.path methodes
# 16.6 Encoding

# 17 Exceptions

# 17.1 Errors en exceptions
# 17.2 Afhandelen van exceptions
# 17.3 Exceptions bij bestandsmanipulatie
# 17.4 Genereren van exceptions

# 18 Binaire Bestanden

# 18.1 Openen en sluiten van binaire bestanden
# 18.2 Lezen uit een binair bestand
# 18.3 Schrijven in een binair bestand
# 18.4 Positioneren van de pointer

# 19 Bitsgewijze Operatoren

# 19.1 Bits en bytes
# 19.2 Manipulatie van bits
# 19.3 Het nut van bitsgewijze operaties

# 20 Object Oriëntatie

# 20.1 De object georiënteerde wereld
# 20.2 Object oriëntatie in Python
# 20.3 Methodes
# 20.4 Nesten van objecten
# 20.5 Geheugenbeheer

# 21 Operator Overloading

# 21.1 Het idee achter operator overloading
# 21.2 Vergelijkingen
# 21.3 Berekeningen
# 21.4 Eénwaardige operatoren
# 21.5 Sequenties

# 22 Overerving

# 22.1 Class overerving
# 22.2 Interfaces

# 23 Iteratoren en Generatoren

# 23.1 Iteratoren
# 23.2 Generatoren
# 23.3 itertools module

# 24 Command Line Verwerking

# 24.1 De command line
# 24.2 Flexibele command line verwerking

# 25 Reguliere Expressies

# 25.1 Reguliere expressies met Python
# 25.2 Reguliere expressies schrijven
# 25.3 Groeperen
# 25.4 Vervangen

# 26 Bestandsformaten

# 26.1 CSV
# 26.2 Pickling
# 26.3 JavaScript Object Notation (JSON)
# 26.4 HTML en XML

# 27 Diverse Nuttige Modules

# 27.1 datetime
# 27.2 collections
# 27.3 urllib
# 27.4 glob
# 27.5 statistics

# Aanvulling 0 Hedy

# Aanvulling 1 memoryview en complex

# Aanvulling 2 *args, **kwargs, * en /

# Aanvulling 3 Pip, PyPI en virtuele environments

# Aanvulling 4 Pillow / PIL

# Aanvulling 5 PySide6, Qt for Python

# Aanvulling 6 Tkinter

# Aanvulling 7 Matplotlib

# Aanvulling 8 guizero

# Aanvulling 9 PySimpleGUI

# Aanvulling 10 callbacks, decorators en properties

# Aanvulling 11 Interface met Excel

# Aanvulling 12 Multitasking

# Aanvulling 13 sqlite3

# Aanvulling 14 MicroPython

# Aanvulling 15 Requests

# Aanvulling 16 NumPy

# Aanvulling 17 INI, TOML, JSON, XML en YAML

# Aanvulling 18 Systeembeheer
    
# Aanvulling 19 MySQL / MariaDB

# Aanvulling 20 Jupyter

# Aanvulling 21 SciPy

# Aanvulling 22 pandas

# Aanvulling 23 MQTT

# Aanvulling 24 3D

# Aanvulling 25 Fractals

# Aanvulling 26 SVG

# Aanvulling 27 Git en GitHub

# Aanvulling 28 Oscilloscoop met Matplotlib

# Aanvulling 29 DuckDB

# Aanvulling 30 Web Browser Engineering

# Aanvulling 31 Signalen in MicroPython met CPython simulatie

# Aanvulling 32 Quarto en RStudio

# Aanvulling 33 TekstKlok

# Aanvulling 34 I2C met MicroPython en met Raspberry Pi

# Aanvulling 35 SPI met Raspberry Pi

# Aanvulling 36 Simulatie met guizero

# Aanvulling 37 SPI met Raspberry Pi Pico

# Aanvulling 38 Python op Android

# Aanvulling 39 Webapp GUI met Microdot

# Aanvulling 40 The Fuzzing Book

# Aanvulling 41 PySDR

# Aanvulling 42 Thonny en MicroPython op een microcontroller zetten

# Aanvulling 43 turtle

# Aanvulling 44 Keyestudio ESP32 Inventor Starter Kit FKS0001

# Aanvulling 45 Pyodide

# Aanvulling 46 uv

# Aanvulling 47 Tabs en spaties

# Aanvulling 48 Bestanden met codecs

# Aanvulling 49 socket

# Aanvulling 50 VPython, GlowScript en RapydScript-ng

# Aanvulling 51 Visual Studio Code

# Aanvulling 52 Classes en inheritance

# Appendix A Problemen Oplossen

# Appendix B Verschillen met Python 2

# Appendix C pcinput.py

# Appendix D pcmaze.py

# Appendix E Test Tekstbestanden

# Appendix F Antwoorden

# Appendix G Engelse Terminologie

# Appendix X Extra


###############################################################
# Titel en versie
#   De Programmeursleerling
#   Leren coderen met Python 3
#   Pieter Spronck
#   Version 1.1.1
#   17-06-2024
#   Copyright © 2016, 2017, 2023 Pieter Spronck
#   Creative Commons Attribution-NonCommercial 3.0 Unported License
#   https://creativecommons.org/licenses/by-nc/3.0/
#   vertaling van de auteur zelf van het boek
#     The Coder's Apprentice: Learning Programming with Python 3
#     Copyright © 2016, 2017 Pieter Spronck
#   meest recente versie van het boek beschikbaar via
#   http://www.spronck.net/pythonbook

# Voorwoord
#  visie van de auteur
#   wereld verandert snel door computer technologie
#   auteur 30 jaar geleden eerste baan als computerprogrammeur
#     bijna niemand had een computer
#     er was nog geen PC, internet, mobiele telefoon
#   in 30 jaar leven en werken drastisch gewijzigd
#     postbodes, banken, helpdesks, warenhuizen
#       geautomatiseerd en meer online
#     chauffeurs voor transport op termijn ook minder
#   naast beroep laag opgeleiden ook hoog opgeleiden niet veilig
#     auteur programmeren onderwijs aan journalisten gegeven
#     computer kan juristen helpen zoeken naar achtergrond informatie
#       kan componeren, schilderen, beeldhouwen zoals met 3D printer
#       helpen bij wetenschappelijke experimenten
#   in 30 jaar arbeidsmarkt veranderd
#     van computers waren zeldzaam naar
#     behoefte menselijke arbeid afgenomen, nog steeds
#   in toekomst alleen beroepen voor mensen die kunnen bijdragen
#     aan wat computer niet alleen kan doen
#     kracht van computer en mens combineren om beide te versterken
#   om kwaliteit van werk verbeteren
#     ervaring met gebruik word processor spreadsheet is onvoldoende
#     moet in staat zijn om mogelijkheden van computers uit te buiten
#     journalist die programma om feiten te verzamelen
#       alleen kan gebruiken is niet nodig
#     journalist die dit programma
#       kan uitbreiden en verbeteren is waardevolle kracht
#   vaardigheden zijn nodig
#     om te denken en problemen oplossen als een programmeur
#     daarvoor cursussen op dit gebied volgen
#   zou verwachten programmeren basis vaardigheid
#     op universiteiten en hogescholen maar is niet zo
#   iedere opleiding faalt
#     die programmeren niet verplichte cursus maakt
#     zelfs middelbare scholen en misschien zelfs basisscholen
#       zouden leerlingen moeten leren programmeren
#     programmeren vereist een creatieve manier van denken
#       gemakkelijker te leren op jonge leeftijd
#       lastiger als al getraind in reproductieve manier
#         van denken en werken die scholen aanleren
#   alle studenten van elke richting moet leren programmeren
#     niet omdat iedereen professioneel programmeur moet worden
#     maar om problemen op te lossen als programmeur en
#       inzicht te krijgen
#         in mogelijkheden en beperkingen van computers
#     geeft kracht om computers in te zetten
#       in een specifiek domein
#       op een uniek menselijke manier
#   doel van boek
#     leren programmeren voor iedereen die dat wil
#     voornamelijke voor middelbare scholieren
#       en studenten die onbekend zijn met programmeren
#     boek maakt het mogelijk voor iedereen
#       om basiskennis van programmeren op te doen
#       voorbereid op arbeidsmarkt van 21e eeu
#  Pieter Spronck 2016
#   Computer Science professor bij Tilburg University

# Wijzigingen in Versie 1.1
#   eerste versie in 2017 gebaseerd op Python 3.4
#   deze verzie op Python 3.11
#   wijzigingen Python relatief klein
#   onderwerpen erbij: type hints, formattering van strings,
#     behoud volgorde dictionary keys, case matches

# Dankwoord
#   Allan B. Downey auteur boek Think Python
#   Peter Wentworth
#   Guido van Rossum heeft Python origineel ontworpen
#     en anderen
#   website http://www.spronck.net/pythonbook
#     Engelstalige downloads
#       The Coder's Apprentice PDF
#       Tablet version PDF
#       listings, library code, exercise templates, test files
#       answers to exercises

#       Computational Thinking with Python PDF
#        version 1.0.0  June 10, 2023  82 bladzijden
#        aanvulling op boek, alleen Engelstalig

#     Dutch version
#   website https://www.spronck.net/pythonbook/dutchindex.xhtml
#     Nederlandstalige downloads
#       De Programmeursleerling PDF
#       Tablet-versie PDF
#       listings, modules, templates voor opgaven, testbestanden
#       antwoorden voor de opgaven

#   suggesties stuur email naar pythonbook@spronck.net
#   forum http://www.spronck.net/forum
#     the forum has been discontinued

# Bijdragen
#   lijst met namen en verbeteringen in welke versie


###############################################################
# 1 Introductie

# computers laten doen wat je wilt
#   heet programmeren

# zij die kunnen programmeren
#   zijn veel beter in staat
#    kracht van computers in te zetten
#     voor bijvoorbeeld
#      gebruik maken van grote hoeveelheid data
# iedereen behoort programmeren te leren

# niet alleen begrijpen wat regels van programma doen
#   maar ook denken als programmeur
# problemen analyseren
#   hoe oplossen met computer
# kan dit niet leren uit een boek
#   maar alleen door programma's te maken

# dit boek om de basis van Python 3 te leren
#   niet alleen de taal gebruiken
#     ook oefeningen doen

# niet alleen voor studenten die al kunnen programmeren
#   maar ook met name voor wie programmeren nog onbekend is
# boek uitgebreider dan andere
#   probeert mogelijke problemen al te voorzien
#    die tegen kan komen bij begrijpen nieuwe concepten

#=============================================================#
# 1.1 Hoe dit boek te gebruiken

# bedoeld als cursus
#   om te leren
# niet als naslagwerk
#   zie daarvoor http://docs.python.org

# hoofdstukken in volgorde bestuderen
# voor korte cursus imperatief programmeren
#   hoofdstukken 1 tot en met 19
#     optioneel 9, 14, 17, 18, 19

# uitgebreidere cursus met object oriëntatie
#   ook hoofdstukken 20 tot en met 23
#     optioneel 23

# overige hoofdstukken 24 tot en met 27
#   nuttig maar optioneel

# computer bij de hand houden
#   met Python erop geïnstalleerd
# boek bevat vele kleine en grote oefeningen
#   als overslaat leer je niet goed programmeren

# veel code fragmenten hebben bestandsnaam
#   beschikbaar als bestand
# ook hieronder overgenomen
#   in ProgrammeursleerlingExtra.py

# kopiëren en plakken vanuit PDF
#   werkt vaak niet goed
# geen probleem bij gebruik van code hieronder

#=============================================================#
# 1.2 Aannames en veronderstellingen

# veronderstelt geen programmeersvaardigheden
#   wel wil om die aan te leren
# veronderstelt vermogen om abstract te denken

# flinke hoeveelheid tijd nodig
# 100 tot 200 tot 400 uur

# leert je niet om professioneel programmeur te zijn
#   wel initiële vaardigheden

# is voor meeste mensen voldoende basis
#   om zelf meer te leren wanneer nodig

# zoveel mogelijk Nederlandse taal
# soms toch Engelse terminologie
# 1 - sommige termen niet goed vertaalbaar
# 2 - sommige zijn Python taalelementen
# 3 - soms is Engelse variant meer gebruikelijk
# zie ook Appendix G Engelse Terminologie

#=============================================================#
# 1.3 Waarom Python?

# Python geschikt om te leren programmeren
# krachtige taal, gemakkelijk te gebruiken
# biedt alle mogelijkheden die andere talen ook bieden
# draaien op verschillende machines en operating systemen
# gratis verkrijgbaar
# dwingt nette code te schrijven handig voor beginners
# wordt in de praktijk veel gebruikt
#   soms als basis voor compleet programma
#   soms als uitbreiding van programma in andere taal

# belangrijkste voordeel
# denken als een programmeur
# geen aandacht nodig voor excentrieke afwijkingen
#   voor een specifieke programmeertaal

# voorbeelden Hello World programma's

# C++

#   #include <iostream>
#   int main() {
#       std::cout << "Hello, world!";
#   }

# C#

#   using System;
#   namespace HelloWorld {
#       class Hello {
#           static void Main() {
#               Console.WriteLine( "Hello, world!" );
#               Console.ReadKey();
#           }
#       }
#   }

# Objective-C

#   #import <Foundation/Foundation.h>
#   int main ( int argc, const char * argv[] ) {
#       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#       NSLog (@"Hello, world!");
#       [pool drain];
#       return 0;
#   }

# Java

#   class Hello {
#       public static void main( String[] args ) {
#           System.out.println( "Hello, world!" );
#       }
#   }

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#
# zie hoofdstuk 8 Functies voor uitleg over def
def markeer( onderdeel, newline=True ):
    # print markering om aan te geven
    #   welk onderdeel met if 1: actief is
    print()
    print( '-' * 4, onderdeel, '-' * ( 50 - len(onderdeel) ) )
    if newline:
        print()
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#

# Python

if 0:
    markeer( '1.3' )

    print( "Hello, world!" )

# Python leesbaarder en begrijpelijker
#   zelfs als je de taal niet kent

#=============================================================#
# 1.4 Python's beperkingen als programmeertaal

# Python universele programmeertaal

# kunt voor van alles gebruiken
# maar is niet altijd meest geschikt
# soms C# of C++ voor games
#   vanwege snelle programma's

# andere talen voor sommige problemen beter

#=============================================================#
# 1.5 Wat betekent "denken als een programmeur?"

# illustratie aan de hand van een taak

# stapel kaarten sorteren van laag naar hoog
# de meeste mensen kunnen die taak uitvoeren
# maar goed uitleggen hoe is lastig

# aan computer uitleggen
# kan niet met vage uitspraken

# computer begrijpt computertaal
# heeft precieze syntax en semantiek
# stap voor stap uitleggen

# overzien van alle stappen is lastig
# moet taak verdelen in kleinere subtaken
# die misschien ook weer opdelen in subtaken
# net zolang tot je deze kunt implementeren
# en samenvoegen tot geheel programma

# perspectief van computer programma
# logische opdeling in subtaken
# kan vaardigheid leren
#   veel oefening vereist

# Python leren
# beginnen met kleine programma's
# groeien in complexiteit
# beheerst zo langzaamaan ook denkprocessen
#   van programmeur

#=============================================================#
# 1.6 De kunst van het programmeren

# programmeren is een kunstvorm
# docent programmeren vergelijkbaar met tekenleraar

# vaardigheden in tekenlessen overbrengen
# oefenen met materialen
# leerlingen opdrachten uitvoeren
# docent beoordeelt gebruik materialen
#   en hoe goed opdracht uitgevoerd

# zo ook docent programmeren
# vertelt over programmeerprincipes,
#   taalelementen computertaal,
#   geavanceerde technieken, lastige concepten
# studenten krijgen opdrachten
# docent beoordeelt programmeertechnieken
#   en uitvoeren opdracht

# tekenleraar opdracht teken een kat
# student met cirkel en 2 driehoekjes
#   beheerst niet materiaaltechnieken
# student met prachtige tekening van boom
#   niet te gebruiken voor taak
# 2 studenten met precies dezelfde tekening
#   hebben plagiaat gepleegd
# niet slechts één acceptabele tekening
#   er zijn vele mogelijke tekeningen

# op dezelfde wijze docent programmeren
# laat studenten kennis inzetten voor
#   programma maken dat aan opdracht voldoet
# studenten die techniek niet beheersen
#   kunnen taak niet of slechts deels oplossen
# studenten die techniek wel kennen
#   kunnen nog steeds niet in staat zijn om
#   creatieve nieuwe manier oplossing inleveren
# 2 studenten met exact dezelfde oplossing
#   hebben ergens vandaan gekopieerd, plagiaat

# programmeren is een kunstvorm
# moet technieken leren beheersen
# en ook deze op creatieve manieren inzetten
#   om problemen op te lossen
# bij programma's gemakkelijker te beoordelen
#   als oplossing van specifiek probleem
#   dan tekening

# wordt nooit goede artiest
#   als je alleen materialen bestudeert
# moet met materialen oefenen
#   vaardigheden trainen om
#   verschillende vaardigheden uit te voeren
# voor programmeren geldt precies hetzelfde
# kunt niet leren programmeren
#   zonder een hoop programma's te schrijven
# programmeren vereist niet alleen kennis
#   ook vaardigheden ontwikkeld door kennis
#   en creativiteit voor uitbreiden vaardigheden

# maar weinig meester-kunstenaars
#   met werk in tentoonstellingen
# op dezelfde manier maar weinig meester-programmeurs
# is voor meesten ook niet nodig
#   zolang in staat programmeerproblemen oplossen
# volstaat niet om slechts basis technieken te beheersen
#   creativiteit is altijd nodig

#=============================================================#
# 1.7 Groei van klein naar groot

# niet enige Python boek
# meeste andere veronderstellen behoorlijk wat
#   achtergrondkennis en ervaring
# boeken voor beginners zijn zeldzaam
# toch behoorlijk wat alternatieven, sommige gratis
#   allemaal in het Engels

# probleem Pythonboeken voor beginners
# beginnen meteen met nuttige of leuke applicaties
# zoals: Leer Games maken met Python

# dergelijke aanpak is misleidend
# maken slechts eenvoudige spelletjes
# zelfs die zijn te lastig voor nieuwelingen

# moet eerst starten met basis concepten
# motivatie moet zijn wens of noodzaak
#   om te leren programmeren
# niet om na een paar uur
#   een leuk spel te kunnen maken
# daarom begin basis programmeerkennis
# bouwt gestaag op naar complexere zaken
# blijft niet steken in kleine dingen
# als hele boek doorwerkt dan
#   aan het einde een vaardig programmeur

# opgaves in boek geprobeerd aantrekkelijk te maken
# sommigen vinden plezierig om aan te werken
#   voor anderen een lijdensweg
# als je wilt leren programmeren en opgaves maakt
#   zal het boek je alles leren om iedere applicatie te maken
#   ook leuke spelletjes

#=============================================================#
# 1.8 Python 2 of Python 3?

# verschillende versies van Python
# Python 2 en Python 3
# niet compatibel met elkaar
# nog steeds veel Python 2 programma's in gebruik

# in Python 3 aantal inconsistenties en eigenaardigheden
#   van Python 2 opgelost
# groot voordeel voor nieuwe studenten

# voorbeeld 7/4
# in Python 2 geheel getal
# in Python 3 uitkomst 1.75

# Python 3 intuïtiever dan Python 2
# meeste Python programma's en modules
#   op zijn minst geconverteerd zijn naar Python 3
# daarom dit boek geschreven voor Python 3
# zie appendix 2 voor lijst verschillen (niet volledig)

# niet in boek
#  in 2025 keuze minder van belang
#  laatste Python 2 versie
#    2.7.18 verschenen in 2020
#  heeft geen ondersteuning meer

#=============================================================#
# 1.9 Oefening

# meest hoofdstukken kleine oefeningen
#   op diverse plaatsen in tekst
# bedoeld als uitleg of snelle test
# meteen doen zodra je deze tegenkomt
# als niet lukt
#   voorgaande tekst opnieuw bestuderen
# als nog steeds niet lukt
#   hulp vragen

# aan einde van ieder hoofstuk sectie "Opgaves"
#   met genummerde opgaves
# moet alle opgaves zelfstandig proberen te maken
#   zonder hulp, zonder antwoorden opzoeken
# antwoorden zijn opgenomen achterin het boek
#   in Appendix F, ook hieronder

# - aan opgaves werken tot opgelost
# - alle opgaves maken
# - opgaves zelfstandig maken
# - geen extra informatie nodig
#     die nog niet naar voren is gebracht
# - als opgave opgelost vergelijken met antwoord in boek
#   vaak anders, hoeft niet fout te zijn

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 1.1
# Opgave 1.2

###############################################################
# 2 Python Gebruiken

#=============================================================#
# 2.1 Python installeren

# voor Microsoft Windows 10 en 11
#  gebruik bij voorkeer downloads
#    vanaf standaard Python website
#      https://www.python.org
#    alle downloads
#      https://www.python.org/downloads/
#   nieuwste versie bijvoorbeeld 3.13.2
#     geeft soms problemen bij externe modules
#       meest recente van versie ervoor 3.12.9 wellicht handiger
#   versie maakt voor Python code in boek niet veel verschil
#     wel voor aanvullende onderwerpen
# Windows installer 64-bit recommended 

# kan Python versies naast elkaar installeren
#   3.13, 3.12, 3.11, 3.10, 3.9, ...
#     zelf kiezen welke
#     niet 32-bit en 64-bit tegelijk
#   3.13.2, 3.12.9, 3.11.11 zijn bug fix releases
#     3.12.9 overschrijft vorige 3.12.8, 3.12.7, ... versies

# install en uninstall uitproberen
# systeem breed of voor enkele gebruiker
# met en zonder aanpassen PATH environment variabele
# bekijken waar python.exe staat
#   wat doet pythonw.exe

# hoe IDLE starten
#   via Start menu
#   als module: py -m idlelib
#   als shortcut
#     kopie van Startmenu
#     daarna settings aanpassen

# py.exe programma komt in Windows directory
#   universele manier om Python code te runnen
#   heeft eigen uninstaller

# Python installatie via Microsoft store
#   beter vermijden
#   heeft beperkte schrijfrechten

# voor Linux Mint
#  gebruik bij voorkeur Systeem Python
#   is al geïnstalleerd
#   'python3' commando
#     is voldoende voor gebruik met boek
#  kan beter Systeem Python niet wijzigen
#    wordt gebruikt voor beheer van Mint
#  https://forums.linuxmint.com/viewtopic.php?t=416132
#   do not install another version of Python system wide
#  gebruik pyenv voor installatie van meer Python versies
#   https://forums.linuxmint.com/viewtopic.php?t=431748
#   https://forums.linuxmint.com/viewtopic.php?t=362499
#   https://realpython.com/intro-to-pyenv/
#  extra install nodig voor IDLE
#   https://forums.linuxmint.com/viewtopic.php?t=367226
#    sudo apt-get install idle

# zie verder
# https://realpython.com/installing-python/

# Python in een webbrowser
# https://pyodide.org
# https://pyodide.org/en/stable/console.html

# gebruik van pip
#   niet nodig voor Python code in boek
#   wel handig voor aanvullende onderwerpen

# Python virtual environments
#   niet nodig voor boek
#   staan los van de algemene Python installatie
#   is een mogelijkheid die na installatie te gebruiken is

#=============================================================#
# 2.2 Python programma's creëren

# Python programma's zijn tekst bestanden
# gewoonlijk met extensie .py
#   dus naam eindigt op .py
# bijvoorbeeld test.py
#   of ProgrammeursleerlingExtra.py

# onder Windows heel soms met extensie .pyw
#   dan niet automatisch een command window
#   bijvoorbeeld gebruikt door IDLE

# kan .py bestand met een text editor maken
#   bijvoorbeeld met IDLE

# in IDLE
#   Python Shell window
#     voor uitvoeren korte stukjes Python code
#   editor windows
#     via menu File, New of File, Open
#     elk voor een .py bestand
#     starten via menu Run of F5
#       uitvoer output in Shell window

# kan ook andere editor of IDE gebruiken
#   zoals Notepad, Thonny,
#      Visual Studio Code, PyCharm
#   sommige met syntax highlighting
# veel meer editors en IDEs beschikbaar
#   voor werken met Python .py programma's

#=============================================================#
# 2.3 Python programma's uitvoeren

# uitvoeren in GUI zoals Windows File Explorer
#  door op .py bestand dubbel klikken
#    of enkel klik voor focus dan Enter
#   vaak flitst uitvoer voorbij, hangt af van code

# Python programma's worden uitgevoerd in
#   een command-line shell
# opent shell zwart window
#   voert dan Python programma uit
#   zodra afgelopen sluit shell

# kan zelf command-line shell beginnen
#   en daarbinnen Python programma runnen

# run cmd.exe, bijvoorbeeld
#   via menu Start, Programs, Systeem
#   of via Zoeken (Win+S)
#   of via Run (Win+R)
# of powershell.exe
# of Windows terminal

# maak test.py bestand met 1 regel inhoud
#   print('Dit is een test programma met unieke code q0e')

# uitvoeren door één of meer van volgende opdrachten:
# > python test.py
# > test.py
# > py test.py
# > python3 test.py

#=============================================================#
# 2.4 Aanvullend materiaal

# zie ook de Python help informatie van Anthony Vogelaar
#   in python_help.pdf
#     in PythonCursusXX.zip

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 2.1
# Opgave 2.2

###############################################################
# 3 Expressies

#=============================================================#
# 3.1 Resultaten tonen

if 0:
    markeer( '3.1 A' )

    5 + 7

if 0:
    markeer( '3.1 B' )

    print( "Hello, world!" )

if 0:
    markeer( '3.1 C' )

    print( "Ik", "heb", "twee", "appels", "en", "een", "banaan" )

if 0:
    markeer( '3.1 D' )

    print("Ik","heb","twee","appels","en","een","banaan")

if 0:
    markeer( '3.1 E' )

    print( "Ik", "heb", 2, "appels", "en", 1, "banaan" )

#-------
# Opgave

if 0:
    markeer( '3.1 Opgave' )

    print( "Een boodschap met spatie aan einde " )
    print( " een boodschap met spatie aan begin" )
    print( 'En', 'nu', 'iets', 'heel anders', 'met enkele aanhalingstekens' )
    print( "En", "nu", "iets", "heel anders", "met dubbele aanhalingstekens" )

#=============================================================#
# 3.2 Data types

#-------------------------------------------------------------#
# 3.2.1 Strings

if 0:
    markeer( '3.2.1 A' )

    print( 'mango\'s' )

if 0:
    markeer( '3.2.1 B' )

    print( 'mango\\\'s' )

if 0:
    markeer( '3.2.1 C' )

    # print( “ronde aanhalingstekens” ) # Syntax Error
    print( '“ronde aanhalingstekens”' )

#-------------------------------------------------------------#
# 3.2.2 Integers

if 0:
    markeer( '3.2.2' )

    print( 1,000,000,000 )

#-------
# Opgave

if 0:
    markeer( '3.2.2 Opgave' )

    # komma is geen scheiding in getal, komma print meerdere getallen
    print( 1,000,000,000 ) # 1 0 0 0
    
    # underscore kan wel vanaf Python 3.6
    # print( 1_000_000_000 ) # 1000000000

#-------------------------------------------------------------#
# 3.2.3 Floats

if 0:
    markeer( '3.2.3' )

    print( 3.1415926 )
    
    print( 0.0 )

#=============================================================#
# 3.3 Expressies

#-------------------------------------------------------------#
# 3.3.1 Eenvoudige berekeningen

#   +   optelling
#   -   aftrekking
#   *   vermenigvuldiging
#   /   deling
#   //  integer deling
#   **  machtsverheffing
#   %   modulo

if 0:
    markeer( '3.3.1' )

    print( 15+4 )
    print( 15-4 )
    print( 15*4 )
    print( 15/4 )
    print( 15//4 )
    print( 15**4 )
    print( 15%4 )

#-------------------------------------------------------------#
# 3.3.2 15 Complexe berekeningen

if 0:
    markeer( '3.3.2 A' )

    print( 5*2-3+4/2 )

if 0:
    markeer( '3.3.2 B' )

    print( 5 * 2 - 3 + 4 / 2 )
    
if 0:
    markeer( '3.3.2 C' )

    print( 5*2 - 3+4    / 2 )

if 0:
    markeer( '3.3.2 D' )

    print( (5*2) - (3+4)/2 )
    print( ((5*2)-(3+4)) / 2 )

#-------
# Opgave

if 0:
    markeer( '3.3.2 Opgave' )

    print( 7 * 24 * 60 * 60 )

#-------------------------------------------------------------#
# 3.3.3 String expressies

if 0:
    markeer( '3.3.3' )

    print( "tot"+"ziens" )
    print( 3*"hallo" )
    print( "tot ziens"*3 )

#-------------------------------------------------------------#
# 3.3.4 Type casting

#   int()
#   float()
#   str()

if 0:
    markeer( '3.3.4 A' )

    print( 15/4 )
    print( int( 15/4 ) )

if 0:
    markeer( '3.3.4 B' )

    print( 15+4 )
    print( float( 15+4 ) )

if 0:
    markeer( '3.3.4 C' )

    print( "Ik heb " + str( 15 ) + " appels." )

    # print( "Ik heb ", 15, " appels." )
    # print( "Ik heb", 15, "appels." )

#=============================================================#
# 3.4 Stijl

if 0:
    markeer( '3.4' )

    # Equivalente regels code
    print( 2 + 3 )
    print(2+3)
    print( 2+3)
    print       (     2     +     3     )

#=============================================================#
# Wat je geleerd hebt

# - Het gebruik van de print() functie om zaken op het scherm te tonen
# - Data types string, integer, en float
# - Berekeningen
# - Basale string expressies
# - Type casting tussen strings, integers, en floats, middels str(), int(), en float()

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 3.1
# Opgave 3.2
# Opgave 3.3
# Opgave 3.4
# Opgave 3.5

###############################################################
# 4 Variabelen

#=============================================================#
# 4.1 Variables en waardes

if 0:
    markeer( '4.1' )

    x = 5
    print( x )

#-------
# Opgave

if 0:
    markeer( '4.1 Opgave' )

    # opgave 3.3.2
    # print(60 * 60 * 24 * 7)

    weekseconden = 60 * 60 * 24 * 7
    print(weekseconden)

if 0:
    markeer( '4.1 A' )

    x = 5
    print( x )
    x = 7 * 9 + 13  # overschrijft de vorige waarde van x
    print( x )
    x = "En nu iets heel anders..."
    print( x )
    x = int( 15 / 4 ) - 27
    print( x )

if 0:
    markeer( '4.1 B' )

    x = 2
    y = 3
    print( "x =", x )
    print( "y =", y )
    print( "x * y =", x * y )
    print( "x + y =", x + y )

if 0:
    markeer( '4.1 C' )

    x = 2
    y = 3
    print( "x =", x, "en y =", y )

    # Verwissel de waardes van x en y via z
    z = x
    x = y
    y = z
    print( "x =", x, "en y =", y )

if 0:
    markeer( '4.1 D' )

    x = 2
    print( x )
    x = x + 3
    print( x )

if 0:
    markeer( '4.1 E' )

    print( dagen_per_jaar )
    dagen_per_jaar = 365

#=============================================================#
# 4.2 Variabele namen

# variabele naam
# - mag slechts bestaan uit letters, cijfers en underscores _
# - beginnen met letter of underscore
# - geen gereserveerd woord

# gereserveerde woorden (keywords)
# False  case     except  in       pass
# None   class    finally is       raise
# True   continue for     lambda   return
# and    def      from    match    try
# as     del      global  nonlocal while
# assert elif     if      not      with
# break  else     import  or       yield

# mag zowel hoofd- als kleine letters gebruiken
# Python is case sensitive, hoofdletter gevoelig

#-------------------------------------------------------------#
# 4.2.1 Conventies

# conventies voor namen van variabelen:
# - geen naam van functie
# - betekenisvol
# - korte naam voor variabelen in klein deel van code
# - alleen kleine letters, geen hoofdletters
#    uitzonderingen Constanten zie 4.2.3
# - bij naam met meerdere woorden underscore(s) ertussen
# - begin niet met underscore, is gereserveerd

# geen verplichting, afwijken mag

if 0:
    markeer( '4.2.1 A' )

    a = 3.14159265
    b = 7.5
    c = 8.25
    d = a * b * b * c / 3
    print( d )

if 0:
    markeer( '4.2.1 B' )

    pi = 3.14159265  # betere pi waarde in math module
    straal = 7.5
    hoogte = 8.25
    volume_van_kegel = pi * straal * straal * hoogte / 3
    print( volume_van_kegel )

#-------------------------------------------------------------#
# 4.2.2 Oefenen met variabele namen

if 0:
    markeer( '4.2.2' )

    classificatie = 1 # 1
    Classificatie = 1 # 2
    # cl@ssificatie = 1 # 3  # Cannot assign to operator, @ is nieuwe operator
    class1f1cat1e = 1 # 4
    # 1classificatie = 1 # 5  # Invalid syntax, variabele naam kan niet beginnen met getal
    _classificatie = 1 # 6
    # class = 1 # 7  # Invalid syntax, class is gereserveerd woord
    Class = 1 # 8

#-------------------------------------------------------------#
# 4.2.3 Constanten

if 0:
    markeer( '4.2.3 A' )

    totaal = 24.95
    eind_totaal = int( 100 * totaal * 1.21 ) / 100
    print( eind_totaal )

if 0:
    markeer( '4.2.3 B' )

    # leesbaarder
    BTW_FACTOR = 1.21
    CENTEN = 100

    totaal = 24.95
    eind_totaal = int( CENTEN * totaal * BTW_FACTOR ) / CENTEN
    print( eind_totaal )

#=============================================================#
# 4.3 Debuggen met variabelen

if 0:
    markeer( '4.3' )

    # foute code
    nr1 = 5
    nr2 = 4
    nr3 = 5
    print( nr3 / (nr1 % nr2) )
    nr1 = nr1 + 1
    print( nr3 / (nr1 % nr2) )
    nr1 = nr1 + 1
    print( nr3 / (nr1 % nr2) )
    nr1 = nr1 + 1
    print( nr3 / (nr1 % nr2) )

#-------
# Opgave

if 0:
    markeer( '4.3 Opgave' )

    nr1 = 5
    nr2 = 4
    nr3 = 5
    print( nr3 / (nr1 % nr2) )
    nr1 = nr1 + 1
    print( nr3 / (nr1 % nr2) )
    nr1 = nr1 + 1
    print( nr3 / (nr1 % nr2) )
    nr1 = nr1 + 1
    print('nr3', nr3, 'nr2', nr2, 'nr1', nr1, 'nr1 % nr2', nr1 % nr2)
    print( nr3 / (nr1 % nr2) )  # ZeroDivisionError, nr1 8 % nr2 4 is 0


#=============================================================#
# 4.4 Soft typing

if 0:
    markeer( '4.4' )

    a = 3
    print( type( a ) )
    a = 3.0
    print( type( a ) )
    a = "3.0"
    print( type( a ) )

#-------
# Opgave

if 0:
    markeer( '4.4 Opgave A' )

    a = 1
    b = 4
    c = "1"
    d = "4"
    print( a + b )
    print( c + d )

    # print(a + c)  # TypeError

#-------
# Opgave

if 0:
    markeer( '4.4 Opgave B' )

    naam = "John Cleese"
    print( "naam" )  # naam

#-------
# Opgave

if 0:
    markeer( '4.4 Opgave C' )

    naam = "John Cleese"
    # print( "naam" )
    print( naam )  # John Cleese

if 0:
    markeer( '4.4 Opgave D' )

    a: int = 1
    b: str = "4"
    print( type(a), type(b) )

if 0:
    markeer( '4.4 Opgave E' )

    a: int = "1"
    b: str = 4
    print( type(a), type(b) )

#=============================================================#
# 4.5 Verkorte operatoren

if 0:
    markeer( '4.5 A' )

    aantal_bananen = 100
    aantal_bananen = aantal_bananen + 1
    print( aantal_bananen )

if 0:
    markeer( '4.5 B' )

    aantal_bananen = 100
    aantal_bananen += 1
    print( aantal_bananen )

if 0:
    markeer( '4.5 C' )

    aantal_bananen = 100
    aantal_bananen += 12
    aantal_bananen -= 13
    aantal_bananen *= 19
    aantal_bananen /= aantal_bananen
    print( aantal_bananen )  # 1.0

#=============================================================#
# 4.6 Commentaar

if 0:
    markeer( '4.6' )

    # Commentaar: schrijf je code hier...
    # Valt het je op dat alles rechts van de # commentaar is?
    print( "Iets..." ) # dat door Python genegeerd wordt?
    print( "en iets anders.." ) # Geef zo commentaar op je code!

    # xx = 'een string\
# volgende regel'    # niet in boek
    # print(xx)

    """Een andere manier van commentaar geven is middels
    drievoudige aanhalingstekens. Dit soort commentaar mag
    meerdere regels""" # beslaan.
    '''dit mag ook via enkele aanhalingstekens''' # maar pas op
    # met het gebruik van aanhalingstekens IN je commentaar als
    # je de meerdere -regels method gebruikt.
    print( "Klaar." )

#=============================================================#
# Wat je geleerd hebt

# - Wat variabelen zijn
# - Het toekennen van een waarde aan een variabele
# - Correcte namen voor variabelen
# - Conventies met betrekking tot variabele namen
# - Het debuggen van code waarin variabelen onverwachte waardes hebben
# - Soft typing
# - Type hints
# - Verkorte operatoren
# - Commentaar

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 4.1
# Opgave 4.2
# Opgave 4.3
# Opgave 4.4

###############################################################
# 5 Eenvoudige Functies

#=============================================================#
# 5.1 Elementen van een functie

#-------------------------------------------------------------#
# 5.1.1 Functie naam

#-------------------------------------------------------------#
# 5.1.2 Parameters

if 0:
    markeer( '5.1.2 A' )

    x = 1.56
    print( int( x ) )
    print( x )

if 0:
    markeer( '5.1.2 B' )

    # volgorde maakt verschil
    basis = 2
    exponent = 3
    print( pow( basis, exponent ) )

if 0:
    markeer( '5.1.2 C' )

    basis = 2
    exponent = 3
    print( pow( exponent, basis ) ) # verwarrende variabele namen

if 0:
    markeer( '5.1.2 D' )

    # runtime errors
    # x = pow( 3, "2" ) # TypeError
    # y = int( "twee-en-een-half" ) # ValueError
    # y = int( "2.5" )  # ValueError
    # y = int( "2 \n" ) # ok

#-------------------------------------------------------------#
# 5.1.3 Retour Waarde

if 0:
    markeer( '5.1.3 A' )

    x = 2.1
    y = '3'
    z = int( x )
    print( z )
    print( int( y ) )

if 0:
    markeer( '5.1.3 B' )

    # None speciale waarde ook type
    print( print( "Hello, world!" ) )

#-------------------------------------------------------------#
# 5.1.4 Een functie is een zwarte doos

# pure functies
#   meeste

# niet pure functies
#  sommige, zoals modifiers
#   of bijvoorbeeld met output zoals print
#    of met globale variabelen

#=============================================================#
# 5.2 Basis functies

#-------------------------------------------------------------#
# 5.2.1 Type casting

# float()
# int()
# str()

#-------
# Opgave

if 0:
    markeer( '5.2.1  Opgave A' )

    # ValueError
    print( 10 * int( "100,000,000" ) )

#-------
# Opgave

if 0:
    markeer( '5.2.1  Opgave B' )

    print( 10 * int( "100000000" ) )

#-------------------------------------------------------------#
# 5.2.2 Berekeningen

# abs()
# max()
# min()
# pow()
# round()

#-------
# Opgave

if 0:
    markeer( '5.2.2  Opgave' )

    x = -2
    y = 3
    z = 1.27

    print( abs( x ) )
    print( max( x, y, z ) )
    print( min( x, y, z ) )
    print( pow( x, y ) )
    print( round( z, 1 ) ) # 1.3

if 0:
    markeer( '5.2.2' )

    # niet in boek
    z = 1.25
    print( round( z, 1 ) ) # 1.2
    z = 1.35
    print( round( z, 1 ) ) # 1.4

    # half afronden naar even
    # banker's rounding

    # https://docs.python.org/3/library/functions.html#round
    # https://realpython.com/python-rounding/#better-rounding-strategies-in-python
    # https://en.wikipedia.org/wiki/Banker's_rounding#Rounding_half_to_even

#-------------------------------------------------------------#
# 5.2.3 len()

#-------
# Opgave

if 0:
    markeer( '5.2.3  Opgave A' )

    print( len( 'man' ) )
    print( len( 'mango' ) )
    print( len( "" ) ) # "" is een lege string

#-------
# Opgave

if 0:
    markeer( '5.2.3  Opgave B' )

    print( len( 'mango\'s' ) )

    # niet in boek
    # print( len( "mango's" ) )
    # print( len( "mango\'s" ) )

#-------------------------------------------------------------#
# 5.2.4 input()

if 0:
    markeer( '5.2.4 A' )

    tekst = input( "Geef een tekst in: " )
    print( "Je hebt het volgende ingetypt:", tekst )

if 0:
    markeer( '5.2.4 B' )

    nummer = input( "Geef een getal: " )
    print( "Je getal in het kwadraat is", nummer * nummer )

if 0:
    markeer( '5.2.4 C' )

    nummer = input( "Geef een getal: " )
    nummer = float( nummer )
    print( "Je getal in het kwadraat is", nummer * nummer )

#-------
# Opgave

if 0:
    markeer( '5.2.4 Opgave' )

    getal1 = input("eerste getal:")
    getal1 = float(getal1)
    getal2 = input("tweede getal:")
    getal2 = float(getal2)
    print("optel", getal1 + getal2)
    print("vermenigvuldig", getal1 * getal2)

#-------------------------------------------------------------#
# 5.2.5 print()

if 0:
    markeer( '5.2.5' )

    print( "X", "X", "X", sep="x" )
    print( "X", end="" )
    print( "Y", end="" )
    # print( "H", '&&', sep='@', end="o" )
    print( "Z" )

#-------------------------------------------------------------#
# 5.2.6 format()

if 0:
    markeer( '5.2.6 A' )

    print( 7/11 )

if 0:
    markeer( '5.2.6 B' )

    print( round( 7/11, 3 ) )

if 0:
    markeer( '5.2.6 C' )

    print( "De eerste drie getallen zijn {}, {} en {}.".format(
        "een", "twee", "drie" ) )

if 0:
    markeer( '5.2.6 D' )

    print( "Achterwaarts zijn ze {2}, {1} en {0}.".format(
        "een", "twee", "drie" ) )

if 0:
    markeer( '5.2.6 E' )

    print( "De eerste drie getallen zijn {}, {} en {}.".format(
        1, "twee", 3.0 ) )

if 0:
    markeer( '5.2.6 F' )

    # precisie maakt soms langer
    print( "De eerste drie getallen zijn {:7}, {:7} en {:7}.".format(
        "een", "twee", "drie" ) )

if 0:
    markeer( '5.2.6 G' )

    # precisie maakt niet korter
    print( "De eerste drie getallen zijn {:3}, {:3} en {:3}.".format(
        "een", "twee", "drie" ) )

if 0:
    markeer( '5.2.6 H' )

    # uitlijnen alignment
    print( "De eerste drie getallen zijn {:<7}, {:^7} en {:>7}.".
        format( "een", "twee", "drie" ) )

    # niet in boek
    if 0:
        # kan ook hier volgorde instellen
        print( "De eerste drie getallen zijn {2:<7}, {1:^7} en {0:>7}.".
          format( "een", "twee", "drie" ) )

if 0:
    markeer( '5.2.6 I' )

    print( "{} gedeeld door {} is {}".format( 1, 2, 1/2 ) )
    print( "{:d} gedeeld door {:d} is {:f}".format( 1, 2, 1/2 ) )
    print( "{:f} gedeeld door {:f} is {:f}".format( 1, 2, 1/2 ) )

if 0:
    markeer( '5.2.6 J' )

    print( "{:5d} gedeeld door {:5d} is {:5f}".format( 1, 2, 1/2 ) )
    print( "{:<5f} gedeeld door {:^5f} is {:>5f}".format( 1, 2, 1/2 ))

if 0:
    markeer( '5.2.6 K' )

    print( "{:.2f} gedeeld door {:.2f} is {:.2f}".format( 1, 2, 1/2 ))

if 0:
    markeer( '5.2.6 L' )

    # redelijk uitziende tabel
    s = "{:>5d} keer {:>5.2f} is {:>5.2f}"
    # s = "string zonder accolades" # niet in boek
    print( s.format( 1, 3.75, 1 * 3.75 ) )
    print( s.format( 2, 3.75, 2 * 3.75 ) )
    print( s.format( 3, 3.75, 3 * 3.75 ) )
    print( s.format( 4, 3.75, 4 * 3.75 ) )
    print( s.format( 5, 3.75, 5 * 3.75 ) )

    # niet in boek
    if 0:
        # ingewikkeld
        a = 20 # wordt niet gebruikt, is anders dan bij f-strings
        s = "{a:>5d} keer {:>5.2f} is {:>5.2f}"
        print( s.format( 1, a, 1 * 3.75, a=10 ) )

if 0:
    markeer( '5.2.6 M' )

    # f-strings
    a = 3.75
    print( f"{2:>5d} times {a:>5.2f} is {2*a:>5.2f}" )

    # niet in boek
    if 0:
        txt = f"{2:>5d} times {a:>5.2f} is {2*a:>5.2f}"
        # txt = f"{2:>5d} times {a:>5.2f}{(a:=7.7):>5.2f} is {2*a:>5.2f}"
        print( txt )

        # a = 103     # heeft geen invloed meer op f-string
        # print( txt )

    if 0:
        txt = f'{2:>5d} times {a:>5.2f} is {2*a:>5.2f}'
        print( txt )

    if 0:    
        print( "{2:>5d} times {a:>5.2f} is {2*a:>5.2f}" )

if 0:
    markeer( '5.2.6 N' )

    # equivalent met .format
    a = 3.75
    print( "{:>5d} times {:>5.2f} is {:>5.2f}".format( 2, a, 2*a ) )

# zie ook RealPython artikel
# https://realpython.com/python-string-formatting/

# .format() voorbeelden
# https://docs.python.org/3/library/string.html#format-examples

# standaard ingebouwde format() functie heeft beperkte mogelijkheden
#  vergeleken met string .format() methode
# https://docs.python.org/3/library/functions.html#format

# nieuw t-strings vanaf Python 3.14
# https://peps.python.org/pep-0750/
# https://realpython.com/python-news-may-2025/#pep-750-template-strings-coming-to-python


#=============================================================#
# 5.3 Modules

# zie ook 8.5 Modules

if 0:
    markeer( '5.3 A' )

    import math
    print( math.sqrt( 4 ) )

if 0:
    markeer( '5.3 B' )

    # met andere naam
    import math as mth

    print( mth.sqrt( 4 ) )

if 0:
    markeer( '5.3 C' )

    from math import sqrt
    # from math import sqrt, sinh
    # from math import *

    print( sqrt( 4 ) )
    # print( sqrt( 4 ), sinh( 4 ) )

if 0:
    markeer( '5.3 D' )

    # met andere naam
    from math import sqrt as squareroot
    # from math import sqrt as squareroot, sinh as sinh2
    # from math import sqrt as squareroot, sinh

    print( squareroot( 4 ) )
    # print( squareroot( 4 ), sinh2( 4 ) )

#-------------------------------------------------------------#
# 5.3.1 math

# - exp()
# - log()
# - log10()
# - sqrt()

if 0:
    markeer( '5.3.1 A' )

    from math import exp, log

    print( "De waarde van e is bij benadering", exp( 1 ) )
    e_sqr = exp( 2 )
    print( "e kwadraat is", e_sqr, "wat betekent" )
    print( "dat log(", e_sqr, ") gelijk is aan", log( e_sqr ) )

if 0:
    markeer( '5.3.1 B' )

    from math import log10

    print( "De waarde van log10(1) is bij benadering", log10( 1 ) )
    print( "De waarde van log10(10) is bij benadering", log10( 10 ) )
    print( "De waarde van log10(100) is bij benadering", log10( 100 ) )

#-------------------------------------------------------------#
# 5.3.2 random

# - random()
# - randint()
# - seed()

if 0:
    markeer( '5.3.2' )

    from random import random, randint, seed

    seed()
    print( "Een toevalsgetal tussen 1 en 10 is", randint( 1, 10 ) )
    print( "Een ander is", randint( 1, 10 ) )
    seed( 0 )
    print( "3 toevalsgetallen zijn:", random(), random(), random() )
    seed( 0 )
    print( "Dezelfde 3 zijn:", random(), random(), random() )

#-------------------------------------------------------------#
# 5.3.3 pcinput

# - getInteger()
# - getFloat()
# - getString()
# - getLetter()

#-------
# Opgave

if 0:
    markeer( '5.3.3 Opgave A' )

    from pcinput import getInteger

    num1 = getInteger( "Geef een geheel getal: " )
    num2 = getInteger( "Geef een ander geheel getal: " )

    print( num1, "+", num2, "=", num1 + num2 )

#-------
# Opgave

if 0:
    markeer( '5.3.3 Opgave B' )

    import pcinput

    prompt = pcinput.getString("Geef een prompt om een float getal in te voeren:")
    getal = pcinput.getFloat(prompt)
    print("Getal is", getal)

#=============================================================#
# Wat je geleerd hebt

# - Wat functies zijn
# - Functie namen
# - Functie parameters
# - Functie retourwaardes
# - Details van type casting functies float(), int(), en str()
# - Basis berekeningen met abs(), max(), min(), pow(), en round()
# - len()
# - input()
# - Details van the print() functie
# - String formattering met format()
# - Formatteren via string literals
# - Wat modules zijn
# - De math functies exp(), log(), log10(), en sqrt()
# - De random functies random(), randint(), en seed()
# - De pcinput functies getInteger(), getFloat(), getString(), en getLetter()

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 5.1
# Opgave 5.2
# Opgave 5.3
# Opgave 5.4
# Opgave 5.5

###############################################################
# 6 Condities

#=============================================================#
# 6.1 Boolean expressies

#-------------------------------------------------------------#
# 6.1.1 Booleans

# equivalent aan False of True
if 0:
    markeer( '6.1.1' )

    # niet in boek
    falses = False, None, 0, 0.0, 0j, '', (), [], {}, set()
    falses2 = bool(), type(None)(), int(), float(), complex(), str(), tuple(), list(), dict(), set()
    trues = True, 1, 1.0, 'x', (1,), [1], { 1:2 }, { 3 }
    for tst in falses + falses2 + trues:
        if tst:
            print('True', tst, type(tst))
        else:
            print('False', tst, type(tst))

#-------------------------------------------------------------#
# 6.1.2 Vergelijkingen

# <   kleiner dan
# <=  kleiner dan of gelijk aan
# ==  gelijk aan
# >=  groter dan of gelijk aan
# >   groter dan
# !=  niet gelijk aan

if 0:
    markeer( '6.1.2' )

    print( "1.", 2 < 5 )
    print( "2.", 2 <= 5 )
    print( "3.", 3 > 3 )
    print( "4.", 3 >= 3 )
    print( "5.", 3 == 3.0 )
    print( "6.", 3 == "3" )
    print( "7.", "syntax" == "syntax" )
    print( "8.", "syntax" == "semantiek" )
    print( "9.", "syntax" == " syntax" )
    print( "10.", "Python" != "rotzooi" )
    print( "11.", "Python" > "Perl" )
    print( "12.", "banaan" < "mango" )
    print( "13.", "banaan" < "Mango" )

#-------
# Opgave

if 0:
    markeer( '6.1.2 Opgave A' )

    print('int: 3 < 13', 3 < 13)
    print('str: "3" < "13"', "3" < "13")

if 0:
    markeer( '6.1.2 Opgave B' )

    groter = 5 > 2
    print( groter )
    groter = 5 < 2
    print( groter )
    print( type( groter ) )

#-------
# Opgave

if 0:
    markeer( '6.1.2 Opgave C' )

    halfKleiner = 1/2 < 0.5  # False
    halfGelijk = 1/2 == 0.5  # True
    halfGroter = 1/2 > 0.5   # False
    print('1/2 kleiner dan, gelijk aan, groter dan 0.5', halfKleiner, halfGelijk, halfGroter)

    derdeKleiner = 1/3 < 0.33  # False
    derdeGelijk = 1/3 == 0.33  # False
    derdeGroter = 1/3 > 0.33   # True  1/3 = 0.333333...
    print('1/3 kleiner dan, gelijk aan, groter dan 0.33', derdeKleiner, derdeGelijk, derdeGroter)

    eenKleiner = (1/3)*3 < 1  # False
    eenGelijk = (1/3)*3 == 1  # True
    eenGroter = (1/3)*3 > 1   # False
    print('(1/3)*3 kleiner dan, gelijk aan, groter dan 1', eenKleiner, eenGelijk, eenGroter)

if 0:
    markeer( '6.1.2 Opgave D' )

    # Deze code geeft een runtime error.
    print( 3 < "3" )

    # print( 3 == "3" )  # geen error

#-------------------------------------------------------------#
# 6.1.3 in operator

if 0:
    markeer( '6.1.3' )

    print( "y" in "Python" )
    print( "x" in "Python" )
    print( "p" in "Python" )
    print( "th" in "Python" )
    print( "to" in "Python" )
    print( "y" not in "Python" )

#-------
# Opgave

if 0:
    markeer( '6.1.3 Opgave' )

    naam = 'Mijn Naam'
    if 'a' in naam:
      print('a in', naam)
    if 'e' in naam:
      print('e in', naam)
    if 'i' in naam:
      print('i in', naam)
    if 'o' in naam:
      print('o in', naam)
    if 'u' in naam:
      print('u in', naam)

#-------------------------------------------------------------#
# 6.1.4 Logische operatoren

if 0:
    markeer( '6.1.4' )

    t = True          # 1
    f = False         # 0

    print( '  and' )  # input  output
                      #          and
    print( t and t )  #  1  1     1
    print( t and f )  #  1  0     0
    print( f and t )  #  0  1     0
    print( f and f )  #  0  0     0

    print( '  or' )
                      #          or
    print( t or t )   #  1  1     1
    print( t or f )   #  1  0     1
    print( f or t )   #  0  1     1 
    print( f or f )   #  0  0     0

    print( '  not' )
                      #          not
    print( not t )    #    1      0
    print( not f )    #    0      1

    # niet in boek
    print( '  ^ xor' )# xor bij bool
                      #           ^
    print( t ^ t )    #  1  1     0
    print( t ^ f )    #  1  0     1
    print( f ^ t )    #  0  1     1
    print( f ^ f )    #  0  0     0

    print( '  != unequal' ) # zelfde als xor bij bool
                      #   wel verschillende prioriteit precedence
                      #          !=
    print( t != t )   #  1  1     0
    print( t != f )   #  1  0     1
    print( f != t )   #  0  1     1
    print( f != f )   #  0  0     0
    
    print( '  & binary and' ) # zelfde als and bij bool
                      #           &
    print( t & t )    #  1  1     1
    print( t & f )    #  1  0     0
    print( f & t )    #  0  1     0
    print( f & f )    #  0  0     0

    print( '  | binary or' ) # zelfde als or bij bool
                      #           |
    print( t | t )    #  1  1     1
    print( t | f )    #  1  0     1
    print( f | t )    #  0  1     1 
    print( f | f )    #  0  0     0
                      #           ~
    # print( '  ~ binary not' ) # niet als not bij bool
    # geven DeprecationWarning in Python 3.12,
    #   werken niet meer in Python 3.16
    # print( ~ t )      #    1      0
    # print( ~ f )      #    0      1
    
#-------
# Opgave

if 0:
    markeer( '6.1.4 Opgave A' )

    a = False # True of False?
    b = False # True of False?
    c = True  # True of False?

    print( (a and b) or c )  # True
    print( a and (b or c) )  # False

    # andere oplossing
    a = False # True of False?
    b = True  # True of False?
    c = True  # True of False?

    print( (a and b) or c )  # True
    print( a and (b or c) )  # False

if 0:
    markeer( '6.1.4 Opgave B' )

    # van links naar rechts, stopt zodra uitkomst bekend
    x = 1
    y = 0
    print( (x == 0) or (y == 0) or (x / y == 1) )
    # print( (x == 0) or (x / y == 1) )  # geeft ZeroDivisionError: division by zero

    # prin( (x == 0) or (  # geeft NameError, melding print alleen
                           #   problematische regel code
                           #     inclusief commentaar op die regel
    #    x / y == 1) )  # commentaar
#=============================================================#
# 6.2 Conditionele statements

if 0:
    markeer( '6.2' )

    x = 5
    if x == 5:
        print( "x is 5" )

#-------------------------------------------------------------#
# 6.2.1 Blokken code

#-------
# Opgave

if 0:
    markeer( '6.2.1 Opgave A' )

    x = 7
    # x = 10
    # x = 100
    if x < 10:
        print( "Deze regel wordt alleen uitgevoerd als x < 10." )
# commentaar hoeft niet aan inspringen mee te doen
        print( "En dat geldt ook voor deze regel." )
    print( "Deze regel wordt echter altijd uitgevoerd." )

#-------
# Opgave

if 0:
    markeer( '6.2.1 Opgave B' )

    x = 5
    # x = 6
    # x = 4
    if x == 5:
        print( "x is 5" )
    if x > 4:
        print( "x is groter dan 4" )
    if x >= 5:
        print( "x is groter dan of gelijk aan 5" )
    if x < 6:
        print( "x is kleiner dan 6" )
    if x <= 5:
        print( "x is kleiner dan of gelijk aan 5" )
    if x != 6 :
        print( "x is niet 6" )

#-------------------------------------------------------------#
# 6.2.2 Inspringen

# Deze code bevat tabulatie-fouten!
# x = 3
# y = 4
# if x == 3 and y == 4:
#     print( "x is 3" )
#    print( "y is 4" )
# if x > 2 and y < 5:
# print( "x > 2" )
# print( "y < 5" )
# if x < 4 and y > 3:
#     print( "x < 4" )
#         print( "y > 3" )

#-------
# Opgave

if 0:
    markeer( '6.2.2 Opgave' )

    # Deze code bevatte tabulatie-fouten!
    x = 3
    y = 4
    if x == 3 and y == 4:
        print( "x is 3" )
        print( "y is 4" )
    if x > 2 and y < 5:
      print( "x > 2" )  # lelijk maar toegestaan
      print( "y < 5" )
    if x < 4 and y > 3:
        print( "x < 4" )
        print( "y > 3" )
            # commentaar heeft andere indent

#-------------------------------------------------------------#
# 6.2.3 Twee-weg beslissingen

if 0:
    markeer( '6.2.3' )

    x = 4
    if x > 2:
        print( "x is groter dan 2" )
    else:
        print( "x is kleiner dan of gelijk aan 2" )

#-------
# Opgave

if 0:
    markeer( '6.2.3 Opgave A' )

    import pcinput

    val = pcinput.getInteger("Geef een integer:")
    # print(val)
    if val % 2:
        print("getal is oneven")
    else:
        print("getal is even")

if 0:
    markeer( '6.2.3 Opgave B' )

    # Syntactisch correct maar lelijke inspringing.
    x = 1
    if x > 2:
        print( "x is groter dan 2" )
    else:
      print( "x is kleiner dan of gelijk aan 2" )

#-------------------------------------------------------------#
# 6.2.4 Stroomdiagrammen

#-------------------------------------------------------------#
# 6.2.5 Meer-weg beslissingen

if 0:
    markeer( '6.2.5' )

    leeftijd = 21
    if leeftijd < 12:
        print( "Je bent een kind!" )
    elif leeftijd < 18:
        print( "Je bent een teenager!" )
    elif leeftijd < 30:
        print( "Je bent nog jong!" )
    elif leeftijd < 50:
        print( "Beginnen grijze haren te komen?" )
    else:
        print( "Wegen de jaren zwaar?" )

#-------
# Opgave

if 0:
    markeer( '6.2.5 Opgave A' )

    # leeftijd = 21
    # leeftijd = 10
    # leeftijd = 12
    # leeftijd = 35
    leeftijd = 50
    if leeftijd < 12:
        print( "Je bent een kind!" )
    elif leeftijd < 18:
        print( "Je bent een teenager!" )
    elif leeftijd < 30:
        print( "Je bent nog jong!" )
    elif leeftijd < 50:
        print( "Beginnen grijze haren te komen?" )
    else:
        print( "Wegen de jaren zwaar?" )

#-------
# Opgave

if 0:
    markeer( '6.2.5 Opgave B' )

    gewicht = 10
    # gewicht = 30
    # gewicht = 20
    if gewicht > 20:
        print("Er moet een toeslag van $25 betaald worden voor baggage die te zwaar is.")
    elif gewicht < 20:
        print("Goede reis!")
    else:
        print("Poeh! Dat gewicht is precies goed!")

#-------------------------------------------------------------#
# 6.2.6 Geneste condities

#-------
# Opgave

if 0:
    markeer( '6.2.6 Opgave A' )

    x = 41
    # x = 42
    # x = 22
    # x = 77
    if x%7 == 0:
        # --- Hier begint een genest blok code ---
        if x%11 == 0:
            print( x, "is deelbaar door 7 en 11." )
        else:
            print( x, "is deelbaar door 7, maar niet door 11." )
        # --- Hier eindigt een genest blok code ---
    elif x%11 == 0:
        print( x, "is deelbaar door 11, maar niet door 7." )
    else:
        print( x, "is niet deelbaar door 7 of 11." )

if 0:
    markeer( '6.2.6 Opgave B' )

    # elif beter leesbaar dan geneste if else
    leeftijd = 21
    if leeftijd < 12:
        print( "Je bent een kind!" )
    else:
        if leeftijd < 18:
            print( "Je bent een teenager!" )
        else:
            if leeftijd < 30:
                print( "Je bent nog jong!" )
            else:
                if leeftijd < 50:
                    print( "Beginnen grijze haren te komen?" )
                else:
                    print( "Wegen de jaren zwaar?" )

#=============================================================#
# 6.3 Vroegtijdig afbreken

if 0:
    markeer( '6.3 A' )

    from pcinput import getInteger

    num = getInteger( "Geef een positief geheel getal: " )
    if num < 0:
        print( "Je had een positief geheel getal moeten geven!" )
    else:
        print( "Ik handel je getal", num, "af" )
        print( "Nog meer code" )
        print( "Honderden regels code" )

if 0:
    markeer( '6.3 B' )

    from pcinput import getInteger

    # niet in boek
    # gebruik ingebouwde exit() niet
    #   dat is zonder import van sys
    #   sluit idle shell af
    from sys import exit
    # import sys

    num = getInteger( "Geef een positief geheel getal: " )
    if num < 0:
        print( "Je had een positief geheel getal moeten geven!" )
        exit()
        # sys.exit()

    print( "Ik handel je getal", num, "af" )
    print( "Nog meer code" )
    print( "Honderden regels code" )

#=============================================================#
# 6.4 Matchen van cases

if 0:
    markeer( '6.4 A' )

    a = 2
    # a = 5
    # b = 7
    match a: # expressie mogelijk
    # match a * 1:
        case 1: # (2 * 5): # 1 + 0 * 2: # expressie mag niet
            print( "a is 1" )
        case 2:
            print( "a is 2" )
        case 3:
            print( "a is 3" )
        case other:
           print( "a is niet 1, 2, of 3")
        # case b:
        #     print( "a is b", a, b )
        # case iets: # other:  # willekeurige naam niet _
        #     print( "a is niet 1, 2, of 3, is", iets)

if 0:
    markeer( '6.4 B' )

    a = 2
    if a == 1:
        print( "a is 1" )
    elif a == 2:
        print( "a is 2" )
    elif a == 3:
        print( "a is 3" )
    else:
        print( "a is niet 1, 2, of 3" )

if 0:
    markeer( '6.4 C' )

    x = 5
    # x = 0
    y = 3
    # y = 0
    match (x, y): # [x,y]: range(2): # tuple, list, range ok
        case (0,0):
            print( "x en y zijn allebei 0" )
        case (0,_):
            print( "x is 0" )
            # print( "x is 0", _ )# NameError
        # case (0, iets):
        #     print( "x is 0", iets )
        case (_,0):
            print( "y is 0" )
        case other:  # andere naam dan other werkt ook
            print( "noch x noch y is 0, maar", other )

#=============================================================#
# Wat je geleerd hebt

# - Wat boolean expressies zijn
# - Boolean waardes True en False
# - Vergelijkingen met <, <=, ==, >, >=, en !=
# - De in operator
# - Logische operatoren and, or, en not
# - Conditionele statements met if, elif, en else
# - Blokken code
# - Inspringing
# - Geneste condities
# - exit()
# - match case

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 6.1
# Opgave 6.2
# Opgave 6.3
# Opgave 6.4

###############################################################
# 7 Iteraties

#=============================================================#
# 7.1 while loop

if 0:
    markeer( '7.1' )

    from pcinput import getInteger

    num1 = getInteger( "Nummer 1: " )
    num2 = getInteger( "Nummer 2: " )
    num3 = getInteger( "Nummer 3: " )
    num4 = getInteger( "Nummer 4: " )
    num5 = getInteger( "Nummer 5: " )

    print( "Totaal is", num1 + num2 + num3 + num4 + num5 )

#-------------------------------------------------------------#
# 7.1.1 while loop, eerste voorbeeld

if 0:
    markeer( '7.1.1' )

    num = 1
    while num <= 5:
        print( num )
        num += 1
    print( "Klaar" )

#-------
# Opgave

if 0:
    markeer( '7.1.1 Opgave' )

    num = 1
    while num <= 9:  # while num <= 5:
        print( num )
        num += 2     # num += 1
    print( "Klaar" )

#-------------------------------------------------------------#
# 7.1.2 while loop, tweede voorbeeld

if 0:
    markeer( '7.1.2' )

    from pcinput import getInteger

    totaal = 0
    teller = 0
    while teller < 5:
        totaal += getInteger( "Geef een nummer: " )
        teller += 1

    print( "Totaal is", totaal )

#-------
# Opgave

if 0:
    markeer( '7.1.2 Opgave A' )

    from pcinput import getInteger

    totaal = 0
    teller = 0
    while teller < 5:
        totaal += getInteger( "Geef een nummer: " )
        teller += 1
        # print(teller)
    # print( "Totaal is", totaal )
    print( "Totaal is", totaal, "gemiddelde is", totaal / teller )

#-------
# Opgave

if 0:
    markeer( '7.1.2 Opgave B' )

    from pcinput import getInteger

    totaal = 0
    teller = 0
    while teller < 5:
        # totaal += getInteger( "Geef een nummer: " )
        teller += 1
        totaal += getInteger( "Geef nummer " + str(teller) + ": " )
    print( "Totaal is", totaal )

#-------------------------------------------------------------#
# 7.1.3 De while loop onder controle van de gebruiker

if 0:
    markeer( '7.1.3 A' )

    from pcinput import getInteger

    num = -1
    totaal = 0
    while num != 0:
        num = getInteger( "Geef een nummer: " )
        totaal += num
    print( "Totaal is", totaal )

if 0:
    markeer( '7.1.3 B' )

    from pcinput import getInteger

    num = getInteger( "Geef een nummer: " )
    totaal = 0
    while num != 0:
        totaal += num
        num = getInteger( "Geef een nummer: " )
    print( "Totaal is", totaal )

if 0:
    markeer( '7.1.3 C' )

    from pcinput import getInteger

    # kan ook met walrus operator
    #   niet besproken in boek

    totaal = 0
    while num := getInteger( "Geef een nummer: " ):
        totaal += num
    print( "Totaal is", totaal )

if 0:
    markeer( '7.1.3 D' )

    from pcinput import getInteger

    totaal = 0

    # fout: num wordt 0 of 1
    # walrus pas na vergelijking
    # while num := getInteger( "Geef een nummer: " ) != 0:

    # ok
    while (num := getInteger( "Geef een nummer: " )) != 0:
        totaal += num
    print( "Totaal is", totaal )

#-------
# Opgave

if 0:
    markeer( '7.1.3 Opgave' )

    from pcinput import getInteger
    num = -1
    totaal = 0
    teller = 0
    while num != 0:
        num = getInteger( "Geef een nummer: " )
        if num != 0:
            totaal += num
            teller += 1
    if teller == 0:
        print("Lege lijst, geen totaal, geen gemiddelde")
    else:
        print( "Totaal is", totaal, "gemiddelde is", totaal / teller )

#-------------------------------------------------------------#
# 7.1.4 Eindeloze loops

if 0:
    markeer( '7.1.4' )

    nummer = 1
    totaal = 0
    while (nummer * nummer) % 1000 != 0:
        totaal += nummer
    print( "Totaal is", totaal, "nummer", nummer )

#-------
# Opgave

if 0:
    markeer( '7.1.4 Opgave' )

    nummer = 1
    totaal = 0
    while (nummer * nummer) % 1000 != 0:
        totaal += nummer
        nummer += 1  # toegevoegd
    print( "Totaal is", totaal, "nummer", nummer )

#-------------------------------------------------------------#
# 7.1.5 while loop oefeningen

#-------
# Opgave

if 0:
    markeer( '7.1.5 Opgave A' )

    nummer = 10
    while nummer != 0:
        print(nummer)
        nummer -= 1
    print( "Start!")

#-------
# Opgave

if 0:
    markeer( '7.1.5 Opgave B' )

    factor = 5
    # factor = 10
    # factor = 69
    faculteit = 1
    while factor > 1:  # factor != 0:  # > 1 is sneller want is al 1
        faculteit *= factor
        factor -= 1
    print( "Faculteit is", faculteit)

#=============================================================#
# 7.2 for loop

#-------------------------------------------------------------#
# 7.2.1 for loop met strings

if 0:
    markeer( '7.2.1' )

    for letter in "banaan":
        print( letter )
    print( "Klaar" )

#-------------------------------------------------------------#
# 7.2.2 for loop met een variabele collectie

if 0:
    markeer( '7.2.2 A' )

    fruit = "banaan"
    for letter in fruit:
        print( letter )
    print( "Klaar" )

if 0:
    markeer( '7.2.2 B' )

    fruit = "banaan"
    for letter in fruit:
        print( letter )
        if letter == "n":
            fruit = "mango"
    print( "Klaar" )

#-------------------------------------------------------------#
# 7.2.3 for loop met een getallenreeks

if 0:
    markeer( '7.2.3 A' )

    for x in range( 10 ):
        print( x )

if 0:
    markeer( '7.2.3 B' )

    for x in range( 1, 11, 2 ):
        print( x )

#-------
# Opgave

if 0:
    markeer( '7.2.3 Opgave A' )

    print("Loop A")
    for x in range( 1, 11, 2 ):
        print( x )
    print("Loop B")
    for x in range( -1, 11, 2 ):
        print( x )
    print("Loop C")
    for x in range( 1, 1, 2 ):
        print( x )
    print("Loop D")
    for x in range( 1, 0, 2 ):
        print( x )
    print("Loop E")
    for x in range( 11, 1, -2 ):
        print( x )

#-------
# Opgave

if 0:
    markeer( '7.2.3 Opgave B' )

    for x in range( 21, 0, -3 ):
        print( x )

#-------------------------------------------------------------#
# 7.2.4 for loop met een handmatige collectie

if 0:
    markeer( '7.2.4 A' )

    for x in ( 10, 100, 1000, 10000 ):
        print( x )

    if 0: # niet in boek
        test = 83
        for x in 10, 100, 1000, 10000, 25.0, test, "abc":
           print( x )

if 0:
    markeer( '7.2.4 B' )

    for x in ( "appel", "peer", "druif", "banaan", "mango", "kers" ):
        print( x )

#-------------------------------------------------------------#
# 7.2.5 for loop oefeningen

#-------
# Opgave

if 0:
    markeer( '7.2.5 Opgave A' )

    from pcinput import getInteger
    
    totaal = 0
    for teller in range(5):
        totaal += getInteger( "Geef nummer " + str(teller + 1) + ": " )

    print( "Totaal is", totaal )

#-------
# Opgave

if 0:
    markeer( '7.2.5 Opgave B' )

    for nummer in range(10, 0, -1):
        print(nummer)
    print( "Start!")

#-------
# Opgave

if 0:
    markeer( '7.2.5 Opgave C' )

    # aantal keren iteratie in for loop staat gewoonlijk vast, is niet variabel
    # opgave wel mogelijk met break dat is nog niet behandeld

    from pcinput import getInteger

    totaal = 0
    for teller in range(60 * 60 * 24 * 365 * 1000): # elke seconde
                                                 # voor 1000 jaar
        num = getInteger( "Geef een nummer: " )
        if num == 0:
            break  # nog niet besproken
        totaal += num

    if teller == 0:
        print("Lege lijst, geen totaal, geen gemiddelde")
    else:
        print( "Totaal is", totaal, "gemiddelde is", totaal / teller )

#=============================================================#
# 7.3 Loop controle

#-------------------------------------------------------------#
# 7.3.1 else

if 0:
    markeer( '7.3.1 A' )

    i = 0
    while i < 5:
        print( i )
        i += 1
    else:
        print( "De loop eindigt, i is nu", i )
    print( "Klaar" )

if 0:
    markeer( '7.3.1 B' )

    for fruit in ( "appel", "mango", "aardbei" ):
        print( fruit )
    else:
        print( "De loop eindigt, fruit is nu", fruit )
    print( "Klaar" )

#-------------------------------------------------------------#
# 7.3.2 break

if 0:
    markeer( '7.3.2 A' )

    i = 1
    while i <= 1000000:
        num1 = int( "1" + str( i ) )
        num2 = int( str( i ) + "1" )
        if num2 == 3 * num1:
            print( num2, "is drie keer", num1 )
            break
        i += 1
    else:
        print( "Geen antwoord gevonden" )

    print('na de while') # niet in boek

if 0:
    markeer( '7.3.2 B' )

    for cijfer in ( 8, 7.5, 9, 6, 6, 6, 5.5, 7, 5, 8, 7, 7.5 ):
        if cijfer < 5.5:
            print( "De student zakt!" )
            break
    else:
        print( "De student slaagt!" )

#-------
# Opgave

if 0:
    markeer( '7.3.2 Opgave' )

    for cijfer in ( 8, 7.5, 9, 6, 6, 6, 5.5, 7, 5, 8, 7, 7.5 ):
    # for cijfer in ( 8, 7.5, 9, 6, 6, 6, 5.5, 7, 8, 7, 7.5 ):
        if cijfer < 5.5:
            print( "De student zakt!" )
            break
    else:
        print( "De student slaagt!" )

#-------------------------------------------------------------#
# 7.3.3 continue

if 0:
    markeer( '7.3.3 A' )

    num = 0
    while num < 100:
        num += 1
        if num%2 == 0:
            continue
        if num%3 == 0:
            continue
        if num%10 == 7:
            continue
        if num%10 == 9:
            continue
        print( num )

if 0:
    markeer( '7.3.3 B' )

    # niet in boek
    num = 0
    while num < 100:
        num += 1
        if num%2 != 0:
            if num%3 != 0:
                if num%10 != 7:
                    if num%10 != 9:
                        print( num )

if 0:
    markeer( '7.3.3 C' )

    # niet in boek
    num = 0
    while num < 100:
        num += 1
        if num%2 == 0:
            1  # in plaats van pass
        elif num%3 == 0:
            None
        elif num%10 == 7:
            "doet niets"
        elif num%10 == 9:
            pass
        else:
            print( num )

if 0:
    markeer( '7.3.3 D' )

    # niet in boek
    num = 0
    while num < 100:
        num += 1
        if ( num%2 != 0 and
             num%3 != 0 and
             num%10 != 7 and
             num%10 != 9 ):
            print( num )
        else:
            print( num, 'doet niet mee' )

if 0:
    markeer( '7.3.3 E' )

    # niet in boek
    num = 0
    while num < 100:
        num += 1
        if ( num%2 == 0 or
             num%3 == 0 or
             num%10 == 7 or
             num%10 == 9 ):
            print( num, 'doet niet mee' )
        else:
            print( num )

if 0:
    markeer( '7.3.3 F' )

    i = 0
    while i < 10:
        if i == 5:
            continue
        i += 1

#-------
# Opgave

if 0:
    markeer( '7.3.3 Opgave' )

    lijst = 12, 4, 3, 33, -2, -5, 7, 0, 22, 4
    # lijst = 12, 4, 3, 33, -2, -5, 7, 22, 4
    # lijst = 12, 4, 3, 33, -2, -5, 7, 22, 4, 0
    som = 0
    for getal in lijst:
        if getal == 0:
            break # kan ook met return uit loop
                  #   bij break en return wordt else niet gedaan
        if getal < 0:
            continue
        som += getal
    else:
        # lijst uitgeput
        print("Som is", som)
    print("Klaar")

    if 0:
        i = 0
        while i < 5:
            if i == 4: # 5
                print( i, 'break gedaan' )
                break
            i += 1
        else:
            # conditie niet meer waar
            print( i, 'else gedaan' )
        

#=============================================================#
# 7.4 Geneste loops

if 0:
    markeer( '7.4 A' )

    for i in range( 3 ):
        print( "De buitenste loop begint met i =", i )
        # print( "De buitenste loop begint met i =", i, 'j is', j ) # NameError j is not defined
        for j in range( 3 ):
            print( "    De binnenste loop begint met j =", j )
            print( "    (i,j) = ({},{})".format( i, j ) )
            print( "    De binnenste loop eindigt met j =", j )
        print( "De buitenste loop eindigt met i =", i )
        # print( "De buitenste loop eindigt met i =", i, 'j is', j )

if 0:
    markeer( '7.4 B' )

    for i in range( 4 ):
        for j in range( i+1, 4 ):
            print( "({},{})".format( i, j ) )

    if 0:
        for test in range( 2, 2 ):  # loop range met begin en einde hetzelfde
            print( 'test', test )   #   print niets

#-------
# Opgave

if 0:
    markeer( '7.4 Opgave A' )

    for i in range( 4 ):
        for j in range( 4 ):
            if i != j:
                print( "({},{})".format( i, j ) )

if 0:
    markeer( '7.4 Opgave B' )

    for i in range( 3 ):
        for j in range( 3 ):
            for k in range( 3 ):
                print( "({},{},{})".format( i, j, k ) )

#=============================================================#
# 7.5 De loop-en-een-half

if 0:
    markeer( '7.5' )

    from pcinput import getInteger

    x = 3
    y = 7

    while (x != 0) and (y != 0) and (x%y != 0) and (y%x != 0):
        x = getInteger( "Geef nummer 1: " )
        y = getInteger( "Geef nummer 2: " )
        if (x > 1000) or (y > 1000) or (x < 0) or (y < 0):
            print( "Nummers moeten tussen 0 en 1000 zijn" )
            continue
        print( x, "keer", y, "is", x * y )

    if x == 0 or y == 0:
        print( "Klaar!" )
    else:
        print( "Fout: de nummers mogen geen delers zijn" )

#-------
# Opgave

# wat ook nog minder goed:
#
# haakjes om condities in while en eerste if zijn overbodig
#   want overal alleen and of alleen or
#   niet consistent met tweede if waar geen haakjes om condities staan
#
# in Python compactere conditie mogelijk 0 < x < 1000
#
# conditie beter toevoegen aan getInteger functie
#   is wel vaker nodig komt later aan bod
#     in volgende hoofdstuk bij functies

if 0:
    markeer( '7.5 Opgave A' )

    from pcinput import getInteger

    x = getInteger( "Geef nummer 1: " )
    y = getInteger( "Geef nummer 2: " )

    while (x != 0) and (y != 0) and (x%y != 0) and (y%x != 0):
        if (x > 1000) or (y > 1000) or (x < 0) or (y < 0):
            print( "Nummers moeten tussen 0 en 1000 zijn" )
            x = getInteger( "Geef nummer 1: " )
            y = getInteger( "Geef nummer 2: " )
            continue
        print( x, "keer", y, "is", x * y )
        x = getInteger( "Geef nummer 1: " )
        y = getInteger( "Geef nummer 2: " )

    if x == 0 or y == 0:
        print( "Klaar!" )
    else:
        print( "Fout: de nummers mogen geen delers zijn" )

if 0:
    markeer( '7.5 Opgave B' )

    from pcinput import getInteger
    from sys import exit

    while True:
        x = getInteger( "Geef nummer 1: " )
        if x == 0:
            break
        y = getInteger( "Geef nummer 2: " )
        if y == 0:
            break
        if (x < 0 or x > 1000) or (y < 0 or y > 1000):
            print( "Nummers moeten tussen 0 en 1000 zijn" )
            continue
        if x%y == 0 or y%x == 0:
            print( "Fout: de nummers mogen geen delers zijn" )
            exit()
        print( x, "keer", y, "is", x * y )

    print( "Klaar!" )

#-------
# Opgave

if 0:
    markeer( '7.5 Opgave C' )

    # opgave onduidelijk wat invoer 0 moet doen
    #   0 is niet negatief en ook niet positief
    from pcinput import getInteger

    while True:
        getal = getInteger( "Geef positief getal:" )
        if getal >= 0:
            break
        print( "Negatief getal niet toegestaan" )
    print(getal)

#=============================================================#
# 7.6 Slim gebruik van loops

# strategieën voor ontwerpen van loops

#-------------------------------------------------------------#
# 7.6.1 Wanneer gebruik je een loop

# bij herhalende code met kleine wijzigingen
# wat als herhaling vele malen meer nodig zou zijn

# TESTS = 10_000 naar 1_000_000 voor voldoende nauwkeurigheid

if 0:
    markeer( '7.6.1 A' )

    from random import randint

    TESTS = 10000
    succes = 0
    for i in range( TESTS ):
        d1 = randint( 1, 6 )
        d2 = randint( 1, 6 )
        d3 = randint( 1, 6 )
        d4 = randint( 1, 6 )
        d5 = randint( 1, 6 )
        if d1 == 6 and d2 == 6 and d3 == 6 and d4 == 6 and d5 == 6:
            succes += 1
    print( "Waarschijnlijkheid van vijf zessen is", succes / TESTS )

if 0:
    markeer( '7.6.1 B' )

    from random import randint

    for i in range( 5 ):
        d = randint( 1, 6 )

# met for loop korter, met if sneller

if 0:
    markeer( '7.6.1 C' )

    from random import randint

    TESTS = 10000
    succes = 0
    for i in range( TESTS ):
        # allemaalzes = True  # kan ook vlag gebruiken in plaats van else
        for j in range( 5 ):
            if randint( 1, 6 ) != 6:
                # allemaalzes = False
                break
        else:
        # if allemaalzes:
            succes += 1
    print( "Waarschijnlijkheid van vijf zessen is", succes / TESTS )

#-------------------------------------------------------------#
# 7.6.2 Data items één voor één verwerken

# vaak voldoende om tijdens herhaling bij te houden
#     waarde tot nu toe
#   bijvoorbeeld voor min, max, deelbaar door 3
#     bekijken waarmee beginnen
#   niet altijd mogelijk

#-------------------------------------------------------------#
# 7.6.3 Begin met de kleinste en bouw naar buiten op

# woorden in boek

# woordtotaal = 0
# for woord in boek:
#     woordtotaal += 1

# woorden in boek, plank

# for boek in plank:
#     verwerk_boek()

# for boek in plank:
#     woordtotaal = 0
#     for woord in boek:
#         woordtotaal += 1

# woordtotaal = 0
# for boek in plank:
#     for woord in boek:
#         woordtotaal += 1

# gemiddeld aantal woorden in boek

# woordtotaal = 0
# boektotaal = 0
# for boek in plank:
#     for woord in boek:
#         woordtotaal += 1
#     boektotaal += 1
# print( woordtotaal / boektotaal )

# gemiddeld aantal woorden in boek, bibliotheek

# woordtotaal = 0
# boektotaal = 0
# for plank in bibliotheek:
#     for boek in plank:
#         for woord in boek:
#             woordtotaal += 1
#         boektotaal += 1
# print( woordtotaal / boektotaal )

#=============================================================#
# 7.7 Over het ontwerpen van algoritmes

# niet meteen beginnen met typen
#   weet nog niet hoe opgave moet oplossen
# eerst stapje terug
#   hoe als mens aanpakken
# dan
#   hoe in code opschrijven

#=============================================================#
# Wat je geleerd hebt

# - Wat loops zijn
# - while loops
# - for loops
# - Eindeloze loops
# - Loop controle via else, break, en continue
# - Geneste loops
# - De loop-en-een-half
# - Slim zijn met loops

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 7.1
# Opgave 7.2
# Opgave 7.3
# Opgave 7.4
# Opgave 7.5
# Opgave 7.6
# Opgave 7.7
# Opgave 7.8
# Opgave 7.9
# Opgave 7.10
# Opgave 7.11
# Opgave 7.12
# Opgave 7.13
# Opgave 7.14
# Opgave 7.15
# Opgave 7.16

###############################################################
# 8 Functies

#=============================================================#
# 8.1 Het nut van functies

# encapsulatie
# generalisatie
# beheersbaarheid
# onderhoudbaarheid
# herbruikbaarheid
# recursie

#=============================================================#
# 8.2 Het creëren van functies

# definiëert functie
#   wordt nog niet uitgevoerd

# def <functie_naam>( <parameter_lijst> ):
#     <acties>

#-------------------------------------------------------------#
# 8.2.1 Hoe Python omgaat met functies

# eerst Hallo, dan Tot ziens

if 0:
    markeer( '8.2.1' )

    def totZiens():  # definitie voor aanroep
        print( "Tot ziens!" )
        # printX( "Tot ziens!" )

    print( "Hallo!" )
    totZiens()

    # def totZiens():  # definitie na aanroep werkt niet
    #     print( "Tot ziens!" )

#-------------------------------------------------------------#
# 8.2.2 Parameters en argumenten

# parameter is naam
# argument is "Groucho", daarna "Chico", ...

if 0:
    markeer( '8.2.2 A' )

    def hallo( naam ):
        # length = len(naam)  # dan geen int of float naam mogelijk
        print( "Hallo, {}!".format( naam ) )

    hallo( "Groucho" )
    hallo( "Chico" )
    hallo( "Harpo" )
    hallo( "Zeppo" )
    # hallo( 123 ) # kan ook wel maar onbedoeld ok

# parameters zijn x en y
# argumenten zijn 2020 en 5278238, daarna 2 en 3

if 0:
    markeer( '8.2.2 B' )

    def vermenigvuldig( x, y ):
        resultaat = x * y
        print( resultaat )

    vermenigvuldig( 2020, 5278238 )
    vermenigvuldig( 2, 3 )

#-------------------------------------------------------------#
# 8.2.3 Parameter types

# controleren met isinstance

if 0:
    markeer( '8.2.3' )

    a = "Hallo"
    # a = 10
    # a = 12.34
    # a = True
    # a = None
    
    if isinstance( a, int ):
        print( "integer" )
    elif isinstance( a, float ):
        print( "float" )
    elif isinstance( a, str ):
        print( "string" )
    else:
        print( "anders" )
        

#-------------------------------------------------------------#
# 8.2.4 Default parameter waardes

# let op volgorde

if 0:
    markeer( '8.2.4' )

    def vermenigvuldig_xyz( x, y=3, z=7 ):
        print( x * y * z )

    vermenigvuldig_xyz( 2, 2, 2 ) # x=2, y=2, z=2
    vermenigvuldig_xyz( 2, 5 )    # x=2, y=5, z=7
    vermenigvuldig_xyz( 2, z=5 )  # x=2, y=3, z=5
    # vermenigvuldig_xyz( z=5, 2 )  # x=2, y=3, z=5 # fout Positional argument follows Keyword argument

#-------------------------------------------------------------#
# 8.2.5 return

if 0:
    markeer( '8.2.5 A' )

    from math import sqrt

    def pythagoras( a, b ):
        return sqrt( a*a + b*b )

    c = pythagoras( 3, 4 )
    print( c )

if 0:
    markeer( '8.2.5 B' )

    from math import sqrt

    def pythagoras( a, b ):
        if a <= 0 or b <= 0:
            return
        return sqrt( a*a + b*b )

    print( pythagoras( 3, 4 ) )
    print( pythagoras( -3, 4 ) )

if 0:
    markeer( '8.2.5 C' )

    from math import sqrt

    def pythagoras( a, b ):
        if a > 0 and b > 0:
            return sqrt( a*a + b*b )
        # als return niet aanwezig dan impliciet
        #    geeft None terug
        # return  # return zonder expressie
        #    geeft ook None terug

    print( pythagoras( 3, 4 ) )
    print( pythagoras( -3, 4 ) )

if 0:
    markeer( '8.2.5 D' )

    from math import sqrt
    from pcinput import getInteger

    def pythagoras( a, b ):
        if a <= 0 or b <= 0:
            return -1
            # return
        return sqrt( a*a + b*b )

    num1 = getInteger( "Geef zijde 1: " )
    num2 = getInteger( "Geef zijde 2: " )
    num3 = pythagoras( num1, num2 )
    if num3 < 0: # == None:
        print( "De getallen kunnen niet worden gebruikt." )
    else:
        print( "De lengte van de diagonaal is", num3 )

if 0:
    markeer( '8.2.5 E' )

    from math import sqrt

    def pythagoras( a, b ):
        if a <= 0 or b <= 0:
            return -1
            print( "Deze regel wordt nooit uitgevoerd" )
            # print( iets, "Deze regel wordt nooit uitgevoerd" )
        return sqrt( a*a + b*b )

    print(pythagoras(-3, 4))

#-------------------------------------------------------------#
# 8.2.6 Het verschil tussen return en print

if 0:
    markeer( '8.2.6 A' )

    def print3():
       print( 3 )
    print3()

if 0:
    markeer( '8.2.6 B' )

    def return3():
       return 3
    print( return3() )

if 0:
    markeer( '8.2.6 C' )

    def return3():
       return 3
    x = 2 ** return3()

    print( x )
    # x
    # f = print
    # f(x, 'andere print')
    # return3
    # ()

if 0:
    markeer( '8.2.6 D' )

    def print3():
        print( 3 )
    x = 2 ** print3() # Runtime error!
    print( x )

#-------------------------------------------------------------#
# 8.2.7 De functie machine

# functie beschouwen als machine
#   zoals om pannenkoeken te bakken
# input argumenten
#   zoals melk, eieren, meel
#  juiste ingrediënt nodig, type
#  juiste hoeveelheid nodig, waarde
# output resultaat
#  pannenkoek, return
# display van machine
#  boodschappen, print
# hoef niet te weten hoe intern werkt

#-------------------------------------------------------------#
# 8.2.8 Meerdere retourwaardes

if 0:
    markeer( '8.2.8' )

    import datetime

    def plusDagen( jaar, maand, dag, increment ):
        startdatum = datetime.datetime( jaar, maand, dag )
        einddatum = startdatum + datetime.timedelta( days=increment )
        return einddatum.year, einddatum.month, einddatum.day

    y, m, d = plusDagen( 2015, 11, 13, 55 )
    # tup = plusDagen( 2015, 11, 13, 55 )
    # print(tup)
    # y, m, d = tup
    print( "{}/{}/{}".format( y, m, d ) )

#-------------------------------------------------------------#
# 8.2.9 Functies aanroepen vanuit functies

if 0:
    markeer( '8.2.9 A' )

    from math import sqrt

    def pythagoras( a, b ):
        if a <= 0 or b <= 0:
            return -1
        return sqrt( a*a + b*b )

    # print( afstand( 1, 1, 4, 5 ) )

    def afstand( x1, y1, x2, y2 ):
        return pythagoras( abs( x1 - x2 ), abs( y1 - y2 ) )

    print( afstand( 1, 1, 4, 5 ) )
    # print( afstand( -1, -1, -4, -5 ) )
    # print( afstand( 0, 0, 0, 0 ) )

if 0:
    markeer( '8.2.9 B' )

    from math import sqrt

    def afstand( x1, y1, x2, y2 ):

        def pythagoras_binnen( a, b ):
            # print(x1)
            if a <= 0 or b <= 0:
                return -1
            return sqrt( a*a + b*b )

        return pythagoras_binnen( abs( x1 - x2 ), abs( y1 - y2 ) )

    print( afstand( 1, 1, 4, 5 ) )
    # print( pythagoras_binnen( 3, 4 ) )

#-------
# Opgave

if 0:
    markeer( '8.2.9 Opgave' )

    # def main():
    #     meerderex(4)
        
    def printx():
        print('x')
        # print('x', end='')

    def meerderex(aantal):
        # print(aantal)
        for i in range(aantal):
            printx()

    meerderex(7)
    # main()

#-------------------------------------------------------------#
# 8.2.10 Functienamen

#-------
# Opgave

if 0:
    markeer( '8.2.10 Opgave A' )

    def isEven(getal):
        return getal % 2 == 0

    for i in range(20):
        print(i, isEven(i))

#-------
# Opgave

if 0:
    markeer( '8.2.10 Opgave B' )

    def isEven(getal):
        return getal % 2 == 0

    def isOneven(getal):
        return not isEven(getal)

    for i in range(20):
        print(i, isOneven(i))

#-------
# Opgave

if 0:
    markeer( '8.2.10 Opgave B' )

    def getFractie(getal):
        # "doc string enkele regel"  # niet in boek
        return getal - int(getal)

    def getFractie2(getal):
#        """returnt fractie
# met meerdere regels
# nog een regel
# """
        return 0 - int(getal) + getal

    for i in range(20):
        val = i / 7.0
        print(val, getFractie(val))
        print(-val, getFractie(-val))

    # help( getFractie )
    # print( getFractie.__doc__ )

    # help( getFractie2 )
    # print( getFractie2.__doc__ )

#-------------------------------------------------------------#
# 8.2.11 Functie commentaar

#-------------------------------------------------------------#
# 8.2.12 Type hints voor functies

if 0:
    markeer( '8.2.12' )

    from math import sqrt

    def pythagoras( a: int, b: int ) -> float:
        if a <= 0 or b <= 0:
            return -1
        return sqrt( a*a + b*b )

    print( pythagoras( 3, 4 ) )
    # print( pythagoras( -3, 4 ) )
    # print( pythagoras( 3.5, 4 ) )

#=============================================================#
# 8.3 Scope en levensduur

#-------------------------------------------------------------#
# 8.3.1 Scope van variabelen

if 0:
    markeer( '8.3.1 A' )

    hallo = "Hallo!"
    totziens = "Tot ziens!"

    for i in range( 3 ):
        for j in range( 2 ):
            middag = "Goedemiddag"
            print( totziens )
        print( j )
        print( hallo )
        print( middag )

    print( i )
    print( j )
    print( middag )

if 0:
    markeer( '8.3.1 B' )

    dubbeltje = "dubbeltje"

    def geboren():
        print( "Als je voor een", dubbeltje, "geboren wordt,",
            "dan wordt je nooit een", kwartje )

    kwartje = "kwartje"
    geboren()
    print( "dubbeltje =", dubbeltje, "en kwartje =", kwartje )

if 0:
    markeer( '8.3.1 C' )

    dubbeltje = "dubbeltje"

    def geboren():
        kwartje = "stuiver"
        print( "Als je voor een", dubbeltje, "geboren wordt,",
            "dan wordt je nooit een", kwartje )

    kwartje = "kwartje"
    geboren()
    print( "dubbeltje =", dubbeltje, "en kwartje =", kwartje )

if 0:
    markeer( '8.3.1 D' )

    appel = "appel"
    banaan = "banaan"
    kers = "kers"
    doerian = "doerian"

    def printfruit_1():
        print( appel, banaan )

    def printfruit_2( appel ):
        banaan = kers
        print( appel, banaan )

    def printfruit_3( appel, banaan ):
        kers = "mango"
        banaan = kers
        print( appel, banaan )

    printfruit_1()
    printfruit_2( kers )
    printfruit_3( kers, doerian )

    # printfruit_2( doerian )

    print( "appel =", appel )
    print( "banaan =", banaan )
    print( "kers =", kers )
    print( "doerian =", doerian )

#-------------------------------------------------------------#
# 8.3.2 Globale variabelen

if 0:
    markeer( '8.3.2' )

    fruit = "appel"
    # global fruit
    def wijzigFruit():
        global fruit
        # print(fruit)
        # if False:
        #    fruit = "banaan"
        fruit = "banaan"
        # print(fruit)

    print( fruit )
    wijzigFruit()
    print( fruit )

# zie ook:
# Namespaces in Python
# https://realpython.com/python-namespace/

#=============================================================#
# 8.4 Grip krijgen op complexiteit

if 0:
    markeer( '8.4 A' )

    # Eerst krijgen de variabelen in groep 1 (num11, num12, num13) en
    # groep 2 (num21, num22, num23) een waarde.

    # geen voorbeeld waardes in boek
    num11, num12, num13 = 5, 33, -10
    num21, num22, num23 = 13,0, -100

    kleinste1 = 0
    kleinste2 = 0
    middelste1 = 0
    middelste2 = 0
    grootste1 = 0
    grootste2 = 0

    if num11 < num12:
        if num11 < num13:
            kleinste1 = num11
        else:
            kleinste1 = num13
    elif num12 < num13:
        kleinste1 = num12
    else:
        kleinste1 = num13

    print( kleinste1 ) # Test

    # Deze code zoekt de kleinste van groep 1.
    # Daarna doe je hetzelfde voor de kleinste van groep 2.
    # Dan moet je ook zoiets doen voor de grootste van de 2 groepen.
    # Daarna moet je nog iets verzinnen voor de middelste.
    # Tenslotte doe je alle optellingen en drukt de resultaten af.

if 0:
    markeer( '8.4 B' )

    num11, num12, num13 = 436, 178, 992
    num21, num22, num23 = 880, 543, 101

    def kleinste( n1, n2, n3 ):
        return n1 # Geef iets terug

    def middelste( n1, n2, n3 ):
        return n1 # Geef iets terug

    def grootste( n1, n2, n3 ):
        return n1 # Geef iets terug

    print( "som van kleinste =", kleinste( num11, num12, num13 ) +
        kleinste( num21, num22, num23 ) )
    print( "som van middelste =", middelste( num11, num12, num13 ) +
        middelste( num21, num22, num23 ) )
    print( "som van grootste =", grootste( num11, num12, num13 ) +
        grootste( num21, num22, num23 ) )

if 0:
    markeer( '8.4 C' )

    def kleinste_van_twee( n1, n2 ):
        if n1 < n2:
            return n1
        return n2

if 0:
    markeer( '8.4 D' )

    num11, num12, num13 = 436, 178, 992
    num21, num22, num23 = 880, 543, 101

    def kleinste_van_twee( n1, n2 ):
        if n1 < n2:
            return n1
        return n2

    def grootste_van_twee( n1, n2 ):
        if n1 > n2:
            return n1
        return n2

    def kleinste( n1, n2, n3 ):
        return kleinste_van_twee( kleinste_van_twee( n1, n2 ), n3 )

    def middelste( n1, n2, n3 ):
        return n1 # geef iets terug

    def grootste( n1, n2, n3 ):
        return grootste_van_twee( grootste_van_twee( n1, n2 ), n3 )

    print( "som van kleinste =", kleinste( num11, num12, num13 ) +
        kleinste( num21, num22, num23 ) )
    print( "som van middelste =", middelste( num11, num12, num13 ) +
        middelste( num21, num22, num23 ) )
    print( "som van grootste =", grootste( num11, num12, num13 ) +
        grootste( num21, num22, num23 ) )

if 0:
    markeer( '8.4 E' )

    num11, num12, num13 = 436, 178, 992
    num21, num22, num23 = 880, 543, 101

    def kleinste_van_twee( n1, n2 ):
        if n1 < n2:
            return n1
        return n2

    def grootste_van_twee( n1, n2 ):
        if n1 > n2:
            return n1
        return n2

    def kleinste( n1, n2, n3 ):
        return kleinste_van_twee( kleinste_van_twee( n1, n2 ), n3 )

    def middelste( n1, n2, n3 ):
        return n1 + n2 + n3 - kleinste( n1, n2, n3 ) \
            - grootste( n1, n2, n3 )

    def grootste( n1, n2, n3 ):
        return grootste_van_twee( grootste_van_twee( n1, n2 ), n3 )

    print( "som van kleinste =", kleinste( num11, num12, num13 ) +
        kleinste( num21, num22, num23 ) )
    print( "som van middelste =", middelste( num11, num12, num13 ) +
        middelste( num21, num22, num23 ) )
    print( "som van grootste =", grootste( num11, num12, num13 ) +
        grootste( num21, num22, num23 ) )

#=============================================================#
# 8.5 Modules

# zie ook 5.3 Modules

# niet in boek

if 0:
    markeer( '8.5 A' )

    import pcinput

    waarde = pcinput.getInteger('geef getal: ')
    print( waarde, type( waarde ) )

if 0:
    markeer( '8.5 B' )

    import pcinput as inp

    waarde = inp.getInteger('geef getal: ')
    print( waarde, type( waarde ) )

    # waarde = pcinput.getInteger('geef getal: ')
    # print( waarde, type( waarde ) )

if 0:
    markeer( '8.5 C' )

    import pcinput
    inp = pcinput
    # del pcinput

    waarde = inp.getInteger('geef getal: ')
    print( waarde, type( waarde ) )

    waarde = pcinput.getInteger('geef getal: ')
    print( waarde, type( waarde ) )

if 0:
    markeer( '8.5 D' )

    from pcinput import getInteger
    # from pcinput import *

    waarde = getInteger('geef getal: ')
    print( waarde, type( waarde ) )
    
if 0:
    markeer( '8.5 E' )

    from pcinput import getInteger as get

    waarde = get('geef getal: ')
    print( waarde, type( waarde ) )

# zie ook:
# Python Modules and Packages – An Introduction
# https://realpython.com/python-modules-packages/

#-------------------------------------------------------------#
# 8.5.1 main()

if 0:
    markeer( '8.5.1 A' )

    def main():
        # code...

        # niet in boek
        pass

        # print( 'in main' )

        # while True:
        #    print('in loop')

    if __name__ == '__main__':
        main()

if 0:
    # niet in boek

    markeer( '8.5.1 B' )

    print( __name__ )

if 0:
    # niet in boek

    markeer( '8.5.1 C' )

    testImportCode = """
print( 'in testimport', __name__ )

input( 'testimport input1, druk op Enter' )

import ProgrammeursleerlingExtra

input( 'testimport input2, druk op Enter' )
"""
    with open( "testimport.py", "w" ) as outf:
        outf.write( testImportCode )

if 0:
    # niet in boek

    markeer( '8.5.1 D' )

    print( 'in extra', __name__ )

    import testimport

if 0:
    # niet in boek

    markeer( '8.5.1 E' )

    print( 'in extra', __name__ )

    if __name__ == '__main__':
    # if 1:  # eindeloze recursie
        import os
        os.system( "py.exe testimport.py" )

if 0:
    markeer( '8.5.1 F' )

    def isEven( num ):
        return num%2 == 0

    if __name__ == '__main__':
        for i in range( 10 ):
            if isEven( i ):
                print( i, "is even" )

# zie ook:
# Defining Main Functions in Python
# https://realpython.com/python-main-function/

#=============================================================#
# 8.6 Anonieme functies

if 0:
    markeer( '8.6 A' )

    f = lambda x: x*x # + x*x
    print( f(12) )

if 0:
    markeer( '8.6 B' )

    def f( x ):
        return x*x # + x*x
    print( f(12) )

if 0:
    # niet in boek
    markeer( '8.6 C' )

    print( lambda x: x*x )         # function
    print( lambda x: x*x (12) )    # function
    print( ( lambda x: x*x )(12) ) # 144

# zie ook:
# How to Use Python Lambda Functions
# https://realpython.com/python-lambda/

#=============================================================#
# Wat je geleerd hebt

# - Het nut van functies
# - Functies creëren
# - Parameters en argumenten
# - Waardes uit functies retourneren middels return
# - Functie benamingen
# - Commentaar in functies
# - Type hints voor functies
# - Variabele scope en levensduur
# - Lokale en globale variabelen
# - Het gebruik van functies om grip te krijgen op complexiteit
# - Modules
# - Gebruik van een main() functie
# - Anonieme functies

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 8.1
# Opgave 8.2
# Opgave 8.3
# Opgave 8.4
# Opgave 8.5
# Opgave 8.6
# Opgave 8.7

###############################################################
# 9 Recursie

#=============================================================#
# 9.1 Wat is recursie?

# functie roept zichzelf aan
# gevaar eindeloze loop
#   functie ontwerpen zodat niet gebeurt
# voor veel problemen elegante oplossing
# beperkingen zoals maximale diepte,
#   geheugen gebruik, snelheid

#=============================================================#
# 9.2 Recursieve definities

if 0:
    markeer( '9.2' )

    def faculteit( n ):
        if n <= 1:
            return 1
        return n * faculteit( n-1 )
    print( faculteit( 5 ) )

# aanroep faculteit( 5 )
#     aanroep faculteit( 4 )
#         aanroep faculteit( 3 )
#             aanroep faculteit( 2 )
#                 aanroep faculteit( 1 )
#                     return 1
#                 return 2 * 1
#             return 3 * 2
#         return 4 * 6
#     return 5 * 24
# print( 120 )

#-------------------------------------------------------------#
# 9.2.1 Wanneer gebruik je recursie

# iteratief vaak beter dan recursief
#   zoals faculteit
# recursie voor sommige problemen beter
#   als niet te diep en snel genoeg 

#-------------------------------------------------------------#
# 9.2.2 Doolhof doorzoeken

# functie leidt_naar_uitgang( huidigecel ):
#     if (huidigecel is de uitgang):
#         return (pad dat alleen de uitgang bevat)
#     for (iedere verbondencel die nog niet onderzocht is):
#         pad = leidt_naar_uitgang( verbondencel )
#         if (pad is niet leeg):
#             voeg huidigecel toe aan pad
#             return pad
#     return (leeg pad)

if 0:
    markeer( '9.2.2 A' )

    # doolhof doorzoeken met pad print
    # exit wordt paars in IDLE
    # verwarrend naam exit ook in sys en site modules
    from pcmaze import entrance, exit, connected

    def leidt_naar_uitgang( komtvan, cel ):
        if cel == exit():
            return True
        for i in range( entrance(), exit()+1 ):
            if i == komtvan:
                continue
            if not connected( cel, i ):
                continue
            if leidt_naar_uitgang( cel, i ):
                print( cel, "->", i )
                return True
        return False

    if leidt_naar_uitgang( 0, entrance() ):
        print( "Pad gevonden!" )
    else:
        print( "Pad niet gevonden" )

if 0:
    markeer( '9.2.2 B' )

    # doolhof doorzoeken met indent info
    from pcmaze import entrance, exit, connected

    def leidt_naar_uitgang( komtvan, cel, diepte ):
        inspringing = diepte * 4 * " "
        if cel == exit():
            return True
        for i in range( entrance(), exit()+1 ):
            if i == komtvan:
                continue
            if not connected( cel, i ):
                continue
            print( inspringing + "Controleer", cel, "->", i )
            if leidt_naar_uitgang( cel, i, diepte + 1 ):
                print( inspringing + "Pad gevonden:", cel, "->", i )
                return True
        return False

    if leidt_naar_uitgang( 0, entrance(), 0 ):
        print( "Pad gevonden!" )
    else:
        print( "Pad niet gevonden" )

#-------------------------------------------------------------#
# 9.2.3 Retourwaardes van recursieve functies

if 0:
    markeer( '9.2.3' )

    # doolhof doorzoeken met pad return
    from pcmaze import entrance, exit, connected

    def leidt_naar_uitgang( komtvan, cel ):
        if cel == exit():
            return "{}".format( exit() )
        for i in range( entrance(), exit()+1 ):
            if i == komtvan:
                continue
            if not connected( cel, i ):
                continue
            check = leidt_naar_uitgang( cel, i )
            if check != "":
                return "{} -> {}".format( cel, check )
        return ""

    check = leidt_naar_uitgang( 0, entrance() )
    if check != "":
        print( "Pad gevonden!", check )
    else:
        print( "Pad niet gevonden" )

#=============================================================#
# Wat je geleerd hebt

# - Recursieve functies
# - Wanneer je recursieve functies gebruikt (en wanneer niet)
# - Het doel van recursieve functies
# - Retourwaardes van recursieve functies

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 9.1
# Opgave 9.2
# Opgave 9.3
# Opgave 9.4
# Opgave 9.5
# Opgave 9.6
#   Torens van Hanoi

# niet in boek

# bedrag in munten
#   uitbreiding van opgave 4.3
#     berekent en print alle mogelijkheden
# in bestand munten.py

# floodfill
#   vervang verbonden pixels met waarde
#     vanaf gekozen punt met nieuwe waarde
# in bestand floodfill.py

###############################################################
# 10 Strings

#=============================================================#
# 10.1 Herhaling

if 0:
    markeer( '10.1 A' )

    # enkele en dubbele aanhalingstekens
    # + en * operatoren
    s1 = "appel"
    s2 = 'banaan'
    print( s1 )
    print( s2 )
    print( s1 + s2 )
    print( 3 * s1 )
    print( s2 * 3 )
    print( 2 * s1 + 2 * s2 )

# in hoofdstuk 5 Eenvoudige Functies
# format()
# len() lengte, kan 0 zijn lege string

# in hoofdstuk 6 Condities
# strings vergelijken
#   alfabetische volgorde ASCII -> Unicode
#   verschil hoofdletters en kleine letters
# in operator

if 0:
    markeer( '10.1 B' )

    s1 = "mango"
    s2 = "banaan"
    for letter in s1:
        if letter in s2:
            print( s1, "en", s2, "bevatten beide de letter", letter )

#=============================================================#
# 10.2 Strings over meerdere regels

if 0:
    markeer( '10.2 A' )

    # met \ aan einde regel
    langestring = "Ik heb mijn buik vol van te worden behandeld \
als een schaap. Wat is de zin van op vakantie gaan als je \
gewoon maar een toerist bent die wordt rondgereden in een bus \
omringd door zweterige uilskuikens uit Den Haag en Rotterdam \
met hun honkbalpetjes en trainingspakken en radio' s en \
De Telegraaf, klagend over de koffie - 'Oh, ze zetten geen \
lekker bakje hier, toch, niet zoals thuis' - en dan stoppen \
bij Mallorcaanse eettentjes waar ze friet en kroket verkopen \
en Heineken en calamaris met salade en ze zitten in hun \
katoenen overgooiers Nivea zonnebrand te spuiten over hun \
pafferige rauwe opgezwollen uitpuilende blubberbuiken 'omdat \
ze op de eerste dag wat teveel zon hebben gehad.' "
    print( langestring )

if 0:
    markeer( '10.2 B' )

    # met drievoudige aanhalingstekens
    langestring = """En je wordt onderworpen aan een eindeloze stroom
Hotel Miramars en Bellevues en Continentaals met hun moderne luxe
internationale studio' s en Heineken van de tap en zwembaden vol
vette Duitse zakenlui die denken dat ze acrobaten zijn en
pyramides vormen en de kinderen bang maken en voordringen in de
rij en als je niet aan tafel zit om klokslag zeven dan mis je je
kop Knorr Romige Tomatensoep, het eerste item op het menu van de
Internationale Cuisine, en iedere donderdagavond is er een
amateuristisch theater in de bar, met een kleine verwijfde
Spanjool met smalle heupjes en een opblazen taart met haar haar
platgeboterd en een enorm achterwerk die Flamenco voor
Buitenlanders presenteren."""
    print( langestring )

if 0:
    markeer( '10.2 C' )

    # met \n\ aan einde voor nieuwe regel
    langestring = "En een paar nasale secretaresses uit Middelburg\n\
met flubberende witte benen en buikloop die flirten met harige\n\
krombenige Spaanse obers genaamd Manuel en eens per week is er\n\
een excursie naar de plaatselijke Romeinse bouwval waar je\n\
cola kunt kopen en gesmolten ijsjes en natuurlijk Heineken en\n\
op een avond bezoek je het zogenaamde typische restaurant met\n\
rustieke uitstraling en plaatselijke atmosfeer en je zit naast\n\
een groepje toeristen uit Amstelveen die maar blijven zingen\n\
'Torremolinos, torremolinos' en klagen over het eten - 'Het is\n\
erg vettig hier, vind je niet ?' - en je wordt in een hoek\n\
gedreven door een dronken groentenboer uit Hilversum met zijn\n\
instant fototoestel en plastic sandalen en het Algemeen\n\
Dagblad van afgelopen dinsdag en hij zaagt maar door en door\n\
en door over hoe meneer Jansen dit land moet besturen en\n\
hoeveel talen Frits Bolkestein wel niet spreekt en dan kotst\n\
hij zijn avondeten uit over de cocktails."
    print( langestring )

    # niet in boek
    if 0:
        zondernewlines = langestring.replace('\n', ' ')
        print(zondernewlines)

if 0:
    markeer( '10.2 D' )

    # niet in boek
    # losse literal strings na elkaar vormen een nieuwe
    langestring = ("En een paar nasale secretaresses uit Middelburg "
        "met flubberende witte benen en buikloop die flirten met harige "
        "krombenige Spaanse obers genaamd Manuel en eens per week is er "
        "een excursie naar de plaatselijke Romeinse bouwval waar je")
    print( langestring )
    kortestring = "aaa" "bbb" "ccc"
    # a = 'xxx'
    # kortestring = "aaa" a "bbb" "ccc" # fout, alleen met literals
    print( kortestring )

#=============================================================#
# 10.3 Speciale tekens

# "\n" newline
# "\'" single quote, "\"" double quote
# "\t" tab
# "\\" backslash
# "\x20" hex waarde spatie

if 0:
    markeer( '10.3' )

    # voorbeeld niet in boek
    print("A"  "\n" "\'"   "\""
          "\t" "\\" "\x20" "Z")

#=============================================================#
# 10.4 Tekens in een string

#-------------------------------------------------------------#
# 10.4.1 String indices

#  p  y  t  h  o  n
#  0  1  2  3  4  5
# -6 -5 -4 -3 -2 -1

if 0:
    markeer( '10.4.1 A' )

    fruit = "aardbei"
    print( fruit[4] )
    print( fruit[2] )
    print( fruit[1] )
    print( fruit[-4] )
    print( fruit[-2] )
    print( fruit[-5] )
    print( fruit[-1] )
    print( fruit[5] )

if 0:
    markeer( '10.4.1 B' )

    # index moet integer zijn
    # enkel karakter
    # buiten bereik string is fout

    from math import sqrt

    fruit = "aalbes"
    x = 3

    print( fruit[3-2] )
    print( fruit[int( sqrt( 4 ) )] )
    print( fruit[2**2] )
    print( fruit[int( (x-len( fruit ))/3 )] )
    print( fruit[-len( fruit )])
    print( fruit[-x] )
    # print( fruit[10] ) # index error, niet in boek

if 0:
    markeer( '10.4.1 C' )

    # ook bij literals
    print( 'aalbes'[3] )  # 'b'
    
    # niet in boek
    print( 'abcdef'[3] )  # 'd'

if 0:
    markeer( '10.4.1 D' )

    # substring
    # met begin en zonder einde
    # buiten bereik string geeft lege
    
    fruit = "aalbes"
    print( fruit[:] )
    print( fruit[0:] )
    print( fruit[:6] )
    print( fruit[:100] )
    print( fruit[:len( fruit )] )
    print( fruit[1:-1] )
    print( fruit[2], fruit[1:6] )

#-------------------------------------------------------------#
# 10.4.2 Strings doorlopen

if 0:
    markeer( '10.4.2 A' )

    fruit = 'appel'
    for teken in fruit:
        print( teken, '- ', end='' )

    if 0:
        # standaard default sep=' ' spatie, niet in boek
        print()
        for teken in fruit:
            print( teken, ' - ', sep='', end='' )

if 0:
    markeer( '10.4.2 B' )

    fruit = 'appel'

    for i in range( 0, len( fruit ) ):
    # for i in range( len( fruit ) ):
        print( fruit[i], "- ", end="")
        
        # niet in boek
        # flush=True soms nodig bij gebruik end=
        #   anders output niet meteen zichtbaar
        #    vanwege buffering
        # print( fruit[i], "- ", end="", flush=True )
    print() 

    i = 0
    while i < len( fruit ):
        print( fruit[i], "- ", end="" )
        i += 1

#-------
# Opgave

if 0:
    markeer( '10.4.2 Opgave A' )

    klinkers = 'aeiou'

    def printIndices(txt):
        print(txt)
        idx = 0
        for ch in txt:
        # for idx, ch in enumerate(txt):
            if ch.lower() in klinkers:
                print(' ', ch, 'index', idx)
            idx += 1

    printIndices('Oude Azijn')

#-------
# Opgave

if 0:
    markeer( '10.4.2 Opgave B' )

    def printSameIndices(txt1, txt2):
        print(txt1)
        print(txt2)
        idx = 0
        for ch in txt1:
        # for idx, ch in enumerate(txt1):
            if idx < len(txt2) and txt2[idx] == ch:
            # if txt2[idx] == ch:
                print(' ', ch, 'index', idx)
            idx += 1

    printSameIndices('The Holy Grail', 'Life of Brian')
    printSameIndices('WijnAzijn', 'BrandeWijn')

#-------
# Opgave

if 0:
    markeer( '10.4.2 Opgave C' )

    def LetterSpace(txt):
        out = ''
        for ch in txt:
            if (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z'):
                out += ch
            else:
                out += ' '
        return out

    print(LetterSpace('ph@t l00t'))

#-------------------------------------------------------------#
# 10.4.3 Substrings met stappen

if 0:
    markeer( '10.4.3 A' )

    fruit = "banaan"
    print( fruit[::2] )
    print( fruit[1::2] )
    print( fruit[::-1] )
    print( fruit[::-2] )

    # niet in boek, default stap 1
    print( 'extra', fruit[::] )

if 0:
    markeer( '10.4.3 B' )

    fruit = "banaan"
    print( fruit[::-1] )
    for i in range( 5, -1, -1 ):
        print( fruit[i] )
        # print( fruit[i], end = '' )
    # print()

if 0:
    markeer( '10.4.3 C' )

    # niet in boek
    s = "abcdefghijklmnopqrstuvwxyz"
    print('s[3]',   s[3])   # d
    print('s[3:5]', s[3:5]) # de
    print('s[3:]',  s[3:])  # defghijklmnopqrstuvwxyz
    print('s[:5]',  s[:5])  # abcde
    print('s[:]',   s[:])   # abcdefghijklmnopqrstuvwxyz
    print()
    print('s[-1]',       s[-1])       # z
    print('s[-7:-1]',    s[-7:-1])    # tuvwxy
    print('s[-7:]',      s[-7:])      # tuvwxyz
    print('s[-7:0]',     s[-7:0])     # ''
    print('s[-7:23]',    s[-7:23])    # tuvw
    print('s[-7:1000]',  s[-7:1000])  # tuvwxyz
    print()
    print('s[::]',        s[::])        # abcdefghijklmnopqrstuvwxyz
    print('s[::-1]',      s[::-1])      # zyxwvutsrqponmlkjihgfedcba
    print('s[27::-1]',    s[27::-1])    # zyxwvutsrqponmlkjihgfedcba
    print('s[26::-1]',    s[26::-1])    # zyxwvutsrqponmlkjihgfedcba
    print('s[25::-1]',    s[25::-1])    # zyxwvutsrqponmlkjihgfedcba
    print('s[24::-1]',    s[24::-1])    # yxwvutsrqponmlkjihgfedcba
    print('s[0::-1]',     s[0::-1])     # a
    print('s[-1::-1]',    s[-1::-1])    # zyxwvutsrqponmlkjihgfedcba
    print('s[-1:-27:-1]', s[-1:-27:-1]) # zyxwvutsrqponmlkjihgfedcba
    print('s[-1:-26:-1]', s[-1:-26:-1]) # zyxwvutsrqponmlkjihgfedcb
    print('s[-1:-25:-1]', s[-1:-25:-1]) # zyxwvutsrqponmlkjihgfedc
    print('s[-1:0:-1]',   s[-1:0:-1])   # zyxwvutsrqponmlkjihgfedcb
    print('s[-1:24:-1]',  s[-1:24:-1])  # z
    print('s[-1:25:-1]',  s[-1:25:-1])  # ''
    print('s[-1:26:-1]',  s[-1:26:-1])  # ''
    print('s[-1:100:-1]', s[-1:100:-1]) # ''
    print('s[-1:-7:-1]',  s[-1:-7:-1])  # zyxwvu
    print('s[-7:-1:-1]',  s[-7:-1:-1])  # ''
    print('s[-7::-1]',    s[-7::-1])    # tsrqponmlkjihgfedcba
    print('s[-7:-27:-1]', s[-7:-27:-1]) # tsrqponmlkjihgfedcba 
    print('s[-7:-26:-1]', s[-7:-26:-1]) # tsrqponmlkjihgfedcb
    print('s[-7:-25:-1]', s[-7:-25:-1]) # tsrqponmlkjihgfedc
    print('s[-7:-1:-1]',  s[-7:-1:-1])  # ''
    print('s[-7:0:-1]',   s[-7:0:-1])   # tsrqponmlkjihgfedcb
    print('s[-7:5:-1]',   s[-7:5:-1])   # tsrqponmlkjihg
    print('s[:0:-1]',     s[:0:-1])     # zyxwvutsrqponmlkjihgfedcb
    print('s[:-1:-1]',    s[:-1:-1])    # ''

#=============================================================#
# 10.5 Strings zijn onveranderbaar

if 0:
    markeer( '10.5 A' )

    fruit = "aaldbei"
    fruit[2] = "r"  # Runtime error! # type error
    print( fruit )

if 0:
    markeer( '10.5 B' )

    fruit = "aaldbei"
    fruit = fruit[:2] + "r" + fruit[3:]
    print( fruit )

    # niet in boek, list komt in hoofdstuk 12
    fruit = list(fruit)
    fruit[2] = 'r'
    fruit = ''.join(fruit)
    # fruit = str(fruit)
    print( fruit )

#=============================================================#
# 10.6 string methodes

#-------------------------------------------------------------#
# 10.6.1 strip()

if 0:
    markeer( '10.6.1 A' )

    s = "    En nu iets heel anders \n    "
    print( "["+s+"]" )
    s = s.strip()
    print( "["+s+"]" )

if 0:
    markeer( '10.6.1 B' )

    # niet in boek
    s = "    En nu iets heel anders \n    "
    # s = "  \n  En nu iets heel anders \n    "
    # rstrip lstrip
    print( "<"+s+">" )
    print( "<"+s.strip()+">" )
    print( "<"+s.rstrip()+">" )
    print( "<"+s.lstrip()+">" )

    print( repr(s) )
    print( repr(s.strip()) )
    print( repr(s.rstrip()) )
    print( repr(s.lstrip()) )

#-------------------------------------------------------------#
# 10.6.2 upper() en lower()

if 0:
    markeer( '10.6.2' )

    s = "The Meaning of Life"
    # s = "äéèôÄÉÈÔ" # niet in boek

    print( s )
    print( s.upper() )
    print( s.lower() )

#-------------------------------------------------------------#
# 10.6.3 find()

if 0:
    markeer( '10.6.3' )

    # verwant met in operator
    s = "Humpty Dumpty zat op de muur"
    print( s.find( "zat" ) )
    print( s.find( "t" ) )
    print( s.find( "t", 8 ) ) # niet in boek
    print( s.find( "t", 12 ) )
    print( s.find( "q" ) )

#-------------------------------------------------------------#
# 10.6.4 replace()

if 0:
    markeer( '10.6.4' )

    s = 'Humpty Dumpty zat op de muur'
    print( s.replace('zat op', 'viel van' ) )

    # niet in boek
    print( s ) # blijft ongewijzigd

    s = 'aabbcc'
    print( s.replace('a', 'z') )
    print( s.replace('A', 'Z') )

#-------------------------------------------------------------#
# 10.6.5 split()

if 0:
    markeer( '10.6.5 A' )

    s = 'Humpty Dumpty    zat   op de muur   '
    # s = 'Humpty Dumpty  \n\t  zat   op de muur   ' # niet in boek
    lijst = s.split()
    for woord in lijst:
        print( woord )
        # print( woord, len(woord) ) # niet in boek, geen whitespace in resultaat

if 0:
    markeer( '10.6.5 B' )

    csv = "2016,september,28,Data Processing,Tilburg University"
    # csv = "2016,sep\ttember,28,Data Processing,Tilburg University"
    waardes = csv.split( ',' )
    waardes = csv.split( ' ' ) # niet in boek, alleen spaties
    waardes = csv.split( )     # whitepace, ook tabs
    for waarde in waardes:
        print( waarde )
        # print( repr(waarde) )

#-------------------------------------------------------------#
# 10.6.6 join()

if 0:
    markeer( '10.6.6' )

    s = "Humpty;Dumpty;zat;op;de;muur"
    lijst = s.split( ';' )
    s = " ".join( lijst )
    print( s )

#-------------------------------------------------------------#
# 10.6.7 Oefening

#-------
# Opgave

if 0:
    markeer( '10.6.7 Opgave A' )

    txt = 'Barbara had een bar, waar ze rabarbar verkocht, en die \
daarom de rabarbarbarbarabar werd genoemd.'

    # niet in boek
    # txt = ( 'Barbara had een bar, waar ze rabarbar verkocht, en die '
    #   'daarom de rabarbarbarbarabar werd genoemd.' )

    txt2 = txt.replace('rabarbar', 'rabarber')
    print(txt2)

#-------
# Opgave

if 0:
    markeer( '10.6.7 Opgave B' )

    txt = 'Niemand verwacht de Spaanse Inquisitie!# In feite, \
    zij die de Spaanse Inquisitie wel verwachten...'

    pos = txt.find('#')
    txt = txt[:pos]
    print(txt)

#-------
# Opgave

if 0:
    markeer( '10.6.7 Opgave C' )

    def LetterSpace(txt):
        out = ''
        for ch in txt:
            if (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z'):
                out += ch
            else:
                out += ' '
        return out

    txt = "Ik heb zo'n honger."
    txt = LetterSpace(txt)
    txt = txt.lower()
    for woord in txt.split():
        print(woord)

#=============================================================#
# 10.7 Codering van tekens

#-------------------------------------------------------------#
# 10.7.1 ASCII

if 0:
    markeer( '10.7.1 A' )

    print( ord( 'A' ) )
    # print( ord( 'ABC' ) ) # niet in boek, error
    print( ord( 'a' ) )
    print( chr( 65 ) )
    print( chr( 97 ) )
    print( "mango" > "mangaan" )

if 0:
    markeer( '10.7.1 B' )

    print( "De twaalfde letter na g is", chr( ord( "g" )+12 ) )

    # niet in boek
    # 42 voorbij v print niets zichtbaars, 48 wel ok
    print( "De 42e letter na v is", chr( ord( "v" )+42 ) )
    print( "De 48e letter na v is", chr( ord( "v" )+48 ) )

if 0:
    markeer( '10.7.1 C' )

    print( ' ', end= ' ' )
    for i in range(16):
        if i < 10:
            print( ' ' +chr( ord( '0' )+i ), end= ' ' )
        else:
            print( ' ' +chr( ord( 'A' )+i-10 ), end= ' ' )
    print()
    for i in range( 2, 8 ):
    # for i in range( 2, 18 ): # voor bekijken na ASCII
        print( i, end= ' ' )
        for j in range( 16 ):
            c = i*16+j
            print( ' ' +chr( c ), end= ' ' )
        print()

#-------------------------------------------------------------#
# 10.7.2 UTF-8

if 0:
    markeer( '10.7.2 A' )

    alpha = "\u0391"
    print( len(alpha), ord( alpha ), hex( ord( alpha ) ) ) # niet in boek
    for i in range( 25 ):
        # print(i, end = '=')
        print( chr( ord( alpha )+i ), end=" " )
    # print()

if 0: # niet in boek
    markeer( '10.7.2 B' )

    omega = "\u03A9"
    print(omega)
    print('utf8 is', omega.encode())
    print('utf8 is', omega.encode('utf-8'))
    print('utf16 is', omega.encode('utf-16'))

if 0: # niet in boek
    markeer( '10.7.2 C' )

    euro = "\u20AC"
    print(euro)
    print('utf8 is', euro.encode())
    print('utf8 is', euro.encode('utf8')) # mag met en zonder streepje
    print('utf16 is', euro.encode('utf16'))

if 0: # niet in boek
    markeer( '10.7.2 D' )

    acirc = "\u00e2"
    print(acirc)
    print('cp500 is', acirc.encode('cp500'), ord(acirc.encode('cp500')))
    print('latin1 is', acirc.encode('latin1'))
    print('utf8 is', acirc.encode())
    print('utf8 is', acirc.encode('utf8'))
    print('utf16 is', acirc.encode('utf16'))

if 0: # niet in boek
    markeer( '10.7.2 E' )

    chr1 = "\xe2"
    chr2 = "\u20Ad"
    chr3 = "\U0001F600"
    # chr3 = '😀'

    print( chr1, chr2, chr3 )
    print( len(chr1), len(chr2), len(chr3) )
    print( chr1.encode('utf8'), chr2.encode('utf8'), chr3.encode('utf8') )
    print( chr1.encode('latin1') )
    # chr2.encode('latin1')  # UnicodeEncodeError, kan niet in latin1
    # chr3.encode('latin1')  # UnicodeEncodeError, not in range(256)

if 0: # niet in boek
    markeer( '10.7.2 F' )

    # in IDLE
    #   \b geeft blokvormig teken voor onbekend karakter
    # in command prompt cmd.exe
    #   \b wist voorgaande karakter
    backspace1 = '\b'
    backspace2 = 'ervoor\berna'
    print( backspace1 )
    print( backspace2 )

    if 0:
        other = '\x01\x03\x04'
        print( other )

    # todo: wat doen \x02 \t \v etc.

# niet in boek
# unicode tabel
# https://en.wikipedia.org/wiki/List_of_Unicode_characters

# unicode utf8 table part
# https://www.utf8-chartable.de/

# info
# https://en.wikipedia.org/wiki/Unicode
# https://en.wikipedia.org/wiki/UTF-8

#=============================================================#
# Wat je geleerd hebt

# - Strings
# - Strings over meerdere regels
# - Positive en negative indices voor strings
# - Substrings
# - Onveranderlijkheid van strings
# - String methodes strip(), upper(), lower(), find(), replace(), split(), en join()
# - Speciale tekens
# - ASCII en UTF-8 coderingen

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 10.1
# Opgave 10.2
# Opgave 10.3
# Opgave 10.4
# Opgave 10.5
# Opgave 10.6

# niet in boek

# formatteren tabel
# in bestand tabel.py

###############################################################
# 11 Tuples

#=============================================================#
# 11.1 Gebruik van tuples

if 0:
    markeer( '11.1 A' )

    # met en zonder haakjes
    t1 = ("appel", "mango")
    print( type( t1 ) )
    t2 = "banaan", "kers"
    print( type( t2 ) )

if 0:
    markeer( '11.1 B' )

    # met verschillende types
    #   inclusief tuple zelf
    t1 = ("appel", 3, 1.4)
    t2 = ("appel", 3, 1.4, ("banaan", 5))
    # niet in boek
    print(t1)
    print(t2)

if 0:
    markeer( '11.1 C' )

    # tuple in tuple is 1 element
    t1 = ("appel", "mango")
    t2 = ("appel", 3, 1.4)
    t3 = ("appel", 3, 1.4, ("banaan", 5))
    print( len( t1 ) )
    print( len( t2 ) )
    print( len( t3 ) )

if 0:
    markeer( '11.1 D' )

    t1 = ("appel", 3, 1.4, ("banaan", 5))
    for element in t1:
        print( element ) # print string appel zonder '', tuple banaan met

if 0:
    markeer( '11.1 E' )

    t1 = (327, 419, 101, 667, 925, 225)

    # niet in boek
    # t1 = (327, 419, 101, 667, 925, 225, 10.7)
    # t1 = (327, 419, 101, 667, 925, 225, 10.0)
    # t1 = (327, 419, 101, 667, 925, 225, 110.0)
    # t1 = (327, 419, 101, 667, 925, 225, "102") # TypeError bij max
    # t1 = ("327", "419", "101", "667", "925", "225")  # TypeError bij sum
    # t1 = ("327", "419", "101", "667", "925", "225", "aaa") # TypeError bij sum

    print( max( t1 ) )
    print( min( t1 ) )
    print( sum( t1 ) )

if 0:
    markeer( '11.1 F' )

    t1 = ("appel", "banaan", "kers")
    print( "banaan" in t1 )
    print( "mango" in t1 )

#-------------------------------------------------------------#
# 11.1.1 Tuple assignments

if 0:
    markeer( '11.1.1 A' )

    # met haakjes zonder komma geen tuple
    t1 = ("appel")
    print( type( t1 ) )

if 0:
    markeer( '11.1.1 B' )

    # wel tuple
    t1 = ("appel",)
    print( type( t1 ) )
    print( len( t1 ) )

if 0:
    markeer( '11.1.1 C' )

    # niet in boek
    t2 = ()  # lege tuple, geen functie aanroep
    t3 = tuple()  # ook lege tuple
    print( type(t2), type(t3) )
    print( len( t2 ), len( t3 ) )

if 0:
    markeer( '11.1.1 D' )

    t1, t2 = "appel", "banaan"
    print( t1 )
    print( t2 )
    # niet in boek
    print( type( t1 ), type( t2 ) )

if 0:
    markeer( '11.1.1 E' )

    t1, t2 = ("apple", "banaan"), "kers"  # apple niet vertaald in boek
    print( t1 )
    print( t2 )
    # niet in boek
    print( type( t1 ), type( t2 ) )

if 0:
    markeer( '11.1.1 F' )

    # niet in boek
    # ValueError: not enough values to unpack (expected 3, got 2)
    # t1, t2, t3 = ("apple", "banaan"), "kers"

    (t1, t2), t3 = ("apple", "banaan"), "kers"
    print( t1 )
    print( t2 )
    print( t3 )

#-------------------------------------------------------------#
# 11.1.2 Tuple indices

if 0:
    markeer( '11.1.2 A' )

    t1 = ("appel", "banaan", "kers", "doerian")
    print( t1[2] )  # string
    print( t1[2:3] ) # weer tuple
    
if 0:
    markeer( '11.1.2 B' )

    t1 = ("appel", "banaan", "kers", "doerian", "mango")
    print( t1[1:4] )

if 0:
    markeer( '11.1.2 C' )

    t1 = ("appel", "banaan", "kers", "doerian", "mango")
    i = 0
    while i < len( t1 ):
        print( t1[i] )
        i += 1

#-------
# Opgave

if 0:
    markeer( '11.1.2 Opgave A' )

    # werkt als while loop
    t1 = ("appel", "banaan", "kers", "doerian", "mango")
    i = 0
    for elm in t1:
        print( i, elm )
        i += 1

if 0:
    markeer( '11.1.2 Opgave B' )

    # met range
    t1 = ("appel", "banaan", "kers", "doerian", "mango")
    for i in range( len( t1 ) ):
        print( i, t1[i] )

if 0:
    markeer( '11.1.2 Opgave C' )

    # niet in boek, met enumerate
    t1 = ("appel", "banaan", "kers", "doerian", "mango")
    for i, elm in enumerate( t1 ):
        print( i, elm )

    if 0:
        for tup in enumerate( t1 ):
            # print( tup )
            i = tup[0]
            elm = tup[1]
            # tup = i, elm
            # i, elm = tup
            # i, elm = i, elm
            print( i, elm )

#-------------------------------------------------------------#
# 11.1.3 Tuple vergelijkingen

if 0:
    markeer( '11.1.3' )

    t1 = ( "appel", "banaan" )
    t2 = ( "appel", "banaan" )
    t3 = ( "appel", "kers" )
    t4 = ( "appel", "banaan", "kers" )
    print( t1 == t2 )
    print( t1 < t3 )
    print( t1 > t4 )
    print( t3 > t4 )

#-------------------------------------------------------------#
# 11.1.4 Tuple retourwaardes

# zie 8.2.8 Meerdere retourwaardes

#=============================================================#
# 11.2 Tuples zijn onveranderbaar

if 0:
    markeer( '11.2' )

    t1 = ("appel", "banaan", "kers", "doerian")
    t1[0] = "mango" # Runtime error

#=============================================================#
# 11.3 Toepassingen van tuples

if 0:
    markeer( '11.3 A' )

    from math import sqrt

    # Retourneert de afstand tussen twee punten in 2-dimensionale
    # ruimte. The punten zijn de parameters van de functie.
    # Ieder punt is een tuple met twee numerieke waardes.
    def afstand( p1, p2 ):
        return sqrt( (p1[0] - p2[0]) **2 + (p1[1] - p2[1]) **2 )

    punt1 = (1,2)
    punt2 = (5,5)
    print( "Afstand tussen", punt1, "en", punt2, "is",
        afstand( punt1, punt2 ) )

    # niet in boek
    # kan punten verplaatsen naar oorsprong
    # kleinste naar oorsprong
    # (1,2) -> (0,0)
    # (5,5) -> (4,3)
    # duidelijker welke driehoek gebruikt wordt
    #   voor berekenen afstand
    
if 0:
    markeer( '11.3 B' )

    from math import sqrt

    # afstand tussen twee punten in N-dimensionale ruimte.
    # De punten hebben dezelfde dimensie, ze zijn allebei tuples
    # met numerieke waardes, met dezelfde lengte.
    def afstand( p1, p2 ):
        totaal = 0
        for i in range( len( p1 ) ):
            totaal += (p1[i] - p2[i]) **2
        return sqrt( totaal )

    # 1-dimensionale ruimte
    punt1 = (1,)
    punt2 = (5,)
    print( "1D: afstand tussen", punt1, "en", punt2, "is",
        afstand( punt1, punt2 ) )

    # 2-dimensionale ruimte
    punt1 = (1,2)
    punt2 = (5,5)
    print( "2D: afstand tussen", punt1, "en", punt2, "is",
        afstand( punt1, punt2 ) )

    # 3-dimensionale ruimte
    punt1 = (1,2,4)
    punt2 = (5,5,8)
    # punt1 = (1,2,0) # niet in boek
    # punt2 = (5,5,0) # als 2D afstand bij gelijke 3e dimensie
    print( "3D: afstand tussen", punt1, "en", punt2, "is",
        afstand( punt1, punt2 ) )

#=============================================================#
# Wat je geleerd hebt

# - Tuples
# - Tuple assignments
# - Tuple indices
# - Onveranderbaarheid van tuples
# - Toepassingen van tuples

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 11.1
# Opgave 11.2
# Opgave 11.3
#   mag met recursie, soms hoofdstuk 9 nog niet besproken
#   kan ook zonder recursie

###############################################################
# 12 Lists

#=============================================================#
# 12.1 Basis van lists

if 0:
    markeer( '12.1 A' )

    fruitlist = ["appel", "banaan", "kers", 27, 3.14]
    print( len( fruitlist ) )
    for element in fruitlist:
        print( element )
    print( fruitlist[2] )

    numlist = [314, 315, 642, 246, 129, 999]
    # numlist = [314, 315, 642, 246, 129, 999, "hello"] # TypeError, niet in boek
    # numlist = [314, 315, 642, 246, 129, 999, 10.7 ] # float resultaat
    print( max( numlist ) )
    print( min( numlist ) )
    print( sum( numlist ) )
    print( 100 in numlist )
    print( 999 in numlist )

if 0:
    markeer( '12.1 B' )

    # niet in boek, init tuple en list
    a, b = [3, 7]
    print(a, b)

    x, y, z = range(3) # geen list of tuple
    # rng = range(3)   # range object
    # print( rng, type( rng ) )
    # x, y, z = rng
    print(x, y, z)

    [k, l] = 8, 6
    print(k, l)

    [p, q, r] = range(1, 4)
    print(p, q, r)

#-------
# Opgave

if 0:
    markeer( '12.1 Opgave' )

    lijst = ['a', 394, None, (3,), False, 99.4, [], [[]] ]
    i = 0
    while i != len(lijst):
        print(lijst[i], type(lijst[i]))
        i += 1

#=============================================================#
# 12.2 Lists zijn veranderbaar

if 0:
    markeer( '12.2 A' )

    fruitlist = ["appel", "banaan", "kers", "doerian", "mango"]
    # met tuple foutmelding, niet in boek
    # fruitlist = ("appel", "banaan", "kers", "doerian", "mango")
    print( fruitlist )
    fruitlist[2] = "aardbei"
    print( fruitlist )

if 0:
    markeer( '12.2 B' )

    fruitlist = ["appel", "banaan", "kers", "doerian", "mango"]
    print( fruitlist )
    fruitlist[1:3] = ["framboos", "aardbei", "aalbes"]
    print( fruitlist )

if 0:
    markeer( '12.2 C' )

    fruitlist = ["appel", "banaan", "kers", "doerian", "mango"]
    print( fruitlist )
    fruitlist[1:1] = ["framboos", "aardbei", "aalbes"]
    print( fruitlist )

if 0:
    markeer( '12.2 D' )

    fruitlist = ["appel", "banaan", "kers", "doerian", "mango"]
    print( fruitlist )
    fruitlist[1:3] = []
    print( fruitlist )

#-------
# Opgave

if 0:
    markeer( '12.2 Opgave' )

    fruitlist = ["appel", "banaan", "kers", "doerian", "mango"]
    for i in range(len(fruitlist)):
        elm = fruitlist[i]
        elm = elm.upper()
        fruitlist[i] = elm
    print(fruitlist)

if 0:
    markeer( '12.2 E' )

    # veranderen tijdens for in loop gaat niet goed
    numlist = [1,2,0,3,4,0,0,5,6,7]
    for num in numlist:
        if num == 0:
            numlist.remove(0) # verwijdert de eerste 0
        else:
            print( num, end=" " )
    print( numlist )
    # beter van achteren naar voren werken

if 0:
    markeer( '12.2 F' )

    # veranderen tijdens for in loop gaat niet goed ook niet via index
    numlist = [1,2,0,3,4,0,0,5,6,7]
    for i in range(len(numlist)):
        # if i >= len(numlist):  # toevoegen helpt niet
        #     continue
        if numlist[i] == 0: # IndexError: list index out of range
            numlist.remove(0) # verwijdert de eerste 0
            # numlist.pop(i) # in plaats van remove helpt niet
        else:
            print( numlist[i], end=" " )
    print( numlist )

if 0:
    markeer( '12.2 G' )

    # niet in boek, is lastig
    # veranderen tijdens loop gaat wel goed van achter naar voren
    #   maar moet dan wel index vanaf vooraan gebruiken
    #   want bij index vanaf achteren wordt verkeerde positie gebruikt
    #     namelijk ten opzichten van nieuwe korte lijst
    #     na verwijderen element, zowel bij remove als bij pop
    def remov(lst, i):
        if lst[i] == 0:
            print('\ni=' + str(i), lst, 'remove(0)', end=' ')
            lst.remove(0) # verwijdert de eerste 0
        else:
            print( 'i=' + str(i), lst[i], end=" " )
    def popr(lst, i):
        if lst[i] == 0:
            print('\ni=' + str(i), lst, 'pop(i)', end=' ')
            lst.pop(i) # verwijdert de gecontroleerde 0
        else:
            print( 'i=' + str(i), lst[i], end=" " )
    def backremove(title, numlst, rang, removefunc):
        print(title, numlst)
        for i in rang:
            removefunc(numlst, i)
        print( '\n', numlst, '\n' )
    # gaat met remove wel goed met positieve index
    numlist0 = [1,2,0,3,4,0,0,5,6,7]
    numlist1 = numlist0[:]
    numlist2 = numlist0[:]
    numlist3 = numlist0[:]
    backremove('rem pos', numlist0,
               range(len(numlist0)-1, -1, -1), remov)
    # maar met remove met negatieve index gaat het niet goed
    backremove('rem neg', numlist1,
               range(-1, -len( numlist1 )+1, -1), remov)
    # gaat ook met pop wel goed met positieve index
    backremove('pop pos', numlist2,
               range(len(numlist2)-1, -1, -1), popr)
    # maar ook met pop niet goed met negatieve index
    backremove('pop neg', numlist3,
               range(-1, -len( numlist3 )+1, -1), popr)

#=============================================================#
# 12.3 Lists en operatoren

if 0:
    markeer( '12.3 A' )

    # operatoren + en *
    fruitlist = ["appel", "banaan"] + ["kers", "doerian"]
    # fruitlist = ("appel", "banaan") + ("kers", "doerian")
    print( fruitlist )
    numlist = 10 * [0] 
    # numlist = [0] * 10 # mag ook, niet in boek
    print( numlist )
    
    # niet in boek, werkt ook voor tuple
    numlist = 10 * (0,) 
    print( numlist )

if 0:
    markeer( '12.3 B' )

    # += operator
    fruitlist = ["appel", "banaan"]
    fruitlist += "kers"  # onbedoeld
    # fruitlist += 1234  # mag niet, niet in boek
    # fruitlist += 1234,  # mag wel, is tuple, iterabele
    print( fruitlist )

    fruitlist = ["appel", "banaan"]
    fruitlist += ["kers"]  # zoals extend
    print( fruitlist )

if 0:
    markeer( '12.3 C' )

    # niet in boek
    # ook *= operator
    lst = [ 2, 3 ]
    # lst = ( 2, 3 )
    lst *= 3
    print(lst)

#=============================================================#
# 12.4 List methodes

#-------------------------------------------------------------#
# 12.4.1 append()

if 0:
    markeer( '12.4.1' )

    fruitlist = ["appel", "banaan", "kers", "doerian"]
    print( fruitlist )
    fruitlist.append( "mango" )
    # fruitlist.append( None )  # niet in boek
    # fruitlist.append( 1234 )
    # fruitlist.append( 12.34 )
    # fruitlist.append( 1234, 12.34 ) # TypeError append takes one argument
    print( fruitlist )

#-------------------------------------------------------------#
# 12.4.2 extend()

if 0:
    markeer( '12.4.2' )

    fruitlist = ["appel", "banaan", "kers", "doerian"]
    print( fruitlist )
    fruitlist.extend( ["framboos", "aardbei", "aalbes"] )
    print( fruitlist )

    # ook iterabele zoals tuple, niet in boek
    # fruitlist.extend( ("framboos", "aardbei", "aalbes") )
    # print( fruitlist )

#-------------------------------------------------------------#
# 12.4.3 insert()

if 0:
    markeer( '12.4.3' )

    fruitlist = ["appel", "banaan", "kers", "doerian"]
    print( fruitlist )
    fruitlist.insert( 2, "mango" )
    # fruitlist.insert( 0, "mango" ) # voor begin, niet in boek
    # fruitlist.insert( -1, "mango" ) # voor laatste doerian
    # fruitlist.insert( len(fruitlist), "mango" ) # aan einde
    # fruitlist.insert( 20, "mango" ) # index ruim voorbij laatste mag ook
    print( fruitlist )

#-------------------------------------------------------------#
# 12.4.4 remove()

if 0:
    markeer( '12.4.4' )

    fruitlist = ["appel", "banaan", "kers", "banaan", "doerian"]
    print( fruitlist )
    fruitlist.remove( "banaan" )
    # fruitlist.remove( "sinaasappel" ) # ValueError, niet in boek
    print( fruitlist )

#-------------------------------------------------------------#
# 12.4.5 pop()

if 0:
    markeer( '12.4.5' )

    fruitlist = ["appel", "banaan", "kers", "doerian"]
    print( fruitlist )
    print( fruitlist.pop() )
    print( fruitlist )
    print( fruitlist.pop( 0 ) )
    print( fruitlist )
    # print( fruitlist.pop( 20 ) ) # IndexError, niet in boek

#-------------------------------------------------------------#
# 12.4.6 del

if 0:
    markeer( '12.4.6' )

    fruitlist = ["appel", "banaan", "kers", "banaan", "doerian"]
    del fruitlist[3]
    # del fruitlist[3:5] # niet in boek
    # del fruitlist[3:15] # niet in boek
    print( fruitlist )

    # niet in boek
    # del niet alleen voor list
    # verwijdert ook variabelen
    # type niet van belang

    if 0:
        del fruitlist
        print( fruitlist )

    if 0:
        a = 10
        print( a )
        del a

        # a = 20
        # print( a )


#-------------------------------------------------------------#
# 12.4.7 index()

if 0:
    markeer( '12.4.7' )

    fruitlist = ["appel", "banaan", "kers", "banaan", "doerian"]
    print( fruitlist.index( "banaan" ) )

    # niet in boek
    # print( fruitlist.index( "banaana" ) ) # ValueError

    # niet in boek
    # index ook bij tuple
    if 0:
        fruittuple = "appel", "banaan", "kers", "banaan", "doerian"
        print( fruittuple.index( "kers" ) )

    # list heeft geen methode find() zit wel in strings
    # kan in operator gebruiken als alleen nodig wel of niet erin

#-------------------------------------------------------------#
# 12.4.8 count()

if 0:
    markeer( '12.4.8' )

    fruitlist = ["appel", "banaan", "kers", "banaan", "doerian"]
    print( fruitlist.count( "banaan" ) )

    # niet in boek
    print( fruitlist.count( "banaana" ) )

    # count ook bij tuple
    if 0:
        fruittuple = ( "appel", "banaan", "kers", "banaan", "doerian" )
        print( fruittuple.count( "doerian" ) )
        print( fruittuple.count( "doer" ) )

    # kan in operator gebruiken als alleen nodig wel of niet erin

#-------------------------------------------------------------#
# 12.4.9 sort()

if 0:
    markeer( '12.4.9 A' )

    fruitlist = ["appel", "aardbei", "banaan", "framboos",
        "kers", "banaan", "doerian", "mango"]
    fruitlist.sort()
    print( fruitlist )

    numlist = [314, 315, 642, 246, 129, 999]
    numlist.sort()
    print( numlist )

if 0:
    markeer( '12.4.9 B' )

    # reverse sort
    fruitlist = ["appel", "aardbei", "banaan", "framboos",
        "kers", "banaan", "doerian", "aalbes"]
    fruitlist.sort( reverse=True )
    print( fruitlist )

if 0:
    markeer( '12.4.9 C' )

    # case-(in)sensitive sort
    fruitlist = ["appel", "Aardbei", "banaan", "framboos",
        "KERS", "banaana", "doerian", "mango"]
    fruitlist.sort()                # case-sensitive sort
    print( fruitlist )
    fruitlist.sort( key=str.lower ) # case-insensitive sort
    print( fruitlist )

if 0:
    markeer( '12.4.9 D' )

    # omgekeerde cijfers sort
    def keercijfersom( item ):    
        return (item%10)*100 + (int(item/10)%10) *10 + int(item/100)

    numlist = [314, 315, 642, 246, 129, 999]
    # numlist = [314, 315, 642, 246, 215, 129, 999]
    # print(numlist.sort( key=keercijfersom)) # , reverse = True )
    numlist.sort( key=keercijfersom ) #, reverse = True )
    print( numlist )

    # niet in boek
    # sorted later pas in boek
    # hoofdstuk 23 Iteratoren en Generatoren
    # 23.1.5 sorted()

    if 0:
        numtuple = (314, 315, 642, 246, 129, 999)
        newlist = sorted(numtuple, key=keercijfersom)
        print( numtuple )
        print( newlist )

if 0:
    markeer( '12.4.9 E' )

    # eerst lengte dan alfabetische sort
    def len_alfabetisch( element ):
        # print(len(element), element)
        return len( element ), element

    fruitlist = ["appel", "aardbei", "banaan", "framboos",
        "kers", "banaan", "doerian", "mango"]
    fruitlist.sort( key=len_alfabetisch )
    print( fruitlist )

if 0:
    markeer( '12.4.9 F' )

    # str na andere types sort
    def mix_key( element ):
        if isinstance( element, str ):
            return 1, element
        return 0, element

    # niet in boek
    # def mix_key( element ):
    #     if isinstance( element, str ):
    #         return element
    #     return "i" + str( element )

    mixedlist = ["appel", 0, "aardbei", 5, "banaan", 2, \
        "moerbei", 9, "kers", "banaan", 7, 7, 6, "mango"]
    mixedlist.sort( key=mix_key )
    print( mixedlist )

if 0:
    markeer( '12.4.9 G' )

    # sort met lambda op lengte dan alfabetisch
    fruitlist = ["appel", "aardbei", "banaan", "framboos",
        "kers", "banaan", "doerian", "mango"]
    fruitlist.sort( key=lambda x: (len(x),x) )
    print( fruitlist )

#-------------------------------------------------------------#
# 12.4.10 reverse()

if 0:
    markeer( '12.4.10 A' )

    fruitlist = ["appel", "aardbei", "banaan", "framboos",
        "kers", "banaan", "doerian", "aalbes"]
    fruitlist.reverse()
    # ret = fruitlist.reverse()
    # print(ret)
    print( fruitlist, type(fruitlist) )

if 0:
    markeer( '12.4.10 B' )

    # niet in boek
    # reversed later pas in boek
    # hoofdstuk 23 Iteratoren en Generatoren
    # 23.1.4 reversed()
    fruitlist = ["appel", "aardbei", "banaan", "framboos",
        "kers", "banaan", "doerian", "aalbes"]
    newlist = list(reversed(fruitlist))
    # newlist = reversed(fruitlist) # iterator
    print( fruitlist )
    print( newlist )
    print( fruitlist )

#-------------------------------------------------------------#
# 12.4.11 Oefening

#-------
# Opgave

if 0:
    markeer( '12.4.11 Opgave A' )

    # print gesorteerde namen door gebruiker ingevoerd
    namen = [ ]
    while True:
        naam = input('Geef naam, alleen Enter voor laatste ')
        if naam == '':
            break
        namen.append(naam)
    namen.sort(key=str.lower)
    for naam in namen:
        print(naam)

#-------
# Opgave

if 0:
    markeer( '12.4.11 Opgave B' )

    # print abs gesorteerde getallen
    lijst = [ -100, 77, 2, -3000, 0, -6, -894, 84]
    # lijst = [ -100, 77, 2, 3000, 0, -6, -3000, -894, 84] # stable sort
    # lijst = [ -100, 77, 2, -3000, 0, -6, 3000, -894, 84]
    lijst.sort(key=abs)
    print(lijst)

#-------
# Opgave

if 0:
    markeer( '12.4.11 Opgave C' )

    # tel hoe vaak letter in string
    txt = "Monty Python's Flying Circus"
    counts = [ 0 ] * 26
    for ch in txt.lower():
        if ch >= 'a' and ch <= 'z':
        # if 'a' <= ch <= 'z':
            counts[ord(ch) - ord('a')] += 1

    print('Letter aantallen in', txt)
    for i in range(len(counts)):
        count = counts[i]
    # for i, count in enumerate(counts):  # enumerate niet in boek
        if count:
            print(chr(i + ord('a')), count)
    
#=============================================================#
# 12.5 Alias

if 0:
    markeer( '12.5 A' )

    # verschillende variabelen naar dezelfde inhoud
    fruitlist = ["appel", "banaan", "kers", "doerian"]
    fruitlist2 = fruitlist
    print( fruitlist )
    print( fruitlist2 )
    fruitlist2[2] = "mango"
    print( fruitlist )
    print( fruitlist2 )

if 0:
    markeer( '12.5 B' )

    # alias te zien met id
    fruitlist = ["appel", "banaan", "kers", "doerian"]
    fruitlist2 = fruitlist
    print( id( fruitlist ) )
    print( id( fruitlist2 ) )

if 0:
    markeer( '12.5 C' )

    # kopie met hele sublist
    fruitlist = ["appel", "banaan", "kers", "doerian"]
    fruitlist2 = fruitlist
    fruitlist3 = fruitlist[:]

    print( id( fruitlist ) )
    print( id( fruitlist2 ) )
    print( id( fruitlist3 ) )

    fruitlist[2] = "mango"
    # fruitlist2[2] = "mango" # niet in boek
    # fruitlist3[2] = "mango" # niet in boek
    print( fruitlist )
    print( fruitlist2 )
    print( fruitlist3 )

if 0:
    markeer( '12.5 D' )

    # niet in boek
    # list heeft copy() method
    # kan gebruiken in plaats van [:]
    fruitlist4 = ["appel", "banaan", "kers", "doerian"]
    fruitlist5 = fruitlist4.copy()

    print( id( fruitlist4 ) )
    print( id( fruitlist5 ) )

    if 0:
        fruitlist4[1] = "aardbei"
        fruitlist5[1] = "sinaasappel"
        print( fruitlist4 )
        print( fruitlist5 )

        print( id( fruitlist4 ) )
        print( id( fruitlist5 ) )

if 0:
    markeer( '12.5 E' )

    # niet in boek
    # ook alle andere types vormen alias
    #   bij toekenning, assignment
    #   van variabele aan andere variabele

    a = 10
    # a = 'xyz'
    b = a
    print( id( a ) )
    print( id( b ) )

    if 0:
        # voor eerder besproken types geen probleem
        #   want kan waardes niet veranderen
        # waarde veranderen kan alleen door nieuwe assignment
        #   dan wordt nieuw object gemaakt
        #   met ander id

        # bool, int, float, str, tuple, functies
        #   zijn immutable
        #   zie ook 10.5 Strings zijn onveranderbaar
        # list is wel veranderbaar, mutable

        a = 11
        # a = 'mno'
        print( id( a ) )
        print( id( b ) )
        print( a )
        print( b )
    

#-------------------------------------------------------------#
# 12.5.1 is

if 0:
    markeer( '12.5.1 A' )

    # is vergelijkt id
    fruitlist = ["appel", "banaan", "kers", "doerian"]
    fruitlist2 = fruitlist
    fruitlist3 = fruitlist[:]

    print( fruitlist is fruitlist2 )
    print( fruitlist is fruitlist3 )
    print( fruitlist2 is fruitlist3 )

    if 0:
        # niet in boek
        # naast 'is' ook 'is not' operator
        #   zeldzaam gebruikt
        # https://docs.python.org/3/reference/expressions.html#is-not

        print()
        print( fruitlist is not fruitlist2 )
        print( fruitlist is not fruitlist3 )

        print( not fruitlist is fruitlist2 )     # kan ook
        print( not (fruitlist is fruitlist2 ) )  # duidelijker
        # print( (not fruitlist) is fruitlist2 ) # anders, niet de bedoeling
        # print( fruitlist is (not fruitlist2) ) # not list, leeg of niet

        # not lagere bewerkingsvolgorde, precedence dan
        #   in, not in, is, is not, <, <=, >, >=, !=, ==
        # https://docs.python.org/3/reference/expressions.html#operator-precedence
        # https://en.wikipedia.org/wiki/Precedence_(mathematics)
        # https://nl.wikipedia.org/wiki/Bewerkingsvolgorde

if 0:
    markeer( '12.5.1 B' )

    # == vergelijkt inhoud
    fruitlist = ["appel", "banaan", "kers", "doerian"]
    fruitlist2 = fruitlist
    fruitlist3 = fruitlist[:]

    print( fruitlist == fruitlist2 )
    print( fruitlist == fruitlist3 )
    print( fruitlist2 == fruitlist3 )

#-------------------------------------------------------------#
# 12.5.2 Ondiepe versus diepe kopieën

if 0:
    markeer( '12.5.2 A' )

    # hele sublist soms onvoldoende
    numlist = [ 1, 2, [3, 4] ]
    copylist = numlist[:]
    
    if 0:
        # niet in boek
        print( id( numlist ) )
        print( id( copylist ) )
        print( id( numlist[2] ) )
        print( id( copylist[2] ) )

    numlist[0] = 5
    numlist[2][0] = 6
    print( numlist )
    print( copylist )

    if 0:
        # niet in boek
        print( id( numlist ) )
        print( id( copylist ) )
        print( id( numlist[2] ) )
        print( id( copylist[2] ) )

if 0:
    markeer( '12.5.2 B' )

    # met deepcopy wel goede kopie
    from copy import deepcopy

    numlist = [ 1, 2, [3, 4] ]
    copylist = deepcopy( numlist )

    numlist[0] = 5
    numlist[2][0] = 6
    print( numlist )
    print( copylist )

    if 0:
        # niet in boek
        print( id( numlist ) )
        print( id( copylist ) )
        print( id( numlist[2] ) )
        print( id( copylist[2] ) )

#-------------------------------------------------------------#
# 12.5.3 Lists als argumenten

if 0:
    markeer( '12.5.3 A' )

    def wijziglist( x ):
        if len( x ) > 0:
            x[0] = "FRUIT!"

    fruitlist = ["appel", "banaan", "kers", "doerian"]
    # fruitlist2 = fruitlist[:] # niet in boek
    wijziglist( fruitlist )
    print( fruitlist )

    # niet in boek
    # alias meegeven, pass by reference
    # veel sneller dan kopie van list meegeven
    if 0:
        wijziglist( fruitlist[:] )
        print( fruitlist )

    if 0:
        print( fruitlist2 )
        wijziglist( fruitlist2 )
        # wijziglist( fruitlist2[:] )
        print( fruitlist2 )

if 0:
    markeer( '12.5.3 B' )

    # niet in boek
    # type hints voorbeelden
    # zie ook 8.2.12 Type hints voor functies

    def bijList( lst:list[str] ):
    # def bijList( lst:list[int] ): # geen foutmelding
        lst.append( 'aardbei' )
        
    fruitlist:list = ["appel", "banaan", "kers", "doerian"]
    # fruitlist:Int = ["appel", "banaan", "kers", "doerian"] # NameError
    # fruitlist:lst = ["appel", "banaan", "kers", "doerian"] # NameError
    bijList( fruitlist )
    print( fruitlist )

#=============================================================#
# 12.6 Geneste lists

if 0:
    markeer( '12.6' )

    # boter, kaas en eieren
    def toon_bord( b ):
        print( "  1 2 3" )
        for rij in range( 3 ):
            print( rij+1, end=" ")
            for kol in range( 3 ):
                print( b[rij][kol], end=" " )
            print()

    # niet in boek
    # kan ook met List Comprehensions, zie 12.8
    # rij = [ '-' ] * 3
    # rij = [ '-' for i in range(3) ]
    # bord = [ rij ] * 3
    # bord = [ rij for i in range(3) ]
    # bord = [ [ '-' for i in range(3) ] for i in range(3) ]
    # print( bord )

    bord = [ ["-", "-", "-"], ["-", "-", "-"], ["-", "-", "-"] ]
    bord[1][1] = "X"
    bord[0][2] = "O"
    toon_bord( bord )

#=============================================================#
# 12.7 List casting

if 0:
    markeer( '12.7 A' )

    # via tuple
    t1 = ( "appel", "banaan", "kers" )
    print( t1 )
    print( type( t1 ) )
    fruitlist = list( t1 )
    print( fruitlist )
    print( type( fruitlist ) )

if 0:
    markeer( '12.7 B' )

    numlist = range( 1, 11 )
    print( numlist )
    numlist = list( range( 1, 11 ) )
    print( numlist )

    if 0:
        # niet in boek
        # range object, als iterator
        numrange = range( 1, 11 )
        print( numrange, type(numrange) )
        numlist = list( numrange )
        print( numlist, type(numlist) )

        # range kan meer
        print( dir( numrange ) )
        print( 9 in numrange )
        print( 20 in numrange )
        print( numrange[ 2 ] )
        print( numrange.index(5) )

if 0:
    markeer( '12.7 C' )

    # geen voorbeeld in boek
    # via str
    txt = 'fruitmand'
    strlist = list( txt )
    print( strlist )
    # strtuple = tuple( txt )
    # print( strtuple )

#=============================================================#
# 12.8 List comprehensions

if 0:
    markeer( '12.8 A' )

    def kwadraatlist():
        k = []
        for i in range( 1, 26 ):
            k.append( i*i )
        return k

    sl = kwadraatlist()
    print( sl )

if 0:
    markeer( '12.8 B' )

    # met list comprehension  # lijst bevatten insluiten inbegrepen
    sl = [ x*x for x in range( 1, 26 ) ]
    print( sl )

if 0:
    markeer( '12.8 C' )

    # met if
    sl = [ x*x for x in range( 1, 26 ) if x%10 != 5]
    print( sl )

if 0:
    markeer( '12.8 D' )

    # met meer for en if, else niet toegestaan in comprehension
    triolist = [ (x,y,z) for x in range( 1, 5 )
        for y in range( 1, 5 ) for z in range( 1, 5 )
        if x != y if x != z if y != z]
    print( triolist )

if 0:
    markeer( '12.8 E' )

    # met meer for en if
    # anders geformatteerd lijkt meer op gewone for en if gebruik
    # met een if voor for is zo iets sneller
    triolist = [ (x,y,z)
                 for x in range( 1, 5 )
                    for y in range( 1, 5 )
                      if x != y
                       for z in range( 1, 5 )
                          # if x != y if x != z if y != z]
                          if x != z if y != z]
                               # (x, y, z) # bij 'gewone' code
    print( triolist )

if 0:
    markeer( '12.8 F' )

    # niet in boek
    triolist = [ ] # (x,y,z)
    for x in range( 1, 5 ):
        for y in range( 1, 5 ):
          if x != y:
            for z in range( 1, 5 ):
                # if x != y:
                    if x != z:
                        if y != z:
                            triolist.append((x, y, z)) # bij 'gewone' code
    print( triolist )

#=============================================================#
# Wat je geleerd hebt

# - Lists
# - Veranderbaarheid van lists
# - Gebruik van + en ∗ met lists
# - List methodes append(), extend(), insert(), remove(), pop(), index(), count(),
#   sort(), en reverse()
# - del met lists
# - Aliases
# - Het gereserveerde woord is
# - List kopieën
# - Diepe kopieën van lists via deepcopy()
# - Lists als argumenten
# - Type hints voor lists
# - Nesten van lists
# - List casting
# - List comprehensions

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 12.1
# Opgave 12.2
# Opgave 12.3
# Opgave 12.4
# Opgave 12.5
# Opgave 12.6
# Opgave 12.7
# Opgave 12.8

###############################################################
# 13 Dictionaries

#=============================================================#
# 13.1 Dictionary basis

if 0:
    markeer( '13.1 A' )

    fruitmand = { "appel":3, "banaan":5, "kers":50 }

    print( fruitmand ) # niet in boek

if 0:
    markeer( '13.1 B' )

    fruitmand = { "appel":3, "banaan":5, "kers":50 }
    print( fruitmand["banaan"] )

if 0:
    markeer( '13.1 C' )

    # listing1301.py
    fruitmand = { "appel":3, "banaan":5, "kers":50 }
    for key in fruitmand:
        print( "{}:{}".format( key, fruitmand[key] ))

if 0:
    markeer( '13.1 D' )

    fruitmand = { "appel":3, "banaan":5, "kers":50 }
    print( fruitmand )
    fruitmand["mango"] = 1
    print( fruitmand )

if 0:
    markeer( '13.1 E' )

    fruitmand = { "appel":3, "banaan":5, "kers":50 }
    print( fruitmand )
    del fruitmand["banaan"]
    print( fruitmand )

    # niet in boek
    # aantal elementen met len()
    print( len( fruitmand ) )


if 0:
    markeer( '13.1 F' )

    # vanaf Python 3.9 | operator
    fruitmand1 = { "appel":3, "banaan":5, "kers":50 }
    # fruitmand1 = { "appel":3, "banaan":5, "kers":50, "appel":20 } # niet in boek
    fruitmand2 = { "appel":2, "mango":7 }
    fruitmand3 = fruitmand1 | fruitmand2
    print( fruitmand3 )

# vanaf Python 3.5 type hints voor dict
# :dict
# :dict[str,int]

# vanaf Python 3.7 dict items volgorde waarin toegevoegd
#   daarvoor volgorde willekeurig

if 0:
    markeer( '13.1 G' )

    # niet in boek

    # dict() constructor
    # alternatieve manieren om een dictionary te maken
    # https://docs.python.org/3/library/stdtypes.html#dict

    dct1 = dict( a=10, b=11, c=12 )
    print( dct1 )

    dct2 = { 'a' : 10, 'b' : 11, 'c' : 12 }
    print( dct2 )

    # dct3 = dict( if=20, else=21, while=22 ) # invalid syntax
    # dct3 = dict( If=20, Else=21, While=22 ) # mag wel
    # print( dct3 )

    # dct4 = { 'if' : 20, 'else' : 21, 'while' : 22 }
    # print( dct4 )

    # dct5 = dict( 1, 2, 3 ) # TypeError
    # print( dct5 )

    # dct6 = dict( (1, 2, 3) ) # TypeError
    # print( dct6 )

    # dct7 = dict( (1, 11), (2, 12), (3, 13) ) # TypeError
    # print( dct7 )

    # dct8 = dict( ( (1, 11), (2, 12), (3, 13) ) )
    # print( dct8 )

    # dct9 = dict( ( (1, 11), (2, 12) ), three=13 )
    # print( dct9 )

    # dct10 = dict( { 1 : 11, 2 : 12 }, three=13 )
    # print( dct10 )

if 0:
    markeer( '13.1 H' )

    # niet in boek
    # dict comprehensions

    dct1 = { x : ord(x) - ord('a') for x in 'voorbeeld' }
    print( dct1 )
    
    dct2 = { x : ord(x) - ord('a') for x in 'test' if x != 'e' }
    print( dct2 )

    dct3 = { x : x * x for x in range(4) }
    print( dct3 )

    dct4 = { x : x * x for x in range(3, 8, 2) }
    print( dct4 )

    dct5 = { x + 3 : x + 4 for x in range(3, 8, 2) }
    print( dct5 )

    dct6 = { x : y for x, y in zip(range(4), range(10, 14) ) }
    print( dct6 )

    # zie ook 12.8 List comprehensions
    # zie ook 23.1.3 zip()

    # ook set comprehensions, zie opgave 14.2 X

    # er bestaan geen tuple comprehensions
    #   ( x for x in range(3) ) is een generator expressie
    # zie ook 23.2 Generatoren, 23.2.1 Generator expressies

#=============================================================#
# 13.2 Dictionary methodes

#-------------------------------------------------------------#
# 13.2.1 copy()

if 0:
    markeer( '13.2.1' )

    # listing1302.py
    fruitmand = { "appel":3, "banaan":5, "kers":50 }
    fruitmandalias = fruitmand
    fruitmandcopy = fruitmand.copy()
    print( id( fruitmand ) )
    print( id( fruitmandalias ) )
    print( id( fruitmandcopy ) )

#-------------------------------------------------------------#
# 13.2.2 keys(), values(), en items()

if 0:
    markeer( '13.2.2 A' )

    fruitmand = { "appel":3, "banaan":5, "kers":50 }
    print( list( fruitmand.keys() ) )
    # print( fruitmand.keys() ) # dict_keys() # niet in boek
    print( list( fruitmand.values() ) )
    print( list( fruitmand.items() ) )

if 0:
    markeer( '13.2.2 B' )

    fruitmand = {"appel":3,"banaan":5,"kers":50,"druif":0,"mango":2}
    for key in fruitmand.keys():
    # for key in fruitmand: # kan ook, niet in boek
        print( "{}:{}".format( key, fruitmand[key] ) )
    print( sum( fruitmand.values() ) )

if 0:
    markeer( '13.2.2 C' )

    # listing1303.py
    fruitmand = {"appel":3,"banaan":5,"kers":50,"druif":0,"mango":2}
    keylist = list( fruitmand.keys() )
    keylist.sort()
    for key in keylist:
        print( "{}:{}".format( key, fruitmand[key] ) )

if 0:
    markeer( '13.2.2 D' )

    # niet in boek
    fruitmand = {"appel":3,"banaan":5,"kers":50,"druif":0,"mango":2}

    # keylist = list( fruitmand.keys() ).sort() # werkt niet
    # keylist.sort()

    # keylist = sorted( fruitmand.keys() ) # sorted() werkt wel
    # keylist = sorted( fruitmand ) # kan ook zonder keys()

    # for key in keylist:
    for key in sorted( fruitmand ):
        print( "{}:{}".format( key, fruitmand[key] ) )

#-------------------------------------------------------------#
# 13.2.3 get()

if 0:
    markeer( '13.2.3' )

    # listing1304.py
    fruitmand = {"appel":3,"banaan":5,"kers":50,"druif":0,"mango":2}
    appel = fruitmand.get( "appel" )
    if appel:
        print( "appel is in de mand" )
    else:
        print( "geen appels in de mand")

    aardbei = fruitmand.get( "aardbei" )
    if aardbei:
        print( "aardbei is in de mand" )
    else:
        print( "geen aardbei in de mand")

    banaan = fruitmand.get( "banaan", 0 )
    print( "aantal bananen in de mand:", banaan )

    aardbei = fruitmand.get( "aardbei", 0 )
    print( "aantal aarbeien in de mand:", aardbei )

#-------------------------------------------------------------#
# 13.2.4 Oefening

#-------
# Opgave

if 0:
    markeer( '13.2.4 Opgave A' )

    # listing1305.py
    woordlist = ["appel","doerian","banaan","doerian","appel","kers",
        "kers","mango","appel","appel","kers","doerian","banaan",
        "appel","appel","appel","appel","banaan","appel"]

    aantallen = { }
    for woord in woordlist:
        if woord in aantallen:
            aantallen[woord] += 1
        else:
            aantallen[woord] = 1
    for woord, aantal in aantallen.items():
        print(woord, aantal)

#-------
# Opgave

if 0:
    markeer( '13.2.4 Opgave B' )

    # listing1306.py
    tekst = "appel,doerian,banaan,doerian,appel,kers,kers,mango," + \
        "appel,appel,kers,doerian,banaan,appel,appel,appel," + \
        "appel,banaan,appel"

    woordlist = tekst.split(',')
    # gelijk aan vorige opgave
    aantallen = { }
    for woord in woordlist:
        if woord in aantallen:
            aantallen[woord] += 1
        else:
            aantallen[woord] = 1
    for woord, aantal in aantallen.items():
        print(woord, aantal)

#-------
# Opgave

if 0:
    markeer( '13.2.4 Opgave C' )

    # listing1307.py
    engels_nederlands = { "last":"laatst", "week":"week", "the":"de",
        "royal":"koninklijk", "festival":"feest", "hall":"hal",
        "saw":"zaag", "first":"eerst", "performance":"optreden",
        "of":"van", "a":"een", "new":"nieuw", "symphony":"symphonie",
        "by":"bij", "one":"een", "world":"wereld", "leading":
        "leidend", "modern":"modern", "composer":"componist",
        "composers":"componisten", "two":"twee", "shed":"schuur",
        "sheds":"schuren" }

    zin = "Last week The Royal Festival Hall saw the first \
performance of a new symphony by one of the world's leading \
modern composers, Arthur \"Two-Sheds\" Jackson."

    # zonder interpunctie
    zinsimpel = ''
    for ch in zin:
        ch = ch.lower()
        if 'a' <= ch <= 'z':
            zinsimpel += ch
        else:
            zinsimpel += ' '
    engwords = zinsimpel.split()
    # print(engwords)
    nedwoorden = [ ]
    for engword in engwords:
        nedwoord = engels_nederlands.get(engword, engword)
        # nedwoord = engels_nederlands.get(engword, nedwoord) # werkt niet
        # nedwoord = engels_nederlands.get(engword)
        # if nedwoord == None:
        #     print('niet gevonden:', nedwoord, engword)
        #     nedwoord = engword
        nedwoorden.append(nedwoord)
    vertaling = ' '.join(nedwoorden)
    print(vertaling)
    print()

    # met interpunctie
    words = [ ]
    for ch in zin:
        ch = ch.lower()
        if ('a' <= ch <= 'z' and len(words) and
          'a' <= words[-1][0] <= 'z'):
          # 'a' <= words[-1][-1] <= 'z'):
            words[-1] += ch  # allemaal a <= z
        else:
            words.append(ch) # lengte 1, geen a <= z
    # print(words)

    woorden = [ ]
    for word in words:
        woord = engels_nederlands.get(word, word)
        woorden.append(woord)
    vertaling = ''.join(woorden)
    print(vertaling)

#=============================================================#
# 13.3 Keys

# key moet onveranderbaar zijn zoals str, int, float
# en ook tuple zoals punt in 2-dimensionale ruimte

if 0:
    markeer( '13.3' )

    # niet in boek
    dct0 = { '' : 5 }
    dct1 = { 5 : 5 }
    dct2 = { 1.0 : 5 }
    dct3 = { (1,2,3) : 5 }
    dct4 = { True : 5 }
    dct5 = { None : 5 }
    # dct6 = { [1,2,3] : 5 } # TypeError

    # dct7 = { {1:1,2:2} : 5 } # TypeError
                             # geen frozendict in Python
                             #  https://peps.python.org/pep-0416/

    # dct8 = { {1,2,3} : 5 } # TypeError  # set zie hoofdstuk 14
    # dct9 = { frozenset({1,2,3}) : 5 } # frozenset zie 14.3

    # dct10 = { markeer : 5 }
    # dct11 = { __name__ : 5 }

    # dct12 = { str : 5 }
    # dct13 = { object : 5 }

    class test:   # zie hoofdstuk 20 Object Oriëntatie
        pass
    # dct14 = { test : 5 }
    # dct15 = { test : 5, test : 6 } # geen error, test:5 overschreven
    # dct16 = { test() : 5 }
    # dct17 = { test() : 5, test() : 6 } # gebruikt id als key

    # print( dct17 )

#=============================================================#
# 13.4 Opslaan van complexe waardes

if 0:
    markeer( '13.4 A' )

    # listing1308.py
    curses = {
        '880254' :[ 'u123456', 'u383213', 'u234178'],
        '822177' :[ 'u123456', 'u223416', 'u234178'],
        '822164' :[ 'u123456', 'u223416', 'u383213', 'u234178' ]}
    for c in curses:
        print( c )
        for s in curses[c]:
            print( s, end=" " )
        print()

if 0:
    markeer( '13.4 B' )

    # listing1309.py

    # { studentnr:        # niet in boek
    #   { naam: xyz, ects: 123,
    #     studenten: { vak: eindcijfer, ... } }, 
    #   ...
    # }
    curses = {
        '880254':
        { "naam":"Onderzoeksvaardigheden Data Processing", "ects":3,
            "studenten":{'u123456':8, 'u383213':7.5, 'u234178':6} },
        '822177':
        { "naam":"Logica", "ects":6,
            "studenten":{'u123456':5, 'u223416':7, 'u234178':9} },
        '822164':
        { "naam":"Computer Games", "ects":6,
            "studenten":{'u123456':7.5, 'u223416':9 } } }

    for c in curses:
        print( "{}: {} ({})".format( c, curses[c]["naam"],
            curses[c]["ects"] ) )
        for s in curses[c]["studenten"]:
            print( "{}: {}".format( s, curses[c]["studenten"][s] ) )
        print()

#=============================================================#
# 13.5 Snelheid

if 0:
    markeer( '13.5 A' )

    # listing1310.py
    from datetime import datetime

    numlist = []
    for i in range( 10000 ):
        numlist.append( i )

    start = datetime.now()
    teller = 0
    for i in range( 10000, 20000 ):
        if i in numlist:
            teller += 1
    eind = datetime.now()

    # code in boek print microseconden (soms) op een verkeerde manier
    #   zie ook 13.5 B hieronder voor meer uitleg
    #   print bijvoorbeeld 879829 microseconden (toevallig) wel goed
    print( "{}.{} seconden om {} nummers te vinden".format(  # in boek
    # print( "{}.{:06} seconden om {} nummers te vinden".format( # niet in boek
        (eind-start).seconds, (eind-start).microseconds, teller ) )

if 0:
    markeer( '13.5 B' )

    # listing1311.py
    from datetime import datetime

    numdict = {}
    for i in range( 10000 ):
        numdict[i] = 1

    start = datetime.now()
    teller = 0
    for i in range( 10000, 20000 ):
        if i in numdict:
            teller += 1
    eind = datetime.now()

    # code in boek print microseconden (meestal) op een verkeerde manier
    print( "{}.{} seconden om {} nummers te vinden".format(  # in boek
    # print( "{}.{:06} seconden om {} nummers te vinden".format( # niet in boek
        (eind-start).seconds, (eind-start).microseconds, teller ) )

    # microseconden kan bijvoorbeeld 968 (snel) zijn of 1969 (minder snel)
    # wordt geprint als 0.968 en 0.1969
    #   gebruiker gaat dan op verkeerde manier vergelijken
    #     0.1969 lijkt sneller dan 0.968
    # print (vaak) te weinig nullen na .
    #   moet zijn 0.000968 en 0.001969, format {:06} nodig
    # microseconds moet met 6 digits na de . na seconds
    #   0.123    -> 123 milliseconden
    #   0.000123 -> 123 microseconden

    # zie ook 27.1 datetime

#=============================================================#
# Wat je geleerd hebt

# - Dictionaries
# - Dictionary keys en waardes
# - Dictionaries samenvoegen
# - Type hints voor dictionaries
# - Dictionary methodes copy(), keys(), values(), items(), en get()
# - Complexe dictionaries
# - Snelheid verschillen tussen lists en dictionaries

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 13.1
# Opgave 13.2
# Opgave 13.3

###############################################################
# 14 Sets

#=============================================================#
# 14.1 Basis van sets

if 0:
    markeer( '14.1 A' )

    fruitset = { "appel", "banaan", "kers" }
    print( fruitset )

if 0:
    markeer( '14.1 B' )

    helloset = set( "hello world" )
    print( helloset )

if 0:
    markeer( '14.1 C' )

    # listing1401.py
    fruitset = { "appel", "banaan", "kers", "doerian", "mango" }
    for element in fruitset:
        print( element )
    print()

    fruitlist = list( fruitset )
    fruitlist.sort()
    for element in fruitlist:
        print( element )

    # niet in boek
    if 0:
        print()
        for element in sorted( fruitset ):
            print( element )

if 0:
    # geen code in boek
    # lege set

    a = {} # dict
    print( a, type(a) )

    b = set() # set
    # b = set{} # Syntax Error, geen {} maar ()
    print( b, type(b) )

#=============================================================#
# 14.2 Set methodes

#-------------------------------------------------------------#
# 14.2.1 add() en update()

if 0:
    markeer( '14.2.1' )

    # listing1402.py
    fruitset = { "appel", "banaan", "kers", "doerian", "mango" }
    print( fruitset )

    fruitset.add( "appel" )
    fruitset.add( "aalbes" )
    print( fruitset )

    fruitset.update( ["appel","appel","appel","aardbei",
        "aardbei","appel","mango"] )
    print( fruitset )

    # niet in boek
    if 0:
        fruitset.update( "peer" ) # als ierator, letters
        print( fruitset )

    if 0: # ook andere types
        fruitset.update( range( 100 ) ) # als iterator, integers
        print( fruitset )

#-------------------------------------------------------------#
# 14.2.2 remove(), discard(), en clear()

if 0:
    markeer( '14.2.2' )

    fruitset = { "appel", "banaan", "kers", "doerian", "mango" }
    print( fruitset )

    fruitset.remove( "appel" )
    print( fruitset )

    # niet in boek
    # fruitset.remove( "peer" ) # KeyError
    # print( fruitset )

    # discard() geeft geen foutmelding als element niet bestaat
    fruitset.discard( "appel" )
    print( fruitset )
    fruitset.discard( "peer" )
    print( fruitset )
    

    # clear() verwijdert alle elementen
    fruitset.clear( )
    print( fruitset )

#-------------------------------------------------------------#
# 14.2.3 pop()

if 0:
    markeer( '14.2.3' )

    fruitset = { "appel", "banaan", "kers", "doerian", "mango" }
    while len( fruitset ) > 0:
        print( fruitset.pop() )

        # print( fruitset.pop(2) ) # TypeError, niet in boek

#-------------------------------------------------------------#
# 14.2.4 copy()

if 0:
    markeer( '14.2.4' )

    # geen code in boek
    fruitset = { "appel", "banaan", "kers", "doerian", "mango" }
    fruitset2 = fruitset
    fruitset3 = fruitset.copy()

    print(id(fruitset), fruitset)
    print(id(fruitset2), fruitset2)
    print(id(fruitset3), fruitset3)

    if 0:
        lst = [ fruitset, fruitset2 ]
        # lst = [ fruitset, fruitset3 ]
        for st in lst:
            print(st)
        lst[0].add( "peer" )
        for st in lst:
            print(st)

#-------------------------------------------------------------#
# 14.2.5 union()

if 0:
    markeer( '14.2.5' )

    # listing1403.py
    fruit1 = { "appel", "banaan", "kers" }
    fruit2 = { "banaan", "kers", "doerian" }
    fruitunion = fruit1.union( fruit2 )
    # fruitunion = fruit2.union( fruit1 ) # niet in boek
    print( fruitunion )
    fruitunion = fruit1 | fruit2
    # fruitunion = fruit2 | fruit1 # niet in boek
    print( fruitunion )

#-------------------------------------------------------------#
# 14.2.6 intersection()

if 0:
    markeer( '14.2.6' )

    # listing1404.py
    fruit1 = { "appel", "banaan", "kers" }
    fruit2 = { "banaan", "kers", "doerian" }
    fruitintersection = fruit1.intersection( fruit2 )
    print( fruitintersection )

    fruitintersection = fruit1 & fruit2
    print( fruitintersection )

#-------------------------------------------------------------#
# 14.2.7 difference()

if 0:
    markeer( '14.2.7' )

    # listing1405.py
    fruit1 = { "appel", "banaan", "kers" }
    fruit2 = { "banaan", "kers", "doerian" }
    fruitdifference = fruit1.difference( fruit2 )
    print( fruitdifference )

    fruitdifference = fruit1 - fruit2
    print( fruitdifference )

    fruitdifference = fruit2 - fruit1
    print( fruitdifference )

#-------------------------------------------------------------#
# 14.2.8 isdisjoint(), issubset(), en issuperset()

if 0:
    markeer( '14.2.8' )

    # listing1406.py
    fruit1 = { "appel", "banaan", "kers" }
    fruit2 = { "banaan", "kers" }
    # fruit2 = { "peer" } # niet in boek
    print( fruit1.isdisjoint( fruit2 ) )
    print() # niet in boek
    print( fruit1.issubset( fruit2 ) )
    print( fruit2.issubset( fruit1 ) )
    print( fruit1.issubset( fruit1 ) )
    print() # niet in boek
    print( fruit1.issuperset( fruit2 ) )
    print( fruit2.issuperset( fruit1 ) )
    print( fruit1.issuperset( fruit1 ) )

    # niet in boek
    # als issubset()
    print()
    print( fruit1 <= fruit2, '<=' )
    print( fruit2 <= fruit1, '<=' )
    print( fruit1 <= fruit1, '<=' )

    # als issuperset()
    print()
    print( fruit1 >= fruit2, '>=' )
    print( fruit2 >= fruit1, '>=' )
    print( fruit1 >= fruit1, '>=' )
    
    # proper subset, geen method
    # fruit1 <= fruit2 and fruit1 != fruit2
    print()
    print( fruit1 < fruit2, '<' )
    print( fruit2 < fruit1, '<' )
    print( fruit1 < fruit1, '<' )

    # proper superset, geen method
    # fruit1 >= fruit2 and fruit1 != fruit2
    print()
    print( fruit1 > fruit2, '>' )
    print( fruit2 > fruit1, '>' )
    print( fruit1 > fruit1, '>' )

#-------------------------------------------------------------#
# 14.2.9 Oefening

#-------
# Opgave

if 0:
    markeer( '14.2.9 Opgave A' )

    set1 = { 'A', 'B', 'C' }
    set2 = { 'B', 'C', 'D' }
    print(set1.symmetric_difference(set2),
          'symmetric_difference')
    print(set1 ^ set2, '^')

    print(set1 - set2, 'set1 - set2')
    print(set2 - set1, 'set2 - set1')
    print((set1 - set2) | (set2 - set1), 'combi1')

    print(set1 | set2, 'set1 | set2')
    print(set1 & set2, 'set1 & set2')
    print((set1 | set2) - (set1 & set2), 'combi2')

    # Venndiagram
    # zie https://nl.wikipedia.org/wiki/Venndiagram
    # duidelijkere plaatjes

    # (A  B C)     set1
    #    [B C  D]  set2
    # (A [   )  ]  set1 - set2  verschil difference
    # (  [   ) D]  set2 - set1  verschil
    # (A [   ) D]  set1 ^ set2  symmetrisch verschil
    # (  [B C)  ]  set1 & set2  doorsnede intersection
    # (A [B C) D]  set1 | set2  vereniging union

    # operatoren alleen met sets links en rechts
    # methodes mag ook met iterabel argument 

#-------
# Opgave

if 0:
    markeer( '14.2.9 Opgave B' )

    # als Opgave 7.6
    woord1 = input("woord1:")
    woord2 = input("woord2:")
    print("Letters gemeen:", ''.join(set(woord1) & set(woord2)))

    # unpacking operator
    # zie ook Aanvulling 2 *args, **kwargs, * en /    
    # print("Letters gemeen:", *(set(woord1) & set(woord2)))

#=============================================================#
# 14.3 Frozensets

if 0:
    markeer( '14.3' )

    fruit1 = frozenset( ["appel", "banaan", "kers"] )
    fruit2 = frozenset( ["banaan", "kers", "doerian"] )
    print( fruit1.union( fruit2 ) )

    # niet in boek
    # f = fruit1.pop() # AttributeError
    
#=============================================================#
# Wat je geleerd hebt

# - Sets
# - Methodes add(), update(), remove(), discard(), clear(), pop(), copy(), union(),
#   intersection(), difference(), isdisjoint(), issubset(), en issuperset()
# - Frozensets

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 14.1
# Opgave 14.2

###############################################################
# 15 Besturingssysteem

# os module
# operating system

#=============================================================#
# 15.1 Computers en besturingssystemen

# computer hardware, programma's software
#   begintijd gebruikte software direct de hardware
# hardware meer complex, divers geworden
# software meer uitwisselbaar, portable geworden

# besturingssysteem is software laag
#   tussen hardware en 'gewone' software
#   vertaalt functionaliteit van hoog-niveau naar laag-niveau
#     zoals van 'maak een gui window' naar 'teken pixels'
#     of 'wijzig een bestand' naar 'lees en schrijf disk sectors'
#   meer programma's en gebruikers tegelijkertijd
#     regelt afscherming en samenwerking
#   veel meer mogelijk via netwerk en internet
#     webbrowsers, Jitsi, mobiel, de Cloud

# standaard Python beschikbaar op besturingssystemen
#   Windows, macOS en Linux
#   diverse varianten: Windows 10, 11, Home, Enterprise
#     of macOS 13 Ventura, 14 Sonoma
#     of Linux Debian, Ubuntu, Mint, Fedora, CentOS, Red Hat, openSuse

# os module overbrugt verschillen tussen Windows, macOS en Linux
#   dezelfde Python functies met verschillende implementaties
#   soms verschil in onderliggend gedrag zichtbaar
#    zoals drive letters in Windows, bestand beveiliging in Linux

#=============================================================#
# 15.2 Command prompt

# GUI visuele presentatie van met name bestandssysteem
#   pictogrammen icons voor programma's, documenten, zijn bestanden
#   indeling in groepen via folders, zijn directories

# command prompt, command shell, terminal eenvoudiger
#   input commando's intypen, output tekst resultaat

# python <programmanaam>.py

# in os module functies bestandssysteem
#   vergelijkbaar met opdrachten in command prompt

#-------
# Opgave

# Windows: run cmd, gebruik dir
# macOS en Linux: run bash, gebruik ls

#=============================================================#
# 15.3 Bestandssysteem

# boomstructuur met directories en bestanden

# root directory
#   Windows \ soms ook /
#   macOS en Linux /
# gebruik / indien mogelijk, werkt vaak in Python

# onder root
#   directories met naam
#   en bestanden ook met naam
# onder directory andere directories en bestanden

# naam heeft beperkingen
#   afhankelijk van besturingssysteem
#   zoals Windows geen \ / < > : in naam

# gebruikelijk naam met extensie aan einde met .
#   geeft aan wat voor bestand .exe .txt .py
#   in GUI extensies vaak verborgen

# unieke identificatie van bestand pad
# vanaf root namen directories tot aan bestand
#   /<directory>/<directory>/../<bestandsnaam>
# bij Windows soms drive letter met : erbij
#   <drive>:/<directory>/<directory>/../<bestandsnaam>
# bijvoorbeeld C:/Python34/Lib/os.py

# bij Windows geen verschil tussen hoofd- en kleine letters
# bij andere juist wel verschillend

# elk draaiend programma heeft een huidige directory

# speciale aanduiding . huidige en .. hogere directory
# .. bij root komt weer in root

# PATH omgevingsvariable environment variable
# lijst directories voor zoeken programma

#=============================================================#
# 15.4 os functies

# voor meer zie Python handleiding

# niet in boek
# https://docs.python.org/3/library/os.html

# zie ook 16.5 os.path methodes

# os module
# bevat meer functies en variabelen dan in boek
#   zoals mkdir, rmdir, rename, environ
# zijn laag niveau, low level
#   dicht bij besturingssysteem
#   vaak met veel weinig gebruikte details
# beter pas nader bekijken
#   na werken met bestanden
# zie hoofdstuk 16 Tekstbestanden en
#     hoofdstuk 18 Binaire bestanden

#-------------------------------------------------------------#
# 15.4.1 getcwd()

if 0:
    markeer( '15.4.1' )

    from os import getcwd
    print( getcwd() )

#-------------------------------------------------------------#
# 15.4.2 chdir()

if 0:
    markeer( '15.4.2' )

    from os import getcwd, chdir

    home = getcwd()
    print( home )
    chdir( ".." )
    print( getcwd() )
    chdir( home )
    print( getcwd() )

    # niet in boek
    print()
    from os import sep
    print(sep)

    dirs = ( ".",    ".\\",
             "\\",   "/",
             "c:\\", "c:/" )
    for dir in dirs:
        print()
        if 1:
            print( "naar home", home )
            chdir( home )
        print( "voor", dir, ":", getcwd() )
        chdir( dir )
        print( "na", dir, ":", getcwd() )
        input( "druk op Enter" )

#-------------------------------------------------------------#
# 15.4.3 listdir()

if 0:
    markeer( '15.4.3' )

    from os import listdir

    flist = listdir( "." )
    # flist = listdir( "c:\\" ) # niet in boek
    for naam in flist:
        print( naam )  # in boek ontbreekt regel indent

#-------------------------------------------------------------#
# 15.4.4 system()

if 0:
    markeer( '15.4.4' )

    # niet in boek
    from os import system
    if 1:
        system('py.exe')

    if 0:
        # gaat soms naar Store
        system('python.exe')

    # gaat via opdracht prompt cmd.exe
    #   of andere shell
    # is maar kort zichtbaar
    if 0:
        system('calc')
    
    # kan van cmd.exe ook ingebouwde commando's gebruiken
    #   is te kort zichtbaar
    if 0:
        system('dir')
    if 0:
        system('dir > dir.txt')
        with open('dir.txt') as dirf:
            for line in dirf:
                print(line, end='') # line heeft al \n

    # beter execl(), execle(), execlp(), etc. in os module

    # niet in boek
    # of run(), Popen() in subprocess module

#=============================================================#
# Wat je geleerd hebt

# - Besturingssystemen
# - Command shell
# - Bestandssystemen
# - Functies getcwd(), chdir(), listdir(), en system() uit de os module

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 15.1

###############################################################
# 16 Tekstbestanden

# zijn gemakkelijk uitwisselbaar tussen programma's

# er bestaan standaard formaten zoals 'comma-separated values' CSV
#   of XML, hebben ondersteuning in specifieke modules

# standaard open, lezen, schrijven, sluiten bestanden ongeacht formaat

#=============================================================#
# 16.1 Platte tekstbestanden

# bestaan uit reguliere tekens zoals in te typen via toetsenbord
#  zoals Python programma's .py of HTML .html
# bestanden bij tekstverwerkers zoals Word .docx
#   en plaatjes .png .jpg zijn geen tekstbestanden

# tekst of niet uitproberen openen met teksteditor zoals IDLE

# andere, niet leesbare tekstbestanden
#   zijn binaire bestanden
#   worden besproken in hoofdstuk 18

# vaak bestaan tekstbestanden uit regels tekst
# aan het einde van iedere regel newline \n
#   in bestand zelf in Windows vaak \r\n
#   in bestand in macOS en Linux altijd \n

# bij Windows vertaalt Python in tekst mode
#   tijdens lezen van \r\n in bestand naar \n in string
#   en bij schrijven van \n in string naar \r\n in bestand
# daardoor overal \n in Python code geen verschil bij tekst

#-------------------------------------------------------------#
# 16.1.1 Handles en pointers

# bestand benaderen door openen
#   geeft handle, file handle
#   is variabele die toegang biedt

# handle bevat pointer naar plaats in bestand
#   is huidige positie
# lezen en schrijven gebruiken pointer
#   daarna wordt pointer verplaatst

# open alleen om te lezen zet pointer aan begin
# open met append toevoegen zet pointer aan einde
# open alleen om te schrijven zet pointer aan begin
#   en maakt bestand leeg

# maak nieuw bestand door open alleen schrijven
#   met naam die nog niet bestaat

#-------------------------------------------------------------#
# 16.1.2 Verplaatsing van de pointer

# pointer wordt automatisch verplaatst
#   bij lezen en schrijven

# bij tekst bestanden voldoende

# methode voor verplaatsen pointer
#   pas in hoofdstuk 18 Binaire Bestanden

#-------------------------------------------------------------#
# 16.1.3 Buffers

# wijzigingen vaak niet onmiddelijk in bestand opgeslagen
# besturingssysteem slaat wijzigingen vaak op
#   in buffer in geheugen
# maakt pas definitief als nodig is flushing

# kan definitief maken forceren door bestand sluiten

# bestanden worden automatisch gesloten
#   bij normaal afsluiten programma
# het is beter om dat altijd in het programma te doen

# bij niet netjes afsluiten programma
#   worden sommige wijzigingen soms niet goed opgeslagen
# bijvoorbeeld bij debuggen

#-------------------------------------------------------------#
# 16.1.4 Bestandsverwerking

# vaak gebruikt programma loop
#   leest inhoud uit bestand
#   verwerkt inhoud
#   schrijft inhoud naar ander bestand

# lijkt op programma met loop
#   vraagt input van gebruiker
#   verwerkt data
#   print resultaat

# als programma met bestand schrijven lastig
#   dan tijdelijk print erin tot in orde

#=============================================================#
# 16.2 Lezen van tekstbestanden

#-------------------------------------------------------------#
# 16.2.1 Openen van een bestand met open()

# handle = open( <bestandsnaam> )

# naam bestand in huidige directory
# of volledig pad
#   absoluut vanaf root
#   of relatief vanaf huidige met . of ..

# tweede argument van open mode is optioneel
#  default mode argument 'r' read
#  lezen tekstbestand

# with open( <bestandsnaam> ) as handle

# gebruik van with open() heeft voordeel
#   zelf aanroepen van close() dan niet meer nodig
#   close() wordt automatisch aangeroepen
#     aan einde van with block
#     ook bij return of exception in with block

# zie ook close()

#-------------------------------------------------------------#
# 16.2.2 Lezen uit een bestand met read()

# handle.read()

# leest complete inhoud van bestand
# resultaat string

# zet pointer aan einde, achter laatste teken
#   nog een keer read() geeft dan lege string

#-------------------------------------------------------------#
# 16.2.3 Sluiten van een bestand met close()

# na close() bestand niet meer te benaderen via handle
# sluiten nodig voor ieder open bestand

if 0:
    markeer( '16.2.3' )

    # listing1601.py
    fp = open( "pc_rose.txt" )
    print( fp.read() )
    fp.close()

#-------------------------------------------------------------#
# 16.2.4 Tonen van de inhoud van een bestand

if 0:
    markeer( '16.2.4' )

    with open( "pc_rose.txt" ) as fp:
        buffer = fp.read()
    print( buffer )

#-------
# Opgave

if 0:
    markeer( '16.2.4 Opgave A' )

    # FileNotFoundError
    with open( "bestaat_niet.txt" ) as fp:
        buffer = fp.read()
    print( buffer )

#-------
# Opgave

if 0:
    markeer( '16.2.4 Opgave B' )

    with open( "pc_woodchuck.txt" ) as fp:
        buffer = fp.read()
    print( buffer )

#-------------------------------------------------------------#
# 16.2.5 Regels lezen met readline()

if 0:
    markeer( '16.2.5' )

    # listing1603.py
    fp = open( "pc_rose.txt" )
    while True:
        buffer = fp.readline()
        if buffer == '':
        # if not buffer: # kan ook, niet in boek
            break
        print( buffer ) # extra lege regel
        # print( repr(buffer) ) # niet in boek
    fp.close()

#-------
# Opgave

if 0:
    markeer( '16.2.5 Opgave' )

    with open( "pc_rose.txt" ) as fp:
        # niet in boek, readline() niet nodig 
        for line in fp:  
        # for line in fp.readline(): # werkt niet zo, levert karakters
            if 'naam' in line:
                print(line, end='')
        # print('aan einde', len(fp.readline()))

#-------------------------------------------------------------#
# 16.2.6 Regels lezen met readlines()

if 0:
    markeer( '16.2.6' )

    # listing1604.py
    fp = open( "pc_rose.txt" )
    buffer = fp.readlines() # laat newlines erin

    # buffer = fp.read() # niet in boek
    # print(type(buffer))
    # buffer = buffer.split('\n') # haalt newlines weg
    # print(type(buffer))

    for line in buffer:
        print( line, end="" )
    fp.close()

#-------------------------------------------------------------#
# 16.2.7 Wanneer gebruik je welke lees-methode?

# read() en readlines() lezen hele bestand
# bij grote bestanden beter regel voor regel lezen met readline()

# tijdens ontwikkelen handig alleen begin lezen

if 0:
    markeer( '16.2.7' )

    # listing1605.py
    fp = open( "pc_jabberwocky.txt" )
    teller = 0
    while teller < 5:
        buffer = fp.readline()
        if buffer == "":
            break
        print( buffer, end="" )
        teller += 1
    fp.close()

#-------
# Opgave

if 0:
    markeer( '16.2.7 Opgave' )

    fp = open( "pc_jabberwocky.txt" )
    teller = 0
    aantal = 0
    while teller < 5:
    # while True: # zonder beperkt aantal regels
        buffer = fp.readline()
        if buffer == '':
            break
        print( buffer, end="" )  # zonder tekst met # aan begin
        if 'wauwelwok' in buffer.lower():
            aantal += 1
        teller += 1
    fp.close()
    print( '\nAantal wauwelwok', aantal )

#=============================================================#
# 16.3 Schrijven in tekstbestanden

# lijkt op lezen uit tekstbestand
# open bestand, schrijf erin, sluit het

#-------------------------------------------------------------#
# 16.3.1 Openen voor schrijven

# open tweede argument mode 'w' write

# als bestand al bestaat dan inhoud weggegooid, truncate

#-------------------------------------------------------------#
# 16.3.2 Schrijven met write()

# argument string

if 0:
    markeer( '16.3.2 A' )

    # listing1606.py
    fp = open( "pc_writetest.tmp", "w" )
    while True:
        tekst = input( "Geef een regel tekst: " )
        if tekst == "":
            break
        fp.write( tekst )
    fp.close()

    fp = open( "pc_writetest.tmp" )
    buffer = fp.read()
    fp.close()

    print( buffer )

#-------
# Opgave

if 0:
    markeer( '16.3.2 A Opgave' )

    fp = open( "pc_writetest.tmp", "w" )
    while True:
        tekst = input( "Geef een regel tekst: " )
        if tekst == "":
            break
        fp.write( tekst + '\n' ) # newline erbij
        # print(tekst, file=fp) # kan ook, wellicht beter
    fp.close()

    fp = open( "pc_writetest.tmp" )
    buffer = fp.read()
    fp.close()

    print( buffer )

# niet in boek
# open tweede argument mode 'x' exclusive creation
#   opent alleen als bestand nog niet bestaat
#   foutmelding als bestand wel bestaat
# open is atomic, gebruik van 'x' is beter
#   dan 'w' met daarvoor controle of bestand bestaat
#     met bijvoorbeeld os.path.exists, zie 16.5.1

# voor meer open mode opties zie
# https://docs.python.org/3/library/functions.html#open

if 0:
    markeer( '16.3.2 B' )

    # twee keer runnen
    #   eerste keer gaat goed bestand bestaat nog niet
    #   tweede keer FileExistsError
    with open('bestaatniet', 'x') as fp:
        fp.write('schrijven')

#-------------------------------------------------------------#
# 16.3.3 Schrijven met writelines()

# argument list met strings

# moet zelf zorgen voor newlines aan einde

# writelines() tegenhanger van readlines()

if 0:
    markeer( '16.3.3 A' )

    # niet in boek
    with open( 'pc_rose.txt' ) as fp:
        lst = fp.readlines()
    with open( 'writelines.txt', 'w' ) as fp:
        fp.writelines(lst)
    with open( 'writelines.txt' ) as fp:
        lst2 = fp.readlines()
    # print(lst2)
    for line in lst2:
        print( line, end='' )
    print()
    assert lst == lst2
    if lst == lst2:
        print( '-> gelijk' )
    else:
        print( '-> ongelijk' )

# writelines(readlines())
#   inhoud geschreven bestand
#   exact gelijk aan gelezen bestand

if 0:
    markeer( '16.3.3 B' )

    # niet in boek
    fpr = open( 'pc_rose.txt' )
    fpw = open( 'writelines2.txt', 'w' )
    fpw.writelines(fpr.readlines())
    fpw.close()
    fpr.close()
    
    fp = open( 'writelines2.txt' )
    lst = fp.readlines()
    fp.close()
    print(lst)

if 0:
    markeer( '16.3.3 C' )

    # niet in boek
    with open( 'pc_rose.txt' ) as fpr:
        with open( 'writelines2.txt', 'w' ) as fpw:
            fpw.writelines(fpr.readlines())
    with open( 'writelines2.txt' ) as fp:
        lst = fp.readlines()
    print(lst)

if 0:
    markeer( '16.3.3 D' )

    # niet in boek
    with (open( 'pc_rose.txt' ) as fpr,
          open( 'writelines2.txt', 'w' ) as fpw):
        fpw.writelines(fpr.readlines())
    with open( 'writelines2.txt' ) as fp:
        lst = fp.readlines()
    print(lst)

# er bestaat geen writeline() functie   
#   vanwege zelf nodig newline
#   zou gelijk zijn aan write()

#-------------------------------------------------------------#
# 16.3.4 Oefening

#-------
# Opgave

if 0:
    markeer( '16.3.4 Opgave A' )

    with open( "pc_rose.txt" ) as fp:
        with open( "pc_writetest.tmp", 'w' ) as fpw:
            fpw.write(fp.read())
    # with open( "pc_writetest.tmp", 'w' ) as fpw: # andersom kan ook
    #    with open( "pc_rose.txt" ) as fp:
    #        fpw.write(fp.read())

    with open( "pc_writetest.tmp" ) as fp:
        buffer = fp.read()
    print( buffer )

#-------
# Opgave

if 0:
    markeer( '16.3.4 Opgave B' )

    with open( "pc_rose.txt" ) as fp:
        with open( "pc_writetest.tmp", 'w' ) as fpw:
            # niet in boek
            # kan ook regels lezen uit bestand
            # met for zonder readline()
            for line in fp:
                fpw.write(line[::-1])
                # fpw.write(''.join(reversed(line))) # kan ook
                # fpw.write(str(reversed(line))) # reversed objects niet goed

    with open( "pc_writetest.tmp" ) as fp:
        buffer = fp.read()
    print( buffer )

# niet in boek
# kan ook schrijven met print() met file=
# voegt wel \n toe tenzij met end= gewijzigd

if 0:
    markeer( '16.3.4 Opgave C' )

    with open( "pc_writetest.tmp", 'w' ) as fp:
        print('voorbeeld', file=fp)

    with open( "pc_writetest.tmp" ) as fp:
        print(fp.read())
        # print(fp.readlines())

#=============================================================#
# 16.4 Toevoegen aan tekstbestanden

# open tweede argument mode 'a' append

# inhoud wordt niet verwijderd
# pointer wordt aan einde gezet

if 0:
    markeer( '16.4 A' )

    # listing1607.py
    NAAM = "pc_writetest.tmp"

    def tooninhoud( bestandsnaam ):
        fp = open( bestandsnaam )
        print( fp.read() )
        fp.close()

    tooninhoud( NAAM )

    fp = open( NAAM, "a" )
    while True:
        tekst = input( "Geef een regel tekst: " )
        if tekst == "":
            break
        fp.write( tekst+"\n" )
    fp.close()

    tooninhoud( NAAM )

# niet in boek
# als bij open met mode 'a'
#   bestand nog niet bestaat
# dan wordt een leeg nieuw bestand gemaakt
#   met filepointer aan begin

if 0:
    markeer( '16.4 B' )

    with open('bestaatniet22', 'a') as fp:
        fp.write('voorbeeld2')
    with open('bestaatniet22') as fp:
        print(fp.read())

#=============================================================#
# 16.5 os.path methodes

# functies die werken met bestanden gemakkelijker maken

# kan waar naam wordt gebruikt ook pad, path gebruiken

# niet in boek
# zijn eigenlijk geen methodes
#   maar functies in een module

#-------------------------------------------------------------#
# 16.5.1 exists()

# bestaat pad

if 0:
    markeer( '16.5.1' )

    from os.path import exists

    if exists( "pc_rose.txt" ):
        print( "pc_rose.txt bestaat" )
    else:
        print( "pc_rose.txt bestaat niet" )

    if exists( "pc_tulip.txt" ):
        print( "pc_tulip.txt bestaat" )
    else:
        print( "pc_tulip.txt bestaat niet" )

    # niet in boek
    if exists( 'a:\\' ): # ook bij directories
        print( 'a:\\ bestaat' )
    else:
        print( 'a:\\ bestaat niet' )

    if exists( 'c:\\' ):
        print( 'c:\\ bestaat' )
    else:
        print( 'c:\\ bestaat niet' )

#-------------------------------------------------------------#
# 16.5.2 isfile()

# bestaat pad en is dit bestand

if 0:
    markeer( '16.5.2' )

    from os.path import isfile

    if isfile( "pc_rose.txt" ):
        print( "pc_rose.txt is een bestand" )
    else:
        print( "pc_rose.txt is geen bestand" )

#-------------------------------------------------------------#
# 16.5.3 isdir()

# bestaat pad en is dit directory

if 0:
    markeer( '16.5.3' )

    from os.path import isdir

    if isdir( "pc_rose.txt" ):
        print( "pc_rose.txt is een directory" )
    else:
        print( "pc_rose.txt is geen directory" )

#-------------------------------------------------------------#
# 16.5.4 join()

# combineert 1 of meer onderdelen
#   naar een geldig pad
#   met \ of / als scheiding

if 0:
    markeer( '16.5.4 A' )

    from os import listdir, getcwd
    from os.path import join

    bestandslist = listdir( "." )
    for naam in bestandslist:
        pad = join( getcwd(), naam )
        print( pad )

# niet in boek
# meer voorbeelden
# pad hoeft niet te bestaan

if 0:
    markeer( '16.5.4 B' )

    import os.path
    print(os.path.join('C:', 'Program Files'))
    print(os.path.join('C:\\', '\\Program Files\\'))
    print(os.path.join('c:\\', 'windows\\'))
    print(os.path.join('/', 'usr', 'bin'))
    print(os.path.join('/', 'home/', 'example/'))
    print(os.path.join('/', 'etc/', '../'))
    # print(os.sep) # soms bruikbaar in plaats van join

#-------------------------------------------------------------#
# 16.5.5 basename()

# bestandsnaam uit einde van pad

if 0:
    markeer( '16.5.5' )

    from os.path import basename

    print( basename( "/System/Home/readme.txt" ) )

#-------------------------------------------------------------#
# 16.5.6 dirname()

# directorynaam uit pad
# is pad zonder bestandsnaam aan einde

if 0:
    markeer( '16.5.6' )

    from os.path import dirname

    print( dirname( "/System/Home/readme.txt" ) )

#-------------------------------------------------------------#
# 16.5.7 getsize()

# grootte van bestand in bytes

if 0:
    markeer( '16.5.7 A' )

    from os.path import getsize

    num = getsize( "pc_rose.txt" )
    print( num )

# niet in boek
# meer voorbeelden
# werkt ook bij directories vanaf Python 3.6
# bestand moet bestaan

if 0:
    markeer( '16.5.7 B' )

    import os.path
    print( os.path.getsize( '\\' ) )  # bijvoorbeeld 8192
    print( os.path.getsize( 'bestaatniet' ) ) # FileNotFoundError

#-------
# Opgave

if 0:
    markeer( '16.5.7 Opgave' )

    from os import listdir
    from os.path import getsize, isfile

    bestandslist = listdir( "." )
    totaal = 0
    for naam in bestandslist:
        if isfile(naam): # size directory != 0
            size = getsize( naam )
            # print(size, naam)
            totaal += size
    print('Totaal', totaal)

#=============================================================#
# 16.6 Encoding

# tekst bestanden hebben een encoding
# hoe tekens geïnterpreteerd moeten worden
# encoding kan verschillend zijn tussen besturingssystemen

if 0:
    markeer( '16.6 A' )

    from sys import getfilesystemencoding

    print( getfilesystemencoding() )

# niet in boek

if 0:
    markeer( '16.6 B' )

    import sys

    print( sys.getdefaultencoding() )

# getfilesystemencoding() is specifiek voor bestandsnamen
# getdefaultencoding() is voor strings in het algemeen
# resultaat van beide vaak utf-8 in recente Python versies

# bij lezen tekstbestand dat andere manier gebruikt
# dan besturingssysteem verwacht dan soms UnicodeDecodeError

# soms werkt code goed bij een besturingssysteem
# maar geeft toch foutmelding bij een ander besturingssysteem

# kan zelfs mis gaan op delen van het bestand die je nog niet leest
# vanwege buffering leest Python soms meer dan gevraagd

# kan bij open optioneel string argument encoding= gebruiken
# geeft aan welk encoding mechanisme

# typische waardes: 'ascii', 'latin-1', 'mbcs', 'utf-8'

# niet in boek
# er zijn veel meer encodings
# met name toen elke taal diens speciale karakters
#   in bereik 0 tot en met 255 moest gebruiken

# gewoonlijk ascii of latin-1
#   ascii is onderdeel van latin-1

# kan bij UnicodeDecodeError proberen
#   open( <bestand>, encoding="latin-1" )

# tabel speciale tekens A0-FF (80-FF zonder 80-9F)

if 0:
    markeer( '16.6 C' )

    print(' ', end='') # met spatie beter leesbaar, niet in boek

    for i in range(16):
        if i < 10:
            print( ' '+chr( ord( '0' )+i ), end= '' )
        else:
            print( ' '+chr( ord( 'A' )+i-10 ), end= '' )
    print()
    for i in range( 10, 16 ):
        print( chr( ord( 'A' )+i-10 ), end= '' )
        for j in range( 16 ):
            c = i*16+j
            print( ' ' +chr( c ), end= '' )
        print()

# niet in boek

# mode overzicht
# 'r' alleen lezen
# 'w' alleen schrijven, inhoud wissen truncate
# 'x' alleen schrijven, moet bestaan
# 'a' alleen schrijven, aan einde

# met optioneel erbij
#   'b'  binair bestand
#   't'  tekstbestand, is default
#        'b' en 't' beide tegelijk mag niet
#   '+'  open voor update
#        mag dan zowel lezen als schrijven
#     'w+' 'w+b'  inhoud wissen truncate
#     'r+' 'r+b'  inhoud ongewijzigd laten

# zie ook 18.1 Openen en sluiten van binaire bestanden
# voor meer over 'b'

# bestanden bestaan uit bytes
#   elke byte kan slechts een beperkte waarde bevatten
#     van 0 tot en met 255
# een Python string bestaat uit Unicode karakters
#   elk karakter, code point, kan een grotere waarde hebben
#     van 0 tot en met 1114111  hex 10FFFF
# daarom voor veel Unicode karakters meer bytes nodig

# in lopende programma's bestaan
#   Python strings alleen in CPU en geheugen

# open bestand in tekst mode vertaalt
#   bij schrijven van string naar bytes
#   bij lezen van bytes naar string
# vertaalt ook newline

# de encoding bepaalt
# hoe bytes in een bestand naar Unicode karakters en
# hoe Unicode karakters naar bytes worden vertaald

# in bestand staat vaak niet welke encoding is gebruikt
# trend is om bij bytes zo veel mogelijk utf-8 te gebruiken
# dus bijvoorbeeld ook over netwerk, zoals HTML

# open() heeft ook optioneel string argument errors=
# 'strict' is default geeft error
# 'ignore' gaat gewoon verder mogelijk verlies van tekens
# 'replace' gebruikt vervangend karakter zoals '?'
# 'backslashreplace' gebruikt Python backslash escape sequence
# en meer
# https://docs.python.org/3/library/functions.html#open

# niet in boek

if 0:
    markeer( '16.6 D' )

    enc = 'ascii'
    # enc = 'mbcs'
    # enc = 'utf-8'
    # enc = 'latin-1'
    # enc = None # default, platform afhankelijk locale.getencoding()

    # er = 'backslashreplace' # geen fout, vervang karakters
    # er = 'ignore' # geen fout, laat karakters weg
    # er = 'strict' # altijd fout
    er = None # werkt als strict, is default

    with open('enctest', 'w', encoding=enc, errors=er) as fp:
        fp.write('€³²¡\u0393')
    with open('enctest', 'r', encoding=enc, errors=er) as fp:
        print(fp.read())

# read en write gebruiken intern string methodes
#   van str string naar bytes byte string met str.encode()
#   van bytes byte string naar str string met bytes.decode()
# meer over byte string b'' in boek bij 18.2.1

if 0:
    markeer( '16.6 E' )

    enc = 'utf-8' # default
    # enc = 'mbcs'
    # enc = 'latin-1'
    # enc = 'ascii'

    dec = 'utf-8' # default
    # dec = 'mbcs'
    # dec = 'utf-8'
    # dec = 'latin-1'

    # er = 'backslashreplace'
    # er = 'ignore'
    er = 'strict' # default
    
    for ch in 'a€³²¡\u0393':
        chb = ch.encode(encoding=enc, errors=er)
        # chb = ch.encode(enc, er)
        # print(type(chb))
        ch2 = chb.decode(dec, er)
        # assert ch == ch2
        print(ch, chb, ch2)

# dezelfde bytes in een bestand
# kunnen verschillende karakters opleveren
# afhankelijk van de gebruikte encoding

if 0:
    markeer( '16.6 F' )

    with open('enctest2', 'w', encoding='latin-1') as fp:
        fp.write('áéíóúàèìòù')
    with open('enctest2', 'r', encoding='latin-1') as fp:
        print(fp.read())
    with open('enctest2', 'r', encoding='cp437') as fp:
        print(fp.read())

# ook als bytes byte string object

if 0:
    markeer( '16.6 G' )

    txt = 'áéíóúàèìòù'
    txtb = txt.encode('latin-1')
    txt2 = txtb.decode('latin-1')
    txt3 = txtb.decode('cp437')
    # print(txt, txtb)
    print(txt2, txt3)

# Windows programma Speciale tekens, Character map
# %windir%\system32\charmap.exe
# geavanceerde weergave aanzetten
# zoeken met naam van karakter
#   bijvoorbeeld naar euro of delta

# Unicode Howto
# https://docs.python.org/3/howto/unicode.html

# Unicode & Character Encodings in Python: A Painless Guide 
# https://realpython.com/python-encodings-guide/

# unicodedata
# standaard Python module
# toegang tot Unicode Character Database
# https://docs.python.org/3/library/unicodedata.html

# Named Unicode character
# https://docs.python.org/3/reference/lexical_analysis.html#named-unicode-character

if 0:
    markeer( '16.6 H' )

    import unicodedata

    ch = '€'
    # ch = '\N{euro sign}'
    # print(ch, len(ch))

    name = unicodedata.name( ch )
    print( name ) # EURO SIGN

    # name = 'euro sign' # ok
    # name = 'euro'      # KeyError

    ch2 = unicodedata.lookup( name )
    print( ch2 )

#=============================================================#
# Wat je geleerd hebt

# - Tekstbestanden
# - Pointers
# - Openen en sluiten van bestanden met open() en close()
# - Lezen met read(), readline(), en readlines()
# - Schrijven met write() en writelines()
# - Toevoegen aan bestanden
# - os.path methodes exists(), isfile(), isdir(), join(), basename(), dirname(),
#   en getsize()
# - Omgaan met tekstbestanden met verschillende encoding mechanismes

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 16.1
# Opgave 16.2
# Opgave 16.3
# Opgave 16.4
# Opgave 16.5

###############################################################
# 17 Exceptions

#=============================================================#
# 17.1 Errors en exceptions

if 0:
    markeer( '17.1 A' )

    # ZeroDivisionError als input 0
    from pcinput import getInteger

    num = getInteger( "Geef een getal: " )
    print( "3 gedeeld door {} is {}".format( num, 3/num ) )
    print( "Tot ziens!" )

if 0:
    markeer( '17.1 B' )

    from pcinput import getInteger

    num = getInteger( "Geef een getal: " )
    if num == 0:
        print( "Je kunt niet delen door nul" )
    else:
        print( "3 gedeeld door {} is {}".format( num, 3/num ) )
    print( "Tot ziens!" )

#=============================================================#
# 17.2 Afhandelen van exceptions

#-------------------------------------------------------------#
# 17.2.1 try ... except

# try:
#     <acties>
# except:
#     <exception afhandeling>

if 0:
    markeer( '17.2.1 A' )

    # listing1701.py
    from pcinput import getInteger

    num = getInteger( "Geef een getal: " )
    try:
        print( "3 gedeeld door {} is {}".format( num, 3/num ) )
    except:
        print( "Je kunt niet delen door nul" )
    print( "Tot ziens!" )

if 0:
    markeer( '17.2.1 B' )

    # listing1702.py
    # spelfout Geen een getal -> Geef een getal
    from pcinput import getInteger

    num = getInteger( "Geef een getal: " )
    try:
        print( "3 gedeeld door {} is {}".format( num, 3/num ) )
        print( "3 gedeeld door {}-3 is {}".format( num, 3/(num-3) ) )
    except:
        print( "Je kunt niet delen door nul" )
    print( "Tot ziens!" )

#-------------------------------------------------------------#
# 17.2.2 Afhandeling van specifieke exceptions

# print( 3 / int( input( "Geef een getal: " ) ) )

if 0:
    markeer( '17.2.2 A' )

    # listing1703.py
    # probeer ook met Ctrl+C voor KeyboardInterrupt
    try:
        print( 3 / int( input( "Geef een getal: " ) ) )
    except ZeroDivisionError:
        print( "Je kunt niet delen door nul" )
    except ValueError:
        print( "Je gaf geen getal" )
    # niet in boek
    except KeyboardInterrupt:
        print("Ctrl+C onderschept")
        # probeer in IDLE of cmd.exe ook Ctrl+D en Ctrl+Z
        #   soms EOFError
    print( "Tot ziens!" )

if 0:
    markeer( '17.2.2 B' )

    # listing1704.py
    try:
        print( 3 / int( input( "Geef een getal: " ) ) )
    except ZeroDivisionError:
        print( "Je kunt niet delen door nul" )
    except ValueError:
        print( "Je gaf geen getal" )
    except:
        print( "Iets onverwachts ging fout" )
    print( "Tot ziens!" )

# specifieke exceptions
# ZeroDivisionError
# IndexError
# KeyError
# IOError
# FileNotFoundError
# ValueError
# TypeError

#-------
# Opgave

if 0:
    markeer( '17.2.2 Opgave A' )

    # listing1705.py
    fruitlist = ["appel", "banaan", "kers"]
    try:
        num = input( "Geef een getal: " )
        if "." in num:
            num = float( num )
        else:
            num = int( num )
        print( fruitlist[num] )
    except:
        print( "Er ging iets fout" )

if 0:
    markeer( '17.2.2 Opgave B' )

    fruitlist = ["appel", "banaan", "kers"]
    try:
        num = input( "Geef een getal: " )
        if "." in num:
            num = float( num )
        else:
            num = int( num )
        print( fruitlist[num] )    # 0
    except ValueError:             # a
        print( "Geen getal" )
    except IndexError:             # 5
        print( "Ongeldige index" )
    except TypeError:              # 1.0
        print( "Ongeldig type" )
    except KeyboardInterrupt:      # Ctrl+C
        print( "Ctrl+C gebruikt" )
    except:
        print( "Er ging iets fout" )

#-------------------------------------------------------------#
# 17.2.3 Toevoegen van een else

if 0:
    markeer( '17.2.3' )

    # listing1706.py
    try:
        num = 3 / int( input( "Geef een getal: " ) )
    except ZeroDivisionError:
        print( "Je kunt niet delen door nul" )
    except ValueError:
        print( "Je gaf geen getal" )
    except:
        print( "Iets onverwachts ging fout" )
    else:
        print( num )
    print( "Tot ziens!" )

#-------------------------------------------------------------#
# 17.2.4 Toevoegen van een finally

if 0:
    markeer( '17.2.4 A' )

    # listing1707.py
    try:
        fp = open( "pc_rose.txt" )
        # fp = open( "pc_rose2.txt" ) # niet in boek: open hoort buiten de try
        print( "Bestand geopend" )
        print( fp.read() )
    finally:
        fp.close()
        print( "Bestand gesloten" )

# niet in boek
# bovenstaande code bevat een fout
# probeer een niet bestaand bestand te openen
# dan mislukt open en wordt fp niet toegekend
#   daarna wordt fp toch gebruikt bij fp.close()

if 0:
    markeer( '17.2.4 B' )

    try:
        fp = open( "pc_rose.txt" ) # probeer ook pc_rose2.txt
        try:
            print( "Bestand geopend" )
            print( fp.read() )
        finally:
            fp.close()
            print( "Bestand gesloten" )
    except FileNotFoundError:
        print( 'Bestand niet gevonden' )

# niet in boek
# als toekenning assignment niet wordt gedaan
# vanwege exception of andere oorzaak
# controle mogelijk met try except NameError

# beter om zulke situaties te voorkomen
# kan bijvoorbeeld aan begin toevoegen
#   val = None

if 0:
    markeer( '17.2.4 C' )

    import random
    for i in range(10):
        # val = None  # voorkomt probleem
        if False: # True
            try:
                val = open('bestaatniet22')
            except FileNotFoundError:
                pass
        else:        
            if random.random() < 0.5:
                val = i

        # val
        try:
            val
        except NameError:
            print('val niet toegekend')
        else:
            print('val bevat', val)
            del val

#-------------------------------------------------------------#
# 17.2.5 Informatie over een exception

if 0:
    markeer( '17.2.5 A' )

    # listing1708.py
    try:
        print( int( "GeenInteger" ) )
    except ValueError as ex:
        print( ex.args )

        # niet in boek
        print( ex )
        print( type(ex) )
        print( str(ex) )
        print( repr(ex) )

if 0:
    markeer( '17.2.5 B' )

    # listing1709.py
    try:
        fp = open( "GeenBestand" )
        fp.close()
    except IOError as ex:
        print( ex.args )

        # niet in boek
        print( ex )
        print( type(ex) )
        print( str(ex) )
        print( repr(ex) )

if 0:
    markeer( '17.2.5 C' )

    # niet in boek

    # exception variabelen verdwijnen
    #   bij verlaten van exception scope
    #   dit voorkomt circulaire referenties
    # kan exception variabele toch bewaren
    #   via assignment naar andere variabele
    #   binnen exception scope
    # https://docs.python.org/3/reference/compound_stmts.html#except-clause

    try:
        fp = open( "GeenBestand" )
        fp.close()
    except IOError as ex:
        print( 'in except', ex )
        a = 10
        print( 'in except',  a )
        ex2 = ex

    # print( 'after', ex ) # NameError
    print( 'after', a )
    print( 'after', ex2 )
    # print( 'after', fp ) # NameError

if 0:
    markeer( '17.2.5 D' )

    # niet in boek

    # IOError is alias van OSError vanaf versie 3.3
    # https://docs.python.org/3/library/exceptions.html#IOError

    # ingebouwde exceptions
    # https://docs.python.org/3/library/exceptions.html#exception-hierarchy

    # sys.exc_info() # geeft info over exceptions
    #   alleen binnen exception scope
    #   tuple (type(ex), ex, ex.__traceback__)
    # https://docs.python.org/3/library/sys.html#sys.exc_info

    # sys.exception() # ex info vanaf versie 3.11
    # https://docs.python.org/3/library/sys.html#sys.exception

    # finally heeft geen extra parameter
    #   fout: finally Exception as ex

    import sys

    try:
        fp = open( "GeenBestand" )
        fp.close()
    except:
    # except Exception as ex:
        print( 'in except', sys.exc_info() )
        print( 'in except', sys.exception() )
        # print( 'in except', ex )
    finally:
    # finally Exception:       # syntax error
    # finally Exception as ex: # syntax error
        print( 'in finally', sys.exc_info() )
        print( 'in finally', sys.exception() )

    print( 'after', sys.exc_info() )
    print( 'after', sys.exception() )

#-------------------------------------------------------------#
# 17.2.6 Advies voor het gebruik van een exception

# als exception wordt opgevangen
#   handel dan netjes af
#   beter niet negeren
#     als toch negeren dan in commentaar waarom
#   gebruik zo specifiek mogelijke exception

# niet in boek
# The Ultimate Guide to Error Handling in Python
# https://blog.miguelgrinberg.com/post/the-ultimate-guide-to-error-handling-in-python

#=============================================================#
# 17.3 Exceptions bij bestandsmanipulatie

# in errno module
# errno.ENOENT # niet gevonden
# errno.EACCES # geen toegang
# errno.ENOSPC # onvoldoende ruimte

if 0:
    markeer( '17.3' )

    # listing1710.py
    import errno

    try:
        fp = open( "GeenBestand" )
        fp.close()
    except IOError as ex:
        if ex.args[0] == errno.ENOENT:
            print( "Bestand niet gevonden!" )
        else:
            print( ex.args[0], ex.args[1] )

#=============================================================#
# 17.4 Genereren van exceptions

if 0:
    markeer( '17.4 A' )

    # listing1711.py
    def getStringLenMax10( prompt ):
        s = input( prompt )
        if len( s ) > 10:
            raise ValueError( "Lengte groter dan 10", len( s ) )
        return s

    print( getStringLenMax10( "Gebruik 10 tekens of minder: " ) )

if 0:
    markeer( '17.4 B' )

    # listing1712.py
    fp = open( "pc_rose.txt ")
    try:
        buf = fp.read()
        print( buf )
    except IOError:
        # print( 'in except' ) # niet in boek
        fp.close()
        raise
    fp.close()

# niet in boek
# bij with wordt fp.close() automatisch gedaan
#   ook bij exception tijdens read

if 0:
    markeer( '17.4 C' )

    # werkt als code hierboven
    with open( "pc_rose.txt ") as fp:
        buf = fp.read()
        print( buf )

if 0:
    markeer( '17.4 D' )

    # niet in boek
    # lock file, alleen voor Windows
    # https://stackoverflow.com/questions/60709401/opening-a-file-on-windows-with-exclusive-locking-in-python

    # voor forceren exception
    #   bij 17.4 B of C hierboven
    #   PermissionError, afgeleid van OSError/IOError
    # of OSError Errno 36 Resource deadlock avoided
    #   na ongeveer 10 secondes bij 2e run
    #   terwijl 1e nog op locked staat

    # in cmd.exe
    #   py ProgrammeersleerlingExtra.py

    import msvcrt

    fil = "pc_rose.txt"
    with open( fil ) as fp:
        msvcrt.locking( fp.fileno(), msvcrt.LK_LOCK, 1000 )
        input( 'locked ' + fil + ' ; press Enter to unlock' )
        msvcrt.locking( fp.fileno(), msvcrt.LK_UNLCK, 1000 )

if 0:
    markeer( '17.4 E' )

    # niet in boek

    # kan meer Exception types opvangen
    #   in dezelfde except

    # kan Ctrl+C opvangen
    #   maar Ctrl+Break niet

    # kan eigen Exception types maken

    import sys

    # zie ook hoofdstuk 20 Object Oriëntatie
    # https://docs.python.org/3/library/exceptions.html#exception-hierarchy

    if 0:
        # bij raise
        # TypeError: exceptions must derive from BaseException
        class TestException:
            pass

    if 0:
        class TestException( BaseException ):     # ok
        # class TestException( KeyboardInterrupt ): # ok
        # class TestException( Exception ):         # ok
        # class TestException( ValueError ):        # ok
            pass

    prompt = 'Tekst> '
    while True:
        try:
            inp = input( prompt ) # a, 0, 9, 1
            val = int(inp)
            dv = 1 / val
            ch = prompt[val]
            # raise TestException

        # SyntaxError: multiple exception types must be parenthesized
        # except ValueError, IndexError: 
        # except ValueError, IndexError as ex:

        # TypeError: catching classes that do not inherit
        #   from BaseException is not allowed
        # except [ValueError, IndexError,
        #      ZeroDivisionError] as ex:
        except (ValueError, IndexError,
              ZeroDivisionError) as ex:
            print( 'Exception:', ex )
            print( '', repr( ex ) )

        # except TestException as ex:
        #     print( 'Test exception:', str(ex) )
        #     print( '', repr( ex ) )

        # SyntaxError: invalid syntax
        # if binnen except lijst mag niet
        # alleen except mogelijk na try
        # if 0:
        #    except KeyboardInterrupt as ex:
        #        pass
        # try:
        #     pass

        # except: werkt wel bij Ctrl+C
        #   print KeyboardInterrupt()

        # maar niet bij Ctrl+Break
        #   in cmd.exe: print '^C' en stopt programma
        #   in IDLE: geen effect, doet niets
        # zie voor toetsenborden zonder Break toets
        # https://en.wikipedia.org/wiki/Break_key#Keyboards_without_Break_key

        # except KeyboardInterrupt as ex:
        #     print( 'Keyboard exception:', str(ex) )
        #     print( '', repr( ex ) )

        # SyntaxError: invalid syntax
        # except as ex:
        except:  
            ex = sys.exception()
            print( 'Other exception:', str(ex) )
            print( '', repr( ex ) )

        # SyntaxError: default except must be last
        # except TestException as ex:
        #     print( 'Test exception:', str(ex) )
        #     print( '', repr( ex ) )

#=============================================================#
# Wat je geleerd hebt

# - Exception afhandeling
# - except, else, en finally
# - ZeroDivisionError, IndexError, KeyError, IOError, ValueError, TypeError, en
#   FileNotFoundError
# - Informatie uit exceptions halen
# - Exceptions bij bestandsmanipulatie
# - raise

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 17.1

###############################################################
# 18 Binaire Bestanden

#=============================================================#
# 18.1 Openen en sluiten van binaire bestanden

if 0:
    markeer( '18.1 A' )

    fp = open( "pc_rose.txt", "rb" )
    fp.close()

    # open mode
    # tekst    binair
    # r  rt    rb    alleen lezen
    # r+ r+t   r+b   lezen en schrijven, niet leeg gemaakt
    # w  wt    wb    alleen schrijven, eerst leeg gemaakt
    # w+ w+t   w+b   lezen en schrijven, eerst leeg gemaakt, truncate

    # x  als w, bestand mag niet bestaan
    # a  als w, schrijft aan einde, append

    # volgorde van letters niet van belang
    #   r+t, +rt, t+r, tr+ ... mag ook

    # https://docs.python.org/3/library/functions.html#open

if 0:
    markeer( '18.1 B' )

    # niet in boek
    # ValueError: Must have exactly one of
    #   create/read/write/append mode and at most one plus
    with open( "pc_rose.txt", "b" ): # alleen b mag niet
        pass

if 0:
    markeer( '18.1 C' )

    # niet in boek
    with open( "pc_rose.txt", "br" ) as fin:
        data = fin.read()

    fname = "pc_rose2.txt"
    with open( fname, "wb" ) as fout:
        fout.write( data )

    # alleen-lezen kenmerk, readonly attribute aan
    # chmod niet in boek 15.4 os functies
    import os
    import stat
    os.chmod( fname, ~stat.S_IREAD ) # hoe uitzetten?
    # os.chmod( fname, stat.S_IWRITE ) # niet zo
    # https://docs.python.org/3/library/os.html#os.chmod

    # PermissionError
    with open( fname, "wb" ) as fin:
        pass

#=============================================================#
# 18.2 Lezen uit een binair bestand

#-------------------------------------------------------------#
# 18.2.1 Byte strings

if 0:
    markeer( '18.2.1 A' )

    hw1 = "Hello, world!"
    hw2 = b"Hello, world!"
    print( hw1 )
    print( hw2 )

if 0:
    markeer( '18.2.1 B' )

    hw1 = "Hello,\x20world!"
    print( hw1 )

if 0:
    markeer( '18.2.1 C' )

    print( "Hello,\x00\x01\x02world!" )

    # niet in boek
    # hoe string met \x00 op scherm eruit ziet
    #   is niet afhankelijk van Python
    #   maar van het programma dat de weergave doet
    # Python stuurt print output naar stdout
    #   heeft geen invloed meer op wat er daarna mee gebeurt
    
    # IDLE 3.13.12
    #   Hello,  world!
    # Thonny 4.1.7
    #   Hello,
    # Windows 11, Windows Terminal, Opdrachtprompt cmd.exe
    #   Hello,world!
    # Windows 10, Console Window Host, Opdrachtprompt cmd.exe
    #   Hello, ☺☻world!

if 0:
    markeer( '18.2.1 D' )

    # niet in boek
    print( len( "Hello,\x00\x01\x02world!" ) )
    #            123456  7   8   9 012345  len 15

    # \x00 - \x1f en \xff zijn speciale tekens
    #   kunnen in een Python string staan
    #   ook al geeft print soms geen output
    print( "Hello,\x01\x02\x03world!" )

    # eigenaardig gedrag in IDLE
    #   zonder markeren 2 posities
    #   tussen Hello en world zichtbaar
    #   bij markeren van alleen (deel) speciale tekens
    #     soms toch 3 positie zichtbaar

if 0:
    markeer( '18.2.1 E' )

    print( b"Hello,\x00\x01\x02world!" )

if 0:
    markeer( '18.2.1 F' )

    # listing1801.py
    hw1 = "Hello, world!"
    hw2 = b"Hello, world!"

    for c in hw1:
        print( c, end=" " )
    print()
    for c in hw1:
        print( ord( c ), end=" " )
    print()
    for c in hw2:
        print( c, end=" " )

if 0:
    markeer( '18.2.1 G' )

    # listing1802.py
    bs = bytes( [72,101,108,108,111,44,32,119,111,114,108,100,33] )
    print( bs )
    bch = bytes( [72] )
    print( bch )
    fout = bytes( 72 )
    print( fout )

if 0:
    markeer( '18.2.1 H' )

    hw1 = b"Hello, world!"
    hw2 = str( hw1 )
    print( hw2 )

    # niet in boek
    print( len( hw2 ) ) # inclusief b en quotes
    print( type( hw2 ), 'hw2' )
    print( type( hw1 ), 'hw1' )

    hw3 = "b'Hello, world!'" # literal, letterlijke conversie
    print( hw3 )
    print( type( hw3 ), 'hw3' )
    print( len( hw3 ) )

    # hw4 = bytes(hw2) # TypeError

if 0:
    markeer( '18.2.1 I' )

    # listing1803.py
    hw1 = b"Hello, world!"
    hw2 = hw1.decode( "utf-8" )
    print( hw2 )
    hw3 = hw2.encode( "utf-8" )
    print( hw3 )

if 0:
    markeer( '18.2.1 J' )

    # niet in boek
    hw1 = b"Hello, world!"
    hw2 = hw1.decode() # default is utf-8
    print( hw2 )
    hw3 = hw2.encode() # default is utf-8
    print( hw3 )

#-------------------------------------------------------------#
# 18.2.2 Demonstratie binair lezen

if 0:
    markeer( '18.2.2 A' )

    # listing1804.py
    fp = open( "pc_rose.txt", "rb" )
    for i in range( 10 ):
        buffer = fp.read( 10 )
        print( buffer )
    fp.close()

if 0:
    markeer( '18.2.2 B' )

    # niet in boek
    with open( "pc_rose.txt", "rb" ) as fp:
        for line in fp:
            print( line )

if 0:
    markeer( '18.2.2 C' )

    # niet in boek
    with open( "pc_rose.txt", "rb" ) as fp:
        while True:
            line = fp.readline()
            if not line:
                break
            print( line )

#=============================================================#
# 18.3 Schrijven in een binair bestand

if 0:
    markeer( '18.3 A' )

    # listing1805.py
    from os.path import getsize

    NAAM = "pc_binarytest.tmp"

    fp = open( NAAM, "wb" )
    fp.write( b"And now for something completely different...\x0A\
\x00\x00\x00\x00\xD4\xE8\xE5\xA0\xD3\xF0\xE1\xEE\xE9\xF3\xE8\xA0\
\xC9\xEE\xF1\xF5\xE9\xF3\xE9\xF4\xE9\xEF\xEE\x00\x00\x00" )
    fp.close()

    print( getsize( NAAM ), "bytes geschreven" )

if 0:
    markeer( '18.3 B' )

    # niet in boek
    fp = open( 'testprint.bin', 'wb') # met 'w' wel ok
    print( 'a', 'b', file=fp )   # TypeError  mag niet
    # print( b'a', file=fp )  # mag niet
    fp.close()

if 0:
    markeer( '18.3 C' )

    # listing1806.py
    NAAM = "pc_binarytest.tmp"

    # niet in boek: latin-1, latin1 en 'latin -1' werken ook
    # zonder encoding werkt ook
    # fp = open( NAAM )
    fp = open( NAAM, encoding="latin-1" )
    # fp = open( NAAM, encoding="utf-8" ) # werkt niet UnicodeDecodeError
    while True:
        buffer = fp.readline()
        if buffer == "":
            break
        print( buffer )
    fp.close()

#-------
# Opgave

if 0:
    markeer( '18.3 Opgave A' )

    # in binaire modus
    NAAM = "pc_binarytest.tmp"

    fp = open( NAAM, 'rb' )
    while True:
        line = fp.readline()
        if not line: # line == b'':
            break

        print(line)

        # \x00 stopt soms print
        #   is afhankelijk van weergave programma
        #   zie 18.2.1 C
        # line = line.replace(b'\x00', b'')

        # print(line.decode('latin-1'))

        # print(line.decode()) # utf-8 default, UnicodeDecodeError
        # print(line.decode(errors='ignore'))

        # replace en backslashreplace werken niet op \x00

        # print(line.decode(errors='replace'))
        # print(line.decode(errors='backslashreplace'))

    fp.close()

if 0:
    markeer( '18.3 Opgave B' )

    # in binaire modus
    NAAM = "pc_binarytest.tmp"

    with open( NAAM, 'rb' ) as fp:
        while True:
            ch = fp.read(1)
            if ch == b"":
               break
            print( chr( ch[0] ), end='' ) # [] byte to int

if 0:
    markeer( '18.3 Opgave C' )

    # in binaire modus
    NAAM = "pc_binarytest.tmp"

    with open( NAAM, 'rb' ) as fp:
        for line in fp:
            print(line.decode('latin-1'))

if 0:
    markeer( '18.3 D' )

    # niet zo duidelijk in boek
    # zie ook 18.2.1 Byte strings
    #   in listing1801.py

    # bytes [] anders dan bij str

    tst = b'abcd'
    print(tst[0], type(tst[0]))     # int
    print(tst[0:1], type(tst[0:1])) # bytes

    tst = 'abcd'
    print(tst[0], type(tst[0]))     # str
    print(tst[0:1], type(tst[0:1])) # str

#=============================================================#
# 18.4 Positioneren van de pointer

if 0:
    markeer( '18.4 A' )

    # listing1807.py
    fp = open( "pc_binarytest.tmp", "rb" )
    print( "1. Huidige positie van de pointer is", fp.tell() )
    fp.seek( 50 )
    print( "2. Huidige positie van de pointer is", fp.tell() )
    buffer = fp.read( 23 )
    print( "3. Huidige positie van de pointer is", fp.tell() )
    fp.close()

    print( buffer )
    s = ""
    for c in buffer:
        s += chr( c-128 )
    print( "De geheime boodschap is:", s )

if 0:
    markeer( '18.4 B' )

    # niet in boek
    # zie ook 18.2.1 Byte strings
    #   listing1801.py
    # anders dan bij str
    tst = b'abcd'
    for c in tst:
        print(c, type(c), bytes([c]), chr(c)) # int, int, bytes, str

    tst = 'abcd'
    for c in tst:
        print(c, type(c), ord(c), bytes([ord(c)])) # str, str, int, bytes

if 0:
    markeer( '18.4 C' )

    # niet in boek
    import os
    fp = open( "pc_binarytest.tmp", "rb")
    # fp = open( "pc_binarytest.tmp", "r+b")
    print( "1. Huidige positie van de pointer is", fp.tell() )

    fp.seek( 50 ) # SEEK_SET is 0 is default
    print( "2. Huidige positie van de pointer is", fp.tell() )
    buffer = fp.read( 23 )
    print( "3. Huidige positie van de pointer is", fp.tell() )

    fp.seek(-23, os.SEEK_CUR) # SEEK_CUR is 1
    print( "4. Huidige positie van de pointer is", fp.tell() )
    print('huidige pos', fp.seek(0, os.SEEK_CUR))

    print('lengte is', fp.seek(0, os.SEEK_END)) # SEEK_END is 2
    # x = fp.read(1)
    # print(x)
    print( "5. Huidige positie van de pointer is", fp.tell() )

    print('ver voorbij', fp.seek(10_000_000))
    # fp.write( b'x' ) # io.UnsupportedOperation: write bij 'rb'
                       # maakt bestand groter met r+b
    # fp.write( b'' )  # maakt niet groter
    print( "6. Huidige positie van de pointer is", fp.tell() )
    fp.close()
    
if 0:
    markeer( '18.4 D' )

    # niet in boek
    # binaire append mode
    with open( "pc_binarytest.tmp", 'ab' ) as fp:
        fp.write( b'append' )
    with open( "pc_binarytest.tmp" ) as fp:
        s = fp.read()
        s = s.replace('\x00', '')
        print( s )

#-------
# Opgave

if 0:
    markeer( '18.4 Opgave' )

    # eerst bestand met verborgen geheim vullen
    #   door listing1805.py hierboven aanzetten
    #   anders resultaat ValueError
    with open( "pc_binarytest.tmp", "r+b" ) as fp:
        fp.seek( 50 )
        buffer = fp.read( 23 )
        if 1: # 0
            # via byte string
            s = b''
            for ch in buffer:
                s += bytes( (ch - 128,) ) # als ValueError dan .tmp opnieuw maken
            # print(s)
        else:
            # naar str en terug
            s = ''
            for c in buffer:
                s += chr( c - 128 ) # bij fout: vergeten .tmp opnieuw te maken
            s = s.encode()
            # print( s )
        fp.seek( 50 )
        fp.write( s )
    with open( "pc_binarytest.tmp" ) as fp:
        s = fp.read()
        s = s.replace('\x00', '')
        print( s )
        
#=============================================================#
# Wat je geleerd hebt

# - Binaire bestanden voor lezen, schrijven, en lezen plus schrijven
# - Binaire read() en write()
# - Byte strings
# - Conversie tussen strings en byte strings via encode() en decode()
# - seek() en tell() methodes

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 18.1
# Opgave 18.2
# Opgave 18.3
# Opgave 18.4

###############################################################
# 19 Bitsgewijze Operatoren

#=============================================================#
# 19.1 Bits en bytes

# bit is kleinste data-eenheid die computer kan manipuleren

# groepjes bits kleinste eenheid byte bestaat uit 8 bits
#   vroeger een byte verschillende groottes
#     afhankelijk van hardware
#   daarom soms nog octet gebruikt voor echt 8 bits
# https://en.wikipedia.org/wiki/Byte

# meeste computers kunnen meer bytes manipuleren
# gebruikelijk 32-bits 4 bytes en 64-bits 8 bytes

#-------------------------------------------------------------#
# 19.1.1 Binair tellen

# byte serie enen en nullen bijvoorbeeld 11010010

# byte als geheel positief getal waarde berekenen
# meest rechtse bit is 1
# ernaast is 2, dan 4, 8, etcetera

# equivalent met decimale getallen
# meest rechtse cijfer vermenigvuldigd met 1
# ernaast met 10, dan 100, 1000, etcetera

# ook equivalent met hexadecimale getallen
# besproken in hoofdstuk 10

# nummer van bit meest rechtse 0, bit ernaast 1, dan 2, etcetera
# representeren 2**0, 2**1, 2**2, etcetera

# Byte                        1     1     0     1     0     0     1     0
# Nummer van de bit           7     6     5     4     3     2     1     0
# Gerepresenteerde waarde  2**7  2**6  2**5  2**4  2**3  2**2  2**1  2**0
# Byte waarde              2**7 +2**6   + 0 +2**4   + 0   + 0 +2**1   + 0 = 210
# niet in boek              128    64    32    16     8     4     2     1
#   Byte waarde             128 +  64 +        16 +               2 = 210

# niet in boek
# rechter bit is minst significante bit least significant bit LSB
# linker bit is meest significante bit most significant bit MSB
#  00000001  LSB is 1  MSB is 0
#  10000000  LSB is 0  MSB is 1

#-------
# Opgave

if 0:
    markeer( '19.1.1 Opgave A' )

    # van rechts naar links
    binstr = input('Geef binaire string met enen en/of nullen: ')
    # binstr = '1100'
    # binstr = '10101011'
    # binstr = '10110011010100101010100111010111'
    factor = 1
    totaal = 0
    for i in range(len(binstr)):
        ch = binstr[-1-i]
    # for ch in binstr[::-1]:
    # for ch in reversed(binstr):
        # factor = pow(2, i)
        # factor = 2 ** i
        # print(ch, factor, i)
        if ch == '1':
            totaal += factor
        factor *= 2
        # factor = pow(2, (i + 1))
        # factor = 2 ** (i + 1)
    print(binstr, totaal)

if 0:
    markeer( '19.1.1 Opgave B' )

    # niet in boek
    # van links naar rechts

    s = input('geef een binair getal ')

    factor = 2**(len(s)-1) # of 1 << len(s)-1
    res = 0
    for ch in s:   
        if int(ch):
        # if ch == "1": # if ch != '0':
        # if ch: # is niet goed, ch is '0' of '1'
            res += factor
        factor //= 2 # of factor >>= 1
        
    print(res)

if 0:
    markeer( '19.1.1 Opgave C' )

    # niet in boek
    # van links naar rechts, beter

    bin = input('geef een binair getal ')

    dec = 0
    for dig in bin:
        dec = dec * 2 + int(dig)

    print(dec)

# laagste getal in byte is 00000000 = 0
# hoogste getal in byte is 11111111 = 255
# 256 verschillende waarden mogelijk als byte
# manier is unsigned, bestaat ook nog signed

#-------------------------------------------------------------#
# 19.1.2 Codering van tekens

# ASCII is eenvoudigste basale codering
# tabel zie hoofdstuk 10
# codes van 32 (hex 20) tot en met 126 (hex 7E)
# codes van 0 tot en met 31 zijn controle tekens zoals newline
# code 127 (hex 7F) is Del teken
# geen andere codes, 7 bits voldoende
# 0 = 00000000 tot en met 127 = 01111111

# ASCII gebruikt alleen onderste 128 waarden van byte
# andere coderingen
# gebruiken ook bovenste 128 waarden
#   die allemaal beginnen met het linker bit 1
# gebruiken alle 256 mogelijke waarden
# zoals latin-1 zie hoofdstuk 16
# helaas gebruiken niet alle coderingen dezelfde tekens
#   voor 128 tot en met 255
# meeste gebruiken wel dezelfde tekens als ASCII
#   voor 0 tot en met 127

# niet in boek
# het is logisch dat er veel coderingen zijn met andere karakters
#  voor 128 tot en met 255
# doel van codering is juist dat er tekens in staan
#    die niet in ASCII of een andere codering beschikbaar zijn
# zie bijvoorbeeld https://en.wikipedia.org/wiki/Code_page

# Python is gebaseerd op Unicode codering
#   het gebruikt UTF8 encoding
# zie ook hoofdstuk 10 en 16

# UTF-8 teken bestaat uit 1 tot en met 4 bytes
# byte met linker bit 0 is ASCII teken
# byte met linker bit 1 is begin van sequentie
#   van meerdere bytes die samen 1 teken vormen
# sequentie bestaat uit leidende meest linker byte
#   en 1 of meer volgende bytes
# leidende byte bestaat uit links naar rechts
#   aantal van 1 tot en met 6 keer bit 1
#   gevolgd door een bit 0 en nog resterende bits
#   geeft aan totale lengte van sequentie
# bijvoorbeeld 1110xxxx iedere x is een bit dan
#   sequentie inclusief leidende byte is 3 bytes lang
# sequentie lengte minimaal 2 bytes
#    maximum 6 bytes, dan leading byte 1111110x
# iedere byte na leidende byte begint met linker bits 10
# in praktijk sequentie maximum 4 bytes lang
#   sommige zijn uitgesloten
# https://en.wikipedia.org/wiki/UTF-8#Description

# UTF-8 kan een zeer groot aantal tekens weergeven
# sommige bit waarden vormen geen geldig UTF-8 teken
# ieder bitpatroon in byte is een legale string in latin-1 codering
# maar in UTF-8 soms waardes niet geldig waardoor UnicodeDecodeError

# niet in boek
# Python ondersteunt ook UTF-16 en UTF-32

# er zijn bijvoorbeeld encodings voor Nederlandse tekens
# zoals https://en.wikipedia.org/wiki/Code_page_1102

# Python standard encodings
# staat cp1102 niet bij
# https://docs.python.org/3/library/codecs.html#standard-encodings

#-------------------------------------------------------------#
# 19.1.3 Coderen van getallen

# positieve getallen opgeslagen in geheugen
#   als multi-byte met linker bit 0
#   zoals hierboven in 19.1.1 over bits in byte

# negatieve getallen multi-byte
#   anders gecodeerd twee-complement systeem
# neem absolute waarde positieve versie bitpatroon
#   flip alle bits 1 naar 0 en 0 naar 1
#   en tel er 1 bij op
#   daardoor linker bit altijd 1
# bijvoorbeeld -1 is bitpatroon van 1 ...00000001
#   na flip ...11111110, dan 1 erbij ...11111111
# dus -1 sequentie alleen maar enen

# gebroken getallen float
# worden opgeslagen in wetenschappelijke notatie
#  deel van multi-byte vormt exponent

# manipuleren van bitpatronen
#   gemakkelijker met positieve getallen

# niet in boek

# 2-complement is niet symmetrisch
#   vanwege 1 erbij tellen nodig
#   als van negatief naar positief
#   na flippen van alle bits

#    +127 01111111
#      ..
#      +1 00000001
# +0 = -0 00000000
#      -1 11111111  na flippen 00000000  1 erbij 00000001  +1
#      ..
#    -127 10000001  na flippen 01111110  1 erbij 01111111  +127
#    -128 10000000  na flippen 01111111  1 erbij 10000000  blijft -128
#   voorkomt dat zowel 0 als -0 bestaan
#   byte minimum is 1 verder dan maximum
#   niet mogelijk om abs(-128) te doen
#     binnen byte type met 8 bits
#     want +128 past niet in 2-complement byte
#   Python int type heeft geen limiet van 8 bits

# alleen flippen zonder 1 erbij bestaat wel
#   is 1-complement
#   +127 01111111
#     +1 00000001
#     +0 00000000
#     -0 11111111
#     -1 11111110
#   -127 10000000
#   wordt bijna niet meer gebruikt
#   nadeel heeft +0 en -0
#   2 verschillende waarden voor 0 is onhandig

if 0:
    markeer( '19.1.3 A' )

    # 'little' of 'big'
    # little-endian op bijvoorbeeld x86 CPU
    # big-endian op bijvoorbeeld 68000 CPU

    # bi-endian op ARM PowerPC Alpha MIPS CPU
    #   schakelbaar tussen little en big

    import sys

    print('system byte order:', sys.byteorder)

if 0:
    markeer( '19.1.3 B' )

    print('int  bits  bytes big little      bin')
    for i in -257, -256, -255, -254, -2, -1, 0, 1, 2, 254, 255, 256, 257:
        print(str(i).rjust(4), '', i.bit_length(), '',
              i.to_bytes(2, 'big', signed=True), # default byteorder is big
              i.to_bytes(2, 'little', signed=True),
              bin(i))

if 0:
    markeer( '19.1.3 C' )

    print(21, 0b10101, bin(21), bin(0b10101))
    print(-21, -0b10101, bin(-21), bin(-0b10101))

    print(21, 0o25, oct(21), oct(0o25))
    print(-21, -0o25, oct(-21), oct(-0o25))

    print(21, 0x15, hex(21), hex(0x15))
    print(-21, -0x15, hex(-21), hex(-0x15))

if 0:
    markeer( '19.1.3 D' )

    print(1_49, 0b_1001_0101, bin(14_9), bin(0b_1001_0101))
    print(-14_9, -0b_1001_0101, bin(-1_49), bin(-0b_1001_0101))

if 0:
    markeer( '19.1.3 E' )

    for i in range(-3, 4):
        print(i, bin(i), int(bin(i), 2))
    print()
    for i in range(-3, 4):
        print(i, oct(i), int(oct(i), 8))
    print()
    for i in range(-3, 4):
        print(i, hex(i), int(hex(i), 16))

if 0:
    markeer( '19.1.3 F' )

    if 0:
        print( 5 .bit_length() )  # 3
        print( (5).bit_length() ) # 3
        # print( 5.bit_length() ) # SyntaxError: invalid decimal literal

    if 0:
        print( -5 .bit_length() )  # -3
        print( (-5).bit_length() ) # 3

    if 0:
        for i in 0, 1, 3, 4, 5, 7, 8, 15, 16, 255, 256, 65535, 65536:
            print( i, i.bit_length(), bin( i ) )
            # print( -i, (-i).bit_length(), bin( -i ) )
    
if 0:
    markeer( '19.1.3 G' )

    if 0:
        print( 5 .bit_count() )
        print( len('abcd') .    bit_count() ) # ook met expressie
        print( (5).bit_count() )

        print( (-5).bit_count() )

        print()

    if 0:
        for i in 0, 1, 3, 4, 5, 7, 8, 15, 16, 255, 256, 65535, 65536:
            print( i, i.bit_count(), bin( i ) )

#=============================================================#
# 19.2 Manipulatie van bits

# operatoren

# <<  shift links
# >>  shift rechts
# &   bitsgewijze and
# |   bitsgewijze or
# ~   bitsgewijze not
# ^   bitsgewijze exclusieve or    

#-------------------------------------------------------------#
# 19.2.1 Shift

# x << y schuift x naar links over y posities
# wordt rechts aangevuld met 0-bits

# x >> y schuift x naar rechts over y posities
# wordt links aangevuld met kopie van linker bit
#                       met 0-bits als positief of 0
#                       met 1-bits als negatief
# x en y beide int getallen

#   decimaal  binair
# !   33     00100001
# B   66     01000010

if 0:
    markeer( '19.2.1 A' )

    code = "!"
    print( chr(ord(code)<<1) )
    code = "B"
    print( chr(ord(code)>>1) )

# 1 naar links is verdubbelen
# 1 naar rechts is halveren

if 0:
    markeer( '19.2.1 B' )

    print( "345 verviervoudigd levert", 345<<2 )
    print( "345 gedeeld door 8 levert", 345>>3 )

    # niet in boek
    for val in 45, 10:
        print( val, bin(val), '<<1', val << 1, bin(val << 1) )
        print( val, bin(val), '>>1', val >> 1, bin(val >> 1) )

#-------------------------------------------------------------#
# 19.2.2 Bitsgewijze and

# x & y als bit van x en bit van y 1 dan
#       resultaat bit 1 anders 0

#   decimaal  binair
# x      11  00001011
# y       6  00000110
# x & y   2  00000010

if 0:
    markeer( '19.2.2' )

    print( 11 & 6 )

#-------
# Opgave

# and bruikbaar voor modulo macht van 2
# modulo 16 gelijk aan and 15

if 0:
    markeer( '19.2.2 Opgave' )

    print(345 % 32, 345 & 31, 345 % 32 == 345 & 31)

    # niet in boek
    print(bin(345), 345)
    print(str(bin(31)).rjust(11), 31)
    print(str(bin(345 & 31)).rjust(11), 25)

#-------------------------------------------------------------#
# 19.2.3 Bitsgewijze or

# x | y als bit van x of bit van y 1 dan
#       resultaat bit 1 anders 0

#   decimaal  binair
# x      11  00001011
# y       6  00000110
# x | y  15  00001111

if 0:
    markeer( '19.2.3' )

    print( 11 | 6 )

#-------
# Opgave

# zetten van een bit met shift links en or
# waarde or (1 << bitpositie)

if 0:
    markeer( '19.2.3 Opgave' )

    print(2, 1 << 7, 2 | (1 << 7))

#-------------------------------------------------------------#
# 19.2.4 Bitsgewijze not

# ~x als bit van x 0 dan resultaat bit 1 anders 0

#   decimaal  binair
# x      11  00001011
# ~x    -12  11110100

# niet -11 vanwege 2-complement zie 19.1.3

if 0:
    markeer( '19.2.4 A' )

    print( ~11 )

if 0:
    markeer( '19.2.4 B' )

    # niet in boek
    # x     -12  11110100
    # ~x     11  00001011
    print( -12, bin( -12) )
    print( ~-12, bin( ~-12 ))
    print( ~~11, bin( ~~11 ) )

#-------
# Opgave

# uitzetten van een bit met shift links, not en and
# waarde and (not (1 << bitpositie))

if 0:
    markeer( '19.2.4 Opgave' )

    # formule:
    # result = begin & ~(1 << shift)
    
    shift = 3 # index 3, 4e bit van rechts
    # shift = 2
    # shift = 1
    # shift = 0 # 0 = no shift
    begin = 15
    # begin = 65535
    # begin = 2 ** 16 - 1
    print(begin, 1 << shift, begin & ~(1 << shift))
    print(bin(begin))
    print(bin(1 << shift))
    # print(bin(~(1 << shift)))
    print(bin(begin & ~(1 << shift)))

if 0:
    markeer( '19.2.4 C' )

    # niet in boek
    # voor binaire representatie van bits
    def two_complement(val, bitcount=8):
        # https://stackoverflow.com/questions/28383585/how-to-get-binary-representation-of-negative-numbers-in-python
        if 1:
            twocomp0 = 1 << bitcount   # 100000000
            # twocomp0 = 2 ** bitcount
            twocomp1 = twocomp0 - 1    #  11111111
            twocomp2 = twocomp1 & val  #  xxxxxxxx  combine
            twocomp3 = bin(twocomp2)   # to str
            twocomp4 = twocomp3[2:]    # remove 0b
            twocomp = twocomp4.rjust(bitcount, '0') # prepend 0 for positive
        if 0:
            twocomp = bin(((1 << bitcount) - 1) & val)[2:].rjust(bitcount, '0')

        # https://stackoverflow.com/questions/21871829/twos-complement-of-numbers-in-python
        if 0:
            # twocomp = format(val, '0' + str(bitcount) + 'b') # - aan begin bij negatief
            twocomp = format(val % (1 << bitcount), '0' + str(bitcount) + 'b') # ok

        # https://stackoverflow.com/questions/52850229/how-to-print-binary-numbers-using-f-string-instead-of-format
        if 0:
            # twocomp = f'{val:>0{bitcount}b}' # - middenin bij negatief
            twocomp = f'{val % (1 << bitcount):>0{bitcount}b}'
            
        return twocomp

    print(two_complement(11))
    print(two_complement(-12))

    print(two_complement(11, 16))
    print(two_complement(-12, 16))


#-------------------------------------------------------------#
# 19.2.5 Bitsgewijze xor

# x ^ y als bit van x gelijk aan bit van y dan
#       resultaat bit 0 anders 1

# linker en rechter bit allebei 0 of allebei 1 dan resultaat 0
# linker en rechter bit verschillend dan resultaat 1

#   decimaal  binair
# x      11  00001011
# y       6  00000110
# x ^ y  13  00001101

# x ^ y  13  00001101
#         6  00000110 voor terug
#        11  00001011 2x xor geeft origineel

# x ^ y  13  00001101
#        11  00001011 voor terug
#         6  00000110 2x xor geeft origineel

if 0:
    markeer( '19.2.5 A' )

    print( 11 ^ 6 )

if 0:
    markeer( '19.2.5 B' )

    # niet in boek
    print( bin(11) )
    print( ' ' + bin(6) )
    print( bin(11 ^ 6) )

if 0:
    markeer( '19.2.5 C' )

    # niet in boek
    print( 11 ^ 6 ^ 6 )
    print( 11 ^ 6 ^ 11 )

if 0:
    markeer( '19.2.5 D' )

    # niet in boek
    # x ^ y
    # inputs  output
    # x   y
    # 0   0     0
    # 0   1     1
    # 1   0     1
    # 1   1     0

    # xor poort Wikipedia
    # https://en.wikipedia.org/wiki/XOR_gate

    print('x y xor')
    for x in range(2):
        for y in range(2):
            print(x, y, x ^ y)
    
if 0:
    markeer( '19.2.5 E' )
    
    # niet in boek
    # x ^ y ^ z
    #   inputs    output
    # x   y   z
    # 0   0   0     0
    # 0   0   1     1
    # 0   1   0     1
    # 0   1   1     0
    # 1   0   0     1
    # 1   0   1     0
    # 1   1   0     0
    # 1   1   1     1

    # Single 3-Input Positive-XOR Gate
    # https://www.ti.com/lit/ds/symlink/sn74lvc1g386.pdf

    print('x y z xor')
    for x in range(2):
        for y in range(2):
            for z in range(2):
                print(x, y, z, x ^ y ^ z)

#-------
# Opgave

# xor gemakkelijke manier om te versleutelen met masker
# waarde xor masker wordt versleutelde waarde
# originele waarde is versleutelde waarde xor masker

if 0:
    markeer( '19.2.5 Opgave' )

    masker = 12345
    # masker = 0b1001_1101_1100_1011 # 40395
    print('masker', masker)
    getal = 73831
    print('getal', getal)
    versleuteld = getal ^ masker
    print('versleuteld', versleuteld)
    onversleuteld = versleuteld ^ masker
    print('onversleuteld', onversleuteld)
    
#-------------------------------------------------------------#
# 19.2.6 Afhandelingsvolgorde van bitsgewijze operatoren

# bitsgewijze operatoren worden niet afgehandeld voor andere operatoren
# gebruik haakjes voor juiste volgorde

#  1<<1 + 2<<1 is niet 1*2 + 2*2 maar is
# (1<<(1+2))<<1 is 1*8*2

if 0:
    markeer( '19.2.6' )

    print( 1<<1 + 2<<1 )
    print( (1<<1) + (2<<1) )

    # zie
    # https://docs.python.org/3/reference/expressions.html#operator-precedence

#=============================================================#
# 19.3 Het nut van bitsgewijze operaties

# wat bitsgewijze operatoren doen
# kan ook met gebruikelijke berekeningsmethodes
#   die nog veel meer kunnen

# bitsgewijze operatoren zijn snel
# toch beter niet gebruiken als het zou kunnen
# Python gebruikt ze vaak intern al
# voor snelle code beter geen Python gebruiken

# bitsgewijze operatoren maken mogelijk
# booleaanse waarden opslaan in kleine data ruimte
# 8 booleans opslaan in tuple kost meer ruimte
#   dan 8 bits in byte
# voor meeste hedendaagse computers is besparing
#   niet belangrijk
# alleen van belang bij enorm veel data

# bitsgewijze operatoren relatief weinig nut
# wel voor programma's dicht bij machine
# voor sommige data structuren
# voor manipuleren binaire bestanden

# voorbeeld kleuren gecodeerd in 3 bytes
# voor rood, groen en blauw kanaal
# met binaire operatoren scheiden van kanalen

if 0:
    markeer( '19.3' )

    # listing1901.py
    def getRGB( color ):
        blauw = color & 255
        groen = (color >> 8) & 255
        rood = (color >> 16) & 255
        return rood, groen, blauw

    r, g, b = getRGB( 223567 )
    print( "rood={}, groen={}, blauw={}".format( r, g, b ) )

    # niet in boek
    r, g, b = getRGB( 0x010203 )
    print( "rood={}, groen={}, blauw={}".format( r, g, b ) )

#=============================================================#
# Wat je geleerd hebt

# - Binair tellen
# - Codering van tekens en getallen
# - Bitsgewijze operatoren <<, >>, &, |, ~, en ^

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 19.1
# Opgave 19.2

###############################################################
# 20 Object Oriëntatie

# eerdere hoofdstukken over
#  gestructureerd of imperatief programmeren
# manieren van programmeren paradigma's ontwikkeld
#  helpen bij ontwerpen en implementeren grote programma's
# een van meest succesvolle paradigma's object oriëntatie
# Python is een object geörienteerde taal

# behoorlijk lastig
#   om een object georiënteerd programma te ontwerpen
# moet probleem goed doorgronden
# kan behoorlijk overweldigend zijn
#   met name bij weinig ervaring met programmeren

# breed onderwerp
# dit hoofdstuk is basis
# volgende 2 hoofdstukken over gespecialiseerde en
#   krachtige aspecten

#=============================================================#
# 20.1 De object georiënteerde wereld

# keukentafel, fruitschaal, appels
# appels hebben dezelfde naam, prijs, leeftijd
#   hebben verschillende gewichten
# ook peren, verschil met appels
#   andere namen, kleuren, bomen waaraan ze groeien
# toch overeenkomsten met fruitsoorten
#   anders dan tafel
# zoals kan eten

# in computer programma objecten modelleren
#   zoals appels, peren, tafels
# sommige zaken veel gemeen
#  zinvol klasse appel definiëren
# bevat eigenschappen van alle appels
# voor iedere individuele appel alleen die
#   eigenschappen waarin verschil met andere appels
# hetzelfde voor peer
# appel en peer verschillen van elkaar
#   maar delen ook eigenschappen in klasse fruit
#   zoals kan eten
# iedere appel behoort niet alleen tot de klasse appel
#   maar ook tot de klasse fruit
# peren ook

# kan meer eten, ook taart, champignons, brood, drop
# klasse erbij waartoe ook fruit behoort
#   zoals voedsel

# probeer zo wereld te modelleren
# in plaats van ieder object apart
#   beter klassen van objecten definiëren
# functies definiëren die werken op klassen
# bijvoorbeeld functie eten
#   werkt op ieder object van klasse voedsel
# eten verwijderd voedsel uit wereld
#   voegt voedingsstoffen toe aan eter
# kan zo objecten van klasse voedsel eten
#   waaronder fruit en ook appel objecten

# computer programma is model van deel van wereld
# werken met objecten, klassen, relaties en
#   functies, methodes die werken met objecten

#-------------------------------------------------------------#
# 20.1.1 Studenten, docenten, en cursussen

# programma's moeten informatie over personen bijhouden
# zoals studentenadministratie
#   met studenten zijn personen
#   studenten volgen cursussen
#   cursussen hebben docenten
#   ook docenten zijn personen
# programma beter met enkele interface
#   voor invoer personen
#   dan bruikbaar voor studenten en docenten

# bij studentenadministratie nodig
# voor alle personen
#   voornaam achternaam adres leeftijd geslacht
#   uniek administratienummer
# zijn eigenschappen attributen van personen

# één van genoemde eigenschappen is eigenlijk een functie
# leeftijd verandert in loop van de tijd
#   beter functie afgeleid van geboortedatum en huidige datum
# leeftijd beter implementeren als methode leeftijd()

# niet in boek, voor gevorderden
# kan toch eigenschap in plaats van methode gebruiken
# met Python property()
#   bij gebruik wordt dan toch een methode aangeroepen
#   tijdens lezen read, schrijven write, wissen del, en doc

# studenten en docenten zijn personen
# delen eigenschappen, zijn ook verschillen
# docenten krijgen salaris, studenten niet
# studenten krijgen cijfers voor cursussen, docenten niet

# naast klasse Persoon ook klasse Student en Docent nodig
# ook klasse Cursus nodig

# relaties
# studenten hebben meerdere cursussen
#   schrijven zich in voor cursussen
# docenten schrijven zich niet in
#   hebben andere relatie met cursussen

# methode inschrijven() nodig hoe
#   methode van student met cursus als argument
# of methode van cursus met student als argument

# hangt ervan af, it depends
#   hoe wordt relatie gezien
# of meer inschrijven() methodes
#   van zowel student als van cursus
# of andere klasse definiëren met methode inschrijven()
#   met zowel cursus als student als argument

# dit illustreert hoe lastig object oriëntatie kan zijn
# keuzes maken hoe definitie van klasses werken
#   om de wereld te modelleren
#   heeft grote invloed op aanpak werking van programma
# als zwakke keuzes dan implementatie problemen

# veel tijd besteden aan ontwerpen van object model
#   is moeilijk zelfs voor ervaren programmeurs
# solide model maakt programma's gemakkelijk
#   te lezen, begrijpen en onderhouden

#-------------------------------------------------------------#
# 20.1.2 Klassen, objecten, en hiërarchiëen
# spelfout: hierarchiën -> hiërarchieën

# bij object oriëntatie behoort iedere entiteit
#   tot een klasse class

# een klasse is een generiek model voor een groep entiteiten

# een klasse beschrijft alle attributen van de entiteiten
# en beschrijft alle methodes waarmee van buiten invloed op
#   de klasse kan worden uitgeoefend

# op zichzelf is een klasse geen entiteit

# niet in boek
# in Python is een klasse class ook een entiteit een object

# een entiteit die behoort tot een klasse wordt object genoemd
# een object is een instantie van een bepaalde klasse

# een klasse beschrijft attributen
# een object, instantie van een klasse
#   geeft waardes aan die attributen
# een klasse beschrijft methodes
# maar een methode kan alleen uitgevoerd worden
#   voor een object dat een instantie is van de klasse

# een klasse is een data type
# een object is een waarde

# klassen bestaan in hiërarchiëen

# een generieke hoog-niveau klasse kan eigenschappen en methodes
#   beschrijven die gedeeld worden door verschillende sub-klassen

# een sub-klasse kan eigenschappen en methodes toevoegen
#   en ook eigenschappen en methodes wijzigen
#   maar eigenlijk niet eigenschappen of methodes verwijderen
#     behoort dat niet te doen

# iedere sub-klasse kan zelf ook weer sub-klassen hebben

# bijvoorbeeld appel sub-klasse van Fruit
#   Fruit weer sub-klasse van Voedsel

# wanneer in programma een object van type Voedsel nodig
#   kan dan objecten gebruiken
#     die instantie zijn van Voedsel
#   en ook objecten
#     die instantie zijn van Fruit of van Appel

# werkt niet andersom
# functie die object van type Appel nodig heeft
#   kan niet gebruiken met instantie van Fruit
#     of andere sub-klasse van Fruit zoals Peer

# Appel is Fruit, Fruit is geen Appel
# kunt niet Appel met Peer vergelijken

# klasse hiërarchie wordt geïmplementeerd met
#   mechanisme overerving inheritance
# onderwerp van hoofdstuk 22 Overerving

#-------------------------------------------------------------#
# 20.1.3 Klassen en data types in Python

# object-georiënteerde talen zoals Python kennen
#   aantal basale data types
# en staan toe om klassen te definiëren
#   vormen nieuwe data types

# in Python tot en met versie 2
#    zijn basale types geen echte klassen
# in Python 3 is ieder data type een klasse
#    dus ook basale types

# deels te zien aan groot aantal functionaliteiten
#   van basale types geïmplementeerd als methodes

# methode altijd syntax <variabele>.<methode>()
# in tegenstelling tot
# functie syntax <functie>( <variabele> )
# bijvoorbeeld <string>.lower() geeft aan
#   dat een string een instatie is van een klasse

# niet alleen strings instanties van klassen
# integers en floats zijn dat ook
# hebben ook methods
#   die worden zelden expliciet aangeroepen

# methodes vaak impliciet aangeroepen
#   bijvoorbeeld bij twee getallen optellen met +
#   is eigenlijk een aanroep van een methode
# onderwerp van hoofdstuk 21 Operator Overloading

# niet in boek
# een klasse beschrijft data en code
#   die logisch bij elkaar horen
# data: de variabelen, de attributen,
#         vormen samen de toestand, de state
# code: de functies, de methodes,
#         vormen samen de werking, de functionaliteit
# de klasses in een programma
#  vormen logische groepen van data met bijbehorende code

# een klasse zonder data of code kan ook
#   is minder gebruikelijk
# zonder data kan dan functies gebruiken
# zonder code kan dan tuple, list of dict gebruiken

# pas tijdens het maken van een instantie van een klasse
#   krijgen de variabelen, de attributen een waarde
#   en kun je de functies, de methodes gebruiken
# de instanties van een klasse
#   werken meestal onafhankelijk van elkaar

# zelfs in programmeertalen zonder klasses
#   zijn er vaak functies die bij elkaar horen
# zoals in C de functies in stdio.h met FILE *
#   fopen, fclose, fread, fwrite, fprintf, fseek, ftell
# in struct FILE staan de variabelen,
#   de fopen, fclose.. functies vormen de methods

#=============================================================#
# 20.2 Object oriëntatie in Python

# maak nieuwe klassen met gereserveerd woord class

#-------------------------------------------------------------#
# 20.2.1 class

# class vormt nieuw datatype

# naam van class zelfde eisen als variabele naam
# conventie class naam begint met hoofdletter

if 0:
    markeer( '20.2.1 A' )

    class Punt:
        pass

# pass voor regel commando code die niets doet
# pass is gereserveerd woord
# class moet na naam en : code hebben
#   zoals ook lege if, while of def niet toegestaan

# voor creëren van een object
#   naam van de class met haakjes erachter
# kunnen argumenten komen tussen de haakjes

if 0:
    markeer( '20.2.1 B' )

    class Punt:
        pass

    p = Punt()
    print( type( p ) ) # <class '__main__.Punt'>

    # niet in boek
    print( p )          # <__main__.Punt object at 0x000001EE2A622A50>

    print( type( Punt ) ) # <class 'type'>
    print( Punt )         # <class '__main__.Punt'>

    p2 = Punt()
    print()
    print( p2 )         # <__main__.Punt object at 0x000001EE2A627D90>
    print( type( p2 ) ) # <class '__main__.Punt'>

# een punt is meer dan enkel een object
#   heeft ook x en y coördinaat

# Python heeft soft types
#   waardes creëren door deze toe te kennen
# speciale initialisatie methode in class

# niet in boek
# model van echte wereld naar class en object is top down
# kan ook bottom up bekijken

# class en object zijn intern een aangepaste dict
# kan bekijken met __dict__ attribute
#   of met vars() ingebouwde functie

if 0:
    markeer( '20.2.1 C' )
    
    # helper voor bekijken __dict__
    # slaat dunder keys over
    # gebruikt in 20.2.1 D en verder

    def printDunderDict( objOrCls ):
        print( 'dunderDict __dict__ for:', objOrCls )
        print( '  type:', type( objOrCls ) )
        try:
            ddict = objOrCls.__dict__
        except AttributeError:
            print( '  no dunder dict attribute' )
            return
        # print( ddict )
        if len( ddict ) == 0:
            print( '  no dunder dict items' )
            return
        if 1:
            dcount = 0
            for key, val in ddict.items():
                if key.startswith( '__' ) or key.endswith( '__' ):
                    continue
                print( ' ', key, '=>', val )
                dcount += 1
            
            if dcount == 0:
                print( ' ', 'dunder items only' )
        if 0:
            ddict2 = { key : val for key, val in ddict.items()
                       if not key.startswith( '__' ) and
                          not key.endswith( '__' )}
            import pprint
            pprint.pprint( ddict2 )

if 0:
    markeer( '20.2.1 D' )

    # 20.2.1 C ook activeren

    class Punt:
        pass

    p = Punt()

    if 0:
        print( 'Punt', vars( Punt ) )
        print()
        print( 'Punt', Punt.__dict__ )

    if 0:
        print()
        print( 'Punt', dir( Punt ) ) # ook van base object
        print()
        print( 'object', dir( object ) )

    if 0:
        printDunderDict( Punt )

    if 0:
        print( 'p', vars( p ) )
        print( 'p', p.__dict__ )
    if 0:
        printDunderDict( p )

    # .__dict__ niet altijd beschikbaar
    # met name wanneer implementatie in C
    # zoals bij instanties objecten van ingebouwde types
    #   wel bij die types zelf
    # __dict__ wel beschikbaar bij eigen class en functie

    if 0:
        print( 'int', vars( int ) )
        print( 'int', int.__dict__ )
    if 0:
        printDunderDict( int )

    if 0:
        print( '11', vars( 11 ) ) # TypeError
        # print( '11', 11 .__dict__ ) # AttributeError no __dict__
        # printDunderDict( 11 ) # AttributeError

    if 0:
        print( 'print', vars( print ) ) # TypeError
        # print( 'print', print.__dict__ ) # AttributeError
        # printDunderDict( print ) # AttributeError

    if 0:
        print( 'markeer', vars( markeer ) )
        print( 'markeer', markeer.__dict__ )
        printDunderDict( markeer )
    
    if 0:
        function = type( markeer )
        print( 'function', vars( function ) )
        # print( 'function', function.__dict__ )
        # printDunderDict( function )

#-------------------------------------------------------------#
# 20.2.2 __init__()

if 0:
    markeer( '20.2.2 A' )

    # listing2001.py
    class Punt:
        def __init__( self ):
            self.x = 5.0
            self.y = 0.6

    p = Punt()
    print( "({}, {})".format( p.x, p.y ) )

if 0:
    markeer( '20.2.2 B' )

    # lelijke code
    class Punt:
        def __init__( self ):
            self.x = 0.0
            self.y = 0.0

    p = Punt()
    p.z = 0.0
    print( "({}, {}, {})".format( p.x, p.y, p.z ) )

if 0:
    markeer( '20.2.2 C' )

    # niet in boek
    class Punt:
        # naam self mag anders zijn
        def __init__( self2 ):
            self2.x = 0.0
            self2.y = 0.0
            # print( self2.y )

        # naam self onafhankelijk van vorige naam
        # beter in alle methods self gebruiken
        def test( self3 ):
            print( "in test", self3.z )
            # print( "in test", z ) # NameError

    p = Punt()
    p.z = 0.7
    p.test()

    print( "({}, {}, {})".format( p.x, p.y, p.z ) ) # zoals in boek

    # print( p.x, p.y, p.z )
    # print( f"({p.x}, {p.y}, {p.z})" )
    # print( tuple( ( p.x, p.y, p.z ) ) ) # met haakjes
    
    markeer.xyz = 20
    print( markeer.xyz )

if 0:
    markeer( '20.2.2 D' )

    # niet in boek
    class Punt:
        w = 4.0 # class variabele, gelijke waarde bij elke instantie

        def __init__( self ):
            self.x = 1.0
            self.y = 2.0

        # w = 5.0

    p = Punt()
    p.x = 15.7
    p.z = 3.0
    print( "({}, {}, {}, {})".format( p.x, p.y, p.z, p.w ) )

    p2 = Punt()
    # print( "({}, {}, {})".format( p2.x, p2.y, p2.z ) )
    print( "({}, {}, {})".format( p2.x, p2.y, p2.w ) )

    if 0:
        # 20.2.1 C activeren
        print()
        Punt.w = 3.33
        printDunderDict( p )

if 0:
    markeer( '20.2.2 E' )

    # listing2002.py
    class Punt:
        def __init__( self, x, y ):
            self.x = x
            self.y = y

    p = Punt( 3.5, 5.0 )
    # p = Punt( 3.5, y=5.0 ) # niet in boek
    print( "({}, {})".format( p.x, p.y ) )

if 0:
    markeer( '20.2.2 F' )

    # listing2003.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y

    p1 = Punt()
    print( "({}, {})".format( p1.x, p1.y ) )

    p2 = Punt( 3.5, 5.0 )
    print( "({}, {})".format( p2.x, p2.y ) )

    if 0:
        # niet in boek
        # 20.2.1 C activeren
        print()
        printDunderDict( Punt )
        # printDunderDict( p1 )
        # printDunderDict( p2 )

#-------
# Opgave

if 0:
    markeer( '20.2.2 Opgave' )

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y

    lst = []
    for x in range(4):
        for y in range(4):
            lst.append( Punt(x, y) )
    # lst = [ Punt(x, y) for x in range(4) for y in range(4) ]
    # print(lst)

    for pnt in lst:
        print(pnt.x, pnt.y, sep=',', end=' ')
        # print( "punt", pnt )
    print()

#-------------------------------------------------------------#
# 20.2.3 __repr__() en __str__()

if 0:
    markeer( '20.2.3 A' )

    # listing2004.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y

    p = Punt( 3.5, 5.0 )
    print( p )

if 0:
    markeer( '20.2.3 B' )

    # listing2005.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )

    p = Punt( 3.5, 5.0 )
    print( p )

if 0:
    markeer( '20.2.3 C' )

    # niet in boek
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __str__(self): # leesbaar voor gebruiker
            return "AlsStr({}, {})".format( self.x, self.y )
        def __repr__( self ): # bruikbaar voor eval()
           # beter naam van class erbij in repr
           return "Punt({}, {})".format( self.x, self.y )

    p = Punt( 3.5, 5.0 )
    print( p )
    print( str(p), repr(p) )
    print( str(p).upper(), repr(p).upper())

#-------
# Opgave

if 0:
    markeer( '20.2.3 Opgave' )

    class Punt:
        def __init__( self, x=0.0, y=0.0, kleur=0x000000 ):
            self.x = x
            self.y = y
            self.kleur = kleur
        def __repr__( self ):
            return "({}, {}, {})".format( self.x, self.y,
                                          hex(self.kleur) )

    p = Punt( 3.5, 5.0, 0xaabbcc )
    print( p )
    
#=============================================================#
# 20.3 Methodes

# __init__(), __repr__(), __str__() al besproken
# meer dunder methodes in latere hoofdstukken

# kan eigen methodes toevoegen
#   zijn functies
#   met dezelfde naam conventies als bij gewone functies

# prefix
#   is  voor methodes die True of False waarde leveren
#  get  voor methodes die andere waarden leveren
#  set  voor methodes die waarden zetten

if 0:
    markeer( '20.3 A' )

    # listing2006.py
    from math import sqrt

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
        def afstand_tot_oorsprong( self ):
            return sqrt( self.x * self.x + self.y * self.y )

        # w=10 # niet in boek

    p = Punt( 3.5, 5.0 )
    print( p.afstand_tot_oorsprong() )

    # niet in boek
    # 20.2.1 C aanzetten
    # printDunderDict( Punt )
    # printDunderDict( p )

if 0:
    markeer( '20.3 B' )

    # listing2007.py
    # niet in boek: import is niet nodig
    #   sqrt wordt niet gebruikt
    from math import sqrt

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
        def translatie( self, shift_x, shift_y ):
            self.x += shift_x
            self.y += shift_y

    p = Punt( 3.5, 5.0 )
    p.translatie( -3, 7 )
    print( p )

    # niet in boek
    # 20.2.1 C activeren
    # printDunderDict( Punt )
    # printDunderDict( p )

#-------
# Opgave

if 0:
    markeer( '20.3 Opgave' )

    # spiegel
    from math import sqrt

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
        def translatie( self, shift_x, shift_y ):
            self.x += shift_x
            self.y += shift_y
        def spiegel( self ):
            self.x = -self.x
            self.y = -self.y
        def spiegel2( self ):
            self.x *= -1
            self.y *= -1

    p = Punt( 3.5, 5.0 )
    p.translatie( -3, 7 )
    p.spiegel()
    # p.spiegel2()
    print( p )

    p = Punt( 3, 4)
    p.spiegel()
    # p.spiegel2()
    print( p )
    
    p = Punt( -1, 2)
    p.spiegel()
    # p.spiegel2()
    print( p )

    if 0:
        p1 = Punt( 3, 4)
        p1.spiegel()
        # p1.spiegel2()
        print( p1 )
        
        p2 = Punt( -1, 2)
        p2.spiegel()
        # p2.spiegel2()
        print( p2 )

#=============================================================#
# 20.4 Nesten van objecten

if 0:
    markeer( '20.4' )

    # Punt in Rechthoek

    # listing2008.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
            # return "Punt({}, {})".format( self.x, self.y ) # niet in boek

    class Rechthoek:
        def __init__( self, punt, breedte, hoogte ):
            self.punt = punt
            self.breedte = breedte
            self.hoogte = hoogte
        def __repr__( self ):
            return "[{},w={},h={}]".format( self.punt, self.breedte,
                self.hoogte )

    p = Punt( 3.5, 5.0 )
    r = Rechthoek( p, 4.0, 2.0 )
    print( r )

    # niet in boek, beter w= h= -> b= h=
    #  van Engels naar Nederlandstalig

    # niet in boek
    # 20.2.1 C aanzetten
    # printDunderDict( Punt )
    # printDunderDict( p )
    # printDunderDict( Rechthoek )
    # printDunderDict( r )

#-------
# Opgave

if 0:
    markeer( '20.4 Opgave' )

    # 2 Punten in Rechthoek

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )

    class Rechthoek:
        def __init__( self, linksbovenpunt, rechtsonderpunt ):
            self.linkerbovenhoek = linksbovenpunt
            self.rechteronderhoek = rechtsonderpunt
        def __repr__( self ):
            return "[{},{}]".format( self.linkerbovenhoek,
                self.rechteronderhoek )

    linksboven = Punt( 3.5, 5.0 ) # zoals in wiskunde
    rechtsonder = Punt( 7.5, 3.0 )
    r = Rechthoek( linksboven, rechtsonder )
    print( r )

#-------------------------------------------------------------#
# 20.4.1 Kopieën en referenties

if 0:
    markeer( '20.4.1 A' )

    # listing2009.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )

    class Rechthoek:
        def __init__( self, punt, breedte, hoogte ):
            self.punt = punt
            self.breedte = breedte
            self.hoogte = hoogte
        def __repr__( self ):
            return "[{},b={},h={}]".format( self.punt, self.breedte,
                self.hoogte )

    p = Punt( 3.5, 5.0 )
    r = Rechthoek( p, 4.0, 2.0 )
    print( r )

    p.x = 1.0
    p.y = 1.0
    print( r )

# wijzigen punt p wijzigt ook rechthoek r
# self.punt in r is alias van p

# meestal niet de bedoeling
# oplossen mogelijk met copy

if 0:
    markeer( '20.4.1 B' )

    # listing2010.py
    from copy import copy

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
    class Rechthoek:
        def __init__( self, punt, breedte, hoogte ):
            self.punt = copy( punt ) # of deepcopy
            self.breedte = breedte
            self.hoogte = hoogte
        def __repr__( self ):
            return "[{},b={},h={}]".format( self.punt, self.breedte,
                self.hoogte )

    p = Punt( 3.5, 5.0 )
    r = Rechthoek( p, 4.0, 2.0 )
    print( r )

    p.x = 1.0
    p.y = 1.0
    print( r )

# Punt gebruikt alleen float voor self.x en self.y
#   dan kopie niet nodig

if 0:
    markeer( '20.4.1 C' )

    # niet in boek
    # kan ook kopie maken met Punt constructor
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
    class Rechthoek:
        def __init__( self, punt, breedte, hoogte ):
            # self.punt = punt
            # self.punt = copy( punt )
            self.punt = Punt( punt.x, punt.y )
            self.breedte = breedte
            self.hoogte = hoogte
        def __repr__( self ):
            return "[{},b={},h={}]".format( self.punt, self.breedte,
                self.hoogte )

    p = Punt( 3.5, 5.0 )
    r = Rechthoek( p, 4.0, 2.0 )
    print( r )

    p.x = 1.0
    p.y = 1.0
    print( r )

#=============================================================#
# 20.5 Geheugenbeheer

# soms nodig rekening houden met geheugen gebruik
#   bij grote aantallen objecten

if 0:
    markeer( '20.5 A' )

    # listing2011.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y

    puntlist = []
    for i in range( 100 ):
        for j in range( 100 ):
            p = Punt( i, j )
            puntlist.append( p )

    print( "Er zijn", len( puntlist ), "punten in de list" )

    if 0:
        # niet in boek
        # kan in taakbeheer task manager geheugen gebruik bekijken
        #   bij Details, sorteer op naam, zoek python*.exe
        # extra kolommen kiezen via rechtermuisknop menu kolomnamen
        #   kies kolommen selecteren, Geheugen (privéwerkset) aanzetten

        # probeer ook met range( 1000 ) of nog meer
        #   gebruikt steeds meer

        # beter te zien in command prompt, buiten IDE zoals IDLE

        import os
        print( 'Process id', os.getpid() )
        input( 'Bekijk geheugengebruik, Enter voor afsluiten' )

# bij te veel geheugengebruik
#   afhankelijk van operating systeem en instellingen
# wordt soms swap geheugen naar disk gebruikt
#   is vaak heel traag
# of soms een foutmelding
# of computer lijkt te hangen

# gebruik voor grote data verzamelingen
#   die niet in geheugen passen
#   bestanden of database systemen

if 0:
    markeer( '20.5 B' )

    # listing2012.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y

    totaalx = 0
    totaaly = 0
    for i in range( 1000 ):
        for j in range( 1000 ):
            p = Punt( i, j )
            totaalx += p.x
            totaaly += p.y
    print( "De totalen van x en y zijn", totaalx, "en", totaaly )

    # niet in boek
    if 0:
        import os
        print( 'Process id', os.getpid() )
        input( 'Bekijk geheugengebruik, Enter voor afsluiten' )

# code met miljoen punten toch veilig
# garbage collection zorgt voor opruimen
#   van objecten die niet meer gebruikt worden
# gaat automatisch, meestal niet druk over maken

# spelfout in boek laatste paragraaf van 20.5 Geheugenbeheer
# he -> je
#   ..  geheugenproblemen kunnen ontstaan
#   als he grote hoeveelheden data in
#   het geheugen laadt die allemaal ..

# niet in boek, voor gevorderden
# vanaf Python 3.7 dataclasses module
# maakt definiëren class met gebruikelijke
#   __init__ en andere 'boilerplate' eenvoudiger
# zie https://docs.python.org/3/library/dataclasses.html
# zie https://realpython.com/python-data-classes/

if 0:
    markeer( '20.5 C' )

    # niet in boek
    # 2024-01-11 Anthony Vogelaar

    import  os;     # os.system('clear') # geen clear op Windows
    import  time

    # =================================== Logger =====================================

    class Logger:

        def __init__(self, file_name):
            self.fn = file_name
            try:    os.remove(self.fn)
            except: pass


        def log(self, msg):
            t = time.localtime()
            st = time.strftime('%Y-%m-%d %H:%M:%S', t)
            with open(self.fn, 'a') as f:
                f.write(f'{st}    {msg}\n')

    # ================================== Controller ==================================
                
    class Controller:

        def __init__(self, log=None):
            self.log = log


        def run(self):
            n = 7
            for i in range(1, 11):
                res = i * n
                print(f'{i:2} * {n} = {res:5}')
                if self.log: self.log(f'i = {i}, n = {n}, res = {res}')
                # if self.log: self.log.log(f'i = {i}, n = {n}, res = {res}')

    # ====================================== Main ====================================

    def main():
        if 1:
            log = Logger('test.log').log
            Controller(log=log).run()
        if 0:
            logobj = Logger('test.log')
            Controller(log=logobj).run()

    main()

if 0:
    markeer( '20.5 D' )

    # niet in boek
    # static en class methods in class
    # zie ook https://realpython.com/instance-class-and-static-methods-demystified/

    from math import sqrt
    
    class Punt:
        def __init__( self, x, y ):
            self.x = x
            self.y = y
            self.factor = 33

        def afstand( self ):
            return sqrt( self.x * self.x + self.y * self.y )
            # return self.versterk( sqrt( self.x * self.x + self.y * self.y ) )
            # return self.versterk()
            # return self.amplify( sqrt( self.x * self.x + self.y * self.y ) )
            # return self.calc()
            # return self._calc()
            # return self.__calc()
            # return self.calc2()

        @staticmethod
        def versterk( val ):
            return val * 3
            # return val * Punt.factor
            # return val.x  # val is self als @staticcomment in commentaar
            # return val * self.factor  # no self
            
        @classmethod
        def amplify( cls, val ):
            return val * cls.factor
            # return val * cls.x  # AttributeError

        factor = 100
        # x = 123

        # helper methods
        # kan ook met underscore aan begin
        # indicatie alleen bedoeld voor class intern gebruik
        #   is private method, andere public method
        # zie ook Python Tutorial
        #   9. Classes, 9.6 Private Variables
        # https://docs.python.org/3/tutorial/classes.html#private-variables
        
        def calc( self ):
        # def _calc( self ): # conventie private
        # def __calc( self ): # meer private, naam aangepast
            return sqrt( self.x * self.x + self.y * self.y )
    
        def calc2( self ):
            return 2 + self.calc()

    p1 = Punt( 2, 3 )
    print( p1.afstand() )
    # print( p1.versterk( 2 ) )
    # print( p1.versterk( ) ) # met @staticmethod commentaar
    
    p2 = Punt( -4, 7 )
    print( p2.afstand() )
    # print( p2.amplify( 2 ) )
    # print( p2.amplify( ) ) # met @classmethod in commentaar

    # print( p1.calc() )
    # print( p1._calc() )
    # print( p1.__calc() ) # AttributeError
    # print( p1._Punt__calc() )
    
    if 0:
        # 20.2.1 C aanzetten
        print()
        printDunderDict( Punt )
        printDunderDict( p1 )
        printDunderDict( p2 )
        
# zie ook
# @property decorator
# Aanvulling 10 callbacks, decorators en properties

if 0:
    markeer( '20.5 E' )

    # niet in boek
    # event, functie variabele in object
    #   functie kan per instantie verschillend zijn
    # methodes zijn voor een class gelijk per instantie

    # geen if .. elif .. else nodig in method

    class Button:
        def __init__( self, label, click=None ):
            self.label = label
            self.click = click

        def press( self ):
            print( 'button press', self.label )
            self.klik()
            if self.click:
                self.click( self )

        def klik( self ):
            if self.label == '+':
                print( '-> klik +, add' )
            elif self.label == '=':
                print( '-> klik =, eval' )
            else:
                print( '-> klik', self.label )

        def __repr__( self ):
            return f'Button({self.label})'


    def clickClear( button ):
        print( '=> click clear', button )

    def clickAdd( button ):
        print( '=> click add', button )

    def clickMul( button ):
        print( '=> click mul', button )

    def clickEval( button ):
        print( '=> click eval', button )
    
    buttons = []
    for i in range( 10 ):
        buttons.append( Button( str(i) ) )
    buttons.append( Button( 'c', clickClear ) )
    buttons.append( Button( '+', clickAdd ) )
    buttons.append( Button( '*', clickMul ) )
    buttons.append( Button( '=', clickEval ) )
                        
    for ch in '3 + 4 c 5 * 2 =':
        for button in buttons:
            if ch == button.label:
                button.press()

    # kan ook specifieke button types maken
    #   met inheritance, subclasses
    # zie hoofdstuk 22 Overerving

    # is minder flexibel
    #   events zijn onafhankelijk van class
    #   subclasses zijn niet onafhankelijk
    class AddButton(Button):
        ...
    class EvalButton(Button):
        ...
    # etc.

if 0:
    markeer( '20.5 F' )

    # niet in boek
    # welke wordt gebruikt?
    class Test:
        def __init__( self ):
            self.a = 1
            self.b = 2
            # self.c = 3

        a = 11
        # b = 22
        c = 33
        
        def test( self ):
            print( 'test method', self.a, self.b, self.c )

    t = Test()
    t.test()  # 1 2 33
    print( 't test', t.a, t.b, t.c )

    # Test().test()

    if 0:
        # 20.2.1 C aanzetten
        print()
        printDunderDict( Test )
        printDunderDict( t )

if 0:
    markeer( '20.5 G' )

    # niet in boek
    # welke wordt gebruikt?
    class Test:
        def __init__( self, func ):
            self.testA = func
            self.testB = lambda : print( 'test lambda' )
            def test():
                print( 'test nested' )
            # self.testC = test

        def testA( self ):
            print( 'test method A' )
        # def testB( self ):
        #     print( 'test method B' ) 
        def testC( self ):
            print( 'test method C' )

    def test():
        print( 'test function' )

    Test(test).testA() # test function
    Test(test).testB() # test lambda
    Test(test).testC() # test method C

    if 0:
        # 20.2.1 C aanzetten
        print()
        t = Test(test)
        printDunderDict( Test )
        printDunderDict( t )

if 0:
    markeer( '20.5 H' )

    # niet in boek

    # getattr, hasattr en setattr
    # ingebouwde functies
    # https://docs.python.org/3/library/functions.html#getattr

    class Test:
        pass

    t = Test()
    t.a = 123
    # t.b = 21
    # t.c = 56

    print( 't.a:', t.a )  # todo: : erbij is duidelijker
    # print( 't.b:', t.b )
    # print( 't.c:', t.c )
    
    if 0:
        print( 'hasattr a:', hasattr( t, 'a' ) )
        print( 'getattr a:', getattr( t, 'a' ) )
        print( 't.a', t.a )

    if 0:
        # print( 'getattr b 1:', getattr( t, 'b' ) ) # AttributeError
        print( 'getattr b 2:', getattr( t, 'b', 321 ) )
        # print( 't.b:', t.b )

    if 0:
        print( 'setattr c:', setattr( t, 'c', 456 ) )
        # t.c = 456
        print( 't.c:', t.c )

    # print( 't.a', t.a )
    # print( 't.b', t.b )
    # print( 't.c', t.b )

    if 0:
        # 20.2.1 C aanzetten
        print()
        printDunderDict( Test )
        printDunderDict( t )

if 0:
    markeer( '20.5 I' )

    # niet in boek
    # instantie maken naast __init__

    # zoals in datetime module
    # bijvoorbeeld class date(year, month, day)
    # met classmethod date.today()
    # https://docs.python.org/3/library/datetime.html#date-objects

    class Test:
        def __init__( self, a=0, b=0 ):
            self.a = a
            self.b = b

        @staticmethod
        def createTestS():
            return Test( 10, 20 )
        @classmethod
        def createTestC(cls):
            return cls( 400, 300 )
        def __repr__(self):
            return f'Test({self.a}, {self.b})'

    # todo: staticmethod en classmethod
    #   gebruik bij inheritance
    #   hoofdstuk 22 Overerving

    t1 = Test()
    t2 = Test.createTestS()
    t3 = Test.createTestC()
    print( t1 )
    print( t2 )
    print( t3 )

if 0:
    markeer( '20.5 J' )

    # niet in boek
    # instantie maken zonder __init__

    class Test:
        @staticmethod
        def createStaticTest():
            t = Test()
            t.a = 10
            t.b = 20
            return t
        @classmethod
        def createClassTest(clss):
            t = clss()
            t.a = 300
            t.b = 400
            return t
        def __repr__(self):
            return f'Test({self.a}, {self.b})'

    def createTest():
        t = Test()
        t.a = 5
        t.b = 6
        return t

    ts = Test.createStaticTest()
    tc = Test.createClassTest()
    tt = createTest()
    print( ts )
    print( tc )
    print( tt )

if 0:
    markeer( '20.5 K' )

    # niet in boek

    # verschil object en instantie
    # object bepaald door id
    # instantie bepaald door type, __class__

    def printVar(naam, var):
        print( 'naam:', naam, 'var:', var, 'id:', id(var),
               'type:', type(var) )
               # 'class:', var.__class__ )

    class Test1:
        def __repr__(self):
            return 'Test1 object'

    t1 = Test1()
    t2 = Test1()

    # t1 en t2 zijn objecten
    #   met een unieke id
    # zijn beide instanties van Test1
    printVar('t1', t1)
    printVar('t2', t2)

    if 0:
        print()
        # Python is dynamisch
        # kan type, __class__ van een object aanpassen
        #   zelfs als object al bestaat
        #   komt in de praktijk niet vaak voor

        class Test2:
            def __repr__(self):
                return 'Test2 object'

        t2.__class__ = Test2

        # t1 en t2 zijn dezelfde objecten
        #   want id blijft gelijk
        # t1 is nog steeds een instantie van Test1
        # maar t2 is nu een instantie van Test2
        printVar('t1', t1)
        printVar('t2', t2)

        if 0:
            # t2.__class__ = 321     # TypeError
            # t2.__class__ = None    # TypeError
            # t2.__class__ = int     # TypeError
            # t2.__class__ = int.__class__     # TypeError
            # t2.__class__ = list    # TypeError
            # t2.__class__ = object  # TypeError
            
            # met inheritance, zie hoofdstuk 22
            class Test3(object):
            # class Test3(list):     # TypeError
            # class Test3(int):      # TypeError
            # class Test3(bool):     # TypeError
            # class Test3(type(None)):  # TypeError
                pass            # geen __repr__

            t2.__class__ = Test3

            printVar('t1', t1)
            printVar('t2', t2)

    # zie ook
    # How to Dynamically Change Class Type in Python After Data Insertion Based on Data Properties
    # https://www.pythontutorials.net/blog/changing-the-class-type-of-a-class-after-inserted-data/

#=============================================================#
# Wat je geleerd hebt

# - Klassen en objecten
# - Het gereserveerde woord class
# - Creëren van objecten
# - __init__(), __repr__(), en __str__()
# - Methodes
# - Nesten van objecten
# - Relaties
# - Geheugenbeheer

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 20.1
# Opgave 20.2

###############################################################
# 21 Operator Overloading

# bij Object Oriëntatie
# operators definiëren voor nieuwe data types

# speciale dunder methodes
#   __<naam>__()

#=============================================================#
# 21.1 Het idee achter operator overloading

# operatoren + - * / etc. bestaan al voor ingebouwde types
# zijn standaard niet gedefiniëerd
#   voor zelf gemaakte classes
# kan deze wel toevoegen

# niet altijd zinvol om operatoren te gaan gebruiken
#   studenten objecten optellen
#     niet goed te begrijpen

# niet in boek
# in Python 3.5 is operator @ toegevoegd __matmul__()
#   wordt in geen standaard ingebouwd type gebruikt
# https://docs.python.org/3/reference/expressions.html#index-65

# operator overloading wordt met name gebruikt
#   bij wiskundige toepassingen
# zoals NumPy package
#   https://numpy.org/devdocs/user/quickstart.html#basic-operations

#=============================================================#
# 21.2 Vergelijkingen

if 0:
    markeer( '21.2 A' )

    # listing2101.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )

    p1 = Punt( 3, 4 )
    p2 = Punt( 3, 4 )
    p3 = p1
    print( p1 is p2 )
    print( p1 is p3 )
    print( p1 == p2 )
    print( p1 == p3 )

# is vergelijkt identiteiten
#   als alias dan True

# == vergelijkt waarden
#   werkt niet standaard
#     op zelf gedefiniëerde classes
#   Python weet niet hoe

if 0:
    markeer( '21.2 B' )

    # listing2102.py
    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
        def __eq__( self, p ):
        # def __ne__( self, p ): # niet in boek
            return self.x == p.x and self.y == p.y

            # niet in boek
            # return self.x, self.y == p.x, p.y     # tuple
            # return (self.x, self.y) == (p.x, p.y) # bool

    p1 = Punt( 3, 4 )
    p2 = Punt( 3, 4 )
    p3 = p1
    print( p1 is p2 )
    print( p1 is p3 )
    print( p1 == p2 )
    print( p1 == p3 )

    # niet in boek
    print( "!=", p1 != p2 )
    print( "!=", p1 != p3 )

# __eq__() == gelijk aan
# __ne__() != ongelijk aan

# __gt__() >  groter dan
# __ge__() >= groter dan of gelijk aan
# __lt__() <  kleiner dan
# __le__() <= kleiner dan of gelijk aan

# als __eq__() wel aanwezig
#   maar __ne__() niet aanwezig
# dan gebruikt Python automatisch
#   omgekeerde van __eq__()

# voor overige van deze dunder methoden
#  is er geen automatisch gebruik van een andere

# https://docs.python.org/3/reference/datamodel.html#object.__lt__

if 0:
    markeer( '21.2 C' )

    # listing2103.py
    class Quaternion:
        def __init__( self, a, b, c, d ):
            self.a = a
            self.b = b
            self.c = c
            self.d = d
        def __repr__( self ):
            return "({},{}i,{}j,{}k)".format( self.a, self.b,
                self.c, self.d )
        def __eq__( self, n ):
            if isinstance( n, int ) or isinstance( n, float ):
                if self.a == n and self.b == 0 and \
                    self.c == 0 and self.d == 0:
                    return True
                else:
                    return False
            elif isinstance( n, Quaternion ):
                if self.a == n.a and self.b == n.b and \
                    self.c == n.c and self.d == n.d:
                    return True
                else:
                    return False
            return NotImplemented

        # niet in boek
        # isinstance 2e argument kan ook tuple meerdere types zijn
        #   werkt zoals meerdere aanroepen met or ertussen
        # https://docs.python.org/3/library/functions.html#isinstance

        # komma comma , is geen operator
        #   maar een separator, scheiding tussen expressies
        # expressies links en rechts met komma ertussen
        #   vormen tuple, of zijn list set dict items,
        #   of zijn functie argumenten parameters
        # https://docs.python.org/3/faq/programming.html#what-s-up-with-the-comma-operator-s-precedence

        # ook assignment operators = += *= .. zijn geen operators
        # , = += *= .. staan niet in tabel 6.17 Operator precedence
        # https://docs.python.org/3/reference/expressions.html#operator-precedence
        
        def x__eq__( self, n ):
            # if isinstance( n, int ) or isinstance( n, float ):
            if isinstance( n, (int, float) ):
                # return self.a, self.b, self.c, self.d == n, 0, 0, 0 # tuple
                return (self.a, self.b, self.c, self.d) == (n, 0, 0, 0) # bool
            if isinstance( n, Quaternion ):
                return (self.a, self.b, self.c, self.d) == (n.a, n.b, n.c, n.d)
            return NotImplemented

    c1 = Quaternion( 1, 2, 3, 4 )
    c2 = Quaternion( 1, 2, 3, 4 )
    c3 = Quaternion( 3, 0, 0, 0 )
    if c1 == c2:
        print( c1, "==", c2 )
    else:
        print( c1, "!=", c2 )
    if c1 == c3:
        print( c1, "==", c3 )
    else:
        print( c1, "!=", c3 )
    if c3 == 1:
        print( c3, "==", 1 )
    else:
        print( c3, "!=", 1 )
    if c3 == 3:
        print( c3, "==", 3 )
    else:
        print( c3, "!=", 3 )
    if c3 == 3.0:
        print( c3, "==", 3.0 )
    else:
        print( c3, "!=", 3.0 )
    if c3 == "3":
        print( c3, "== \"3\"" )
    else:
        print( c3, "!= \"3\"" )
    if 3 == c3:
        print( 3, "==", c3 )
    else:
        print( 3, "!=", c3 )

    # verwisselt linker en rechter operanden
    #   als operator niet gevonden bij linker
    # geen int operator == met parameter quaternion object
    #   probeert dan operator van rechter operand
    # als nodig ook zo bij != operator
    # wisselt ook bij < naar > en bij <= naar >=
    #   zodat resultaat logisch blijft
    #   zoals bij 3 < 4 en 4 > 3
    
    # niet in boek
    if 0:
        if c1 != c2:
            print( c1, 'ongelijk aan', c2 )
        else:
            print( c1, 'gelijk aan', c2 )

        if c1 != 'tekst': # geen deprecation warning
            print( c1, 'ongelijk aan', 'tekst' )
        else:
            print( c1, 'gelijk aan', 'tekst' )

        print( NotImplemented, type(NotImplemented) )
        # print( bool(NotImplemented)) # deprecation warning
        # print( type(bool(NotImplemented)) )  # deprecation warning
        # print( not NotImplemented, type(not NotImplemented) )  # deprecation warning
    
#-------
# Opgave

if 0:
    markeer( '21.2 Opgave' )

    # Rect == als vorm gelijk
    #      < of > als oppervlakte kleiner of groter
    from copy import copy

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )
    class Rechthoek:
        def __init__( self, punt, breedte, hoogte ):
            self.punt = copy( punt )
            self.breedte = breedte
            self.hoogte = hoogte
        def __repr__( self ):
            return "[{},b={},h={}]".format( self.punt, self.breedte,
                self.hoogte )
        def __eq__( self, other ):
            return (self.breedte, self.hoogte) == (other.breedte, other.hoogte)
        def __ne__( self, other ):
            return not self == other
        def __lt__( self, other ):
            return self.breedte * self.hoogte < other.breedte * other.hoogte
        def __le__( self, other ):
            # return self <= other # RecursionError
            # return other >= self # RecursionError
            return self < other or self == other
        def __gt__( self, other ):
            return other < self
        def __ge__( self, other ):
            return other <= self

    r1 = Rechthoek( Punt( 3.5, 5.0 ), 4.0, 2.0 )
    print( 'r1', r1 )
    r2 = Rechthoek( Punt( 1.5, -3.0 ), 4.0, 2.0 )
    print( 'r2', r2 )
    r3 = Rechthoek( Punt( 1.5, -3.0 ), 3.0, 2.0 )
    print( 'r3', r3 )
    
    r4 = Rechthoek( Punt( 1.5, 5.0 ), 2.0, 4.0 )
    print( 'r1', r1 )

    print( 'r1 == r2', r1 == r2 )
    print( 'r1 < r2',  r1 < r2  )

    print( 'r2 < r3',  r2 < r3  )
    print( 'r2 <= r3', r2 <= r3 )
    print( 'r2 == r3', r2 == r3 )
    print( 'r2 > r3',  r2 > r3  )
    print( 'r2 >= r3', r2 >= r3 )
    print( 'r2 != r3', r2 != r3 )

    print( 'r1 < r4',  r1 < r4  )
    print( 'r1 <= r4', r1 <= r4 )
    print( 'r1 == r4', r1 == r4 )
    print( 'r1 > r4',  r1 > r4  )
    print( 'r1 >= r4', r1 >= r4 )
    print( 'r1 != r4', r1 != r4 )
    

# eerder besproken in hoofdstuk 6
# zie 6.1.1 Booleans

# ingebouwde conversie naar bool
if 0:
    markeer( '21.2 D' )

    buffer = []
    if buffer:
        print( buffer )
    else:
        print( "buffer is empty" )

# eigen conversie met __bool__() methode
# als niet geïmplementeerd
#   dan wordt __len__() methode gebruikt
#     lengte 0 is False
#   als ook niet geïmplementeerd
#     dan altijd True

# niet in boek
if 0:
    markeer( '21.2 E' )

    class AlsBool:
        def __init__(self, val):
            self.val = val
        def __repr__(self):
            return 'AlsBool(' + repr(self.val) + ')'
        def __bool__(self):
        # def x__bool__(self):
            return bool(self.val)
            # return not bool(self.val)
            # return NotImplemented # TypeError
            # return 0 # TypeError
        def __len__(self):
            if isinstance(self.val, int):
                return self.val
                # return False
                # return NotImplemented # TypeError
                # return 2.5 # TypeError
            return len(self.val)
            # return False

    tests = False, True, 0, 1, [], [2], "", "3"
    for a in tests:
        print(a, bool(a), bool(AlsBool(a)), AlsBool(a))
        if AlsBool(a):
            print(' de if')
        else:
            print(' de else')

#=============================================================#
# 21.3 Berekeningen

# __add__()      +  optellen
# __sub__()      -  aftrekken
# __mul__()      *  vermenigvuldigen
# __truediv__()  /  delen
# __floordiv__() // integer delen
# __mod__()      %  modulo
# __pow__()      ** machtsverheffen
# __lshift__()   << shift-links
# __rshift__()   >> shift-rechts
# __and__()      &  bitsgewijze and
# __or__()       |  bitsgewijze or
# __xor__()      ^  bitsgewijze xor

if 0:
    markeer( '21.3 A' )

    # listing2104.py
    class Quaternion:
        def __init__( self, a, b, c, d ):
            self.a, self.b, self.c, self.d = a, b, c, d
        def __repr__( self ):
            return "({},{}i,{}j,{}k)".format( self.a, self.b,
                self.c, self.d )
        def __add__( self, n ):
            if isinstance( n, int ) or isinstance( n, float ):
            # if isinstance( n, (int, float) ): # niet in boek
                return Quaternion( n+self.a, self.b, self.c, self.d )
            elif isinstance( n, Quaternion ):
                return Quaternion( n.a + self.a, n.b + self.b, \
                    n.c + self.c, n.d + self.d )
            return NotImplemented

    c1 = Quaternion( 3, 4, 5, 6 )
    c2 = Quaternion( 1, 2, 3, 4 )
    print( c1 + c2 )
    print( c1 + 10 )

    # print( c1 + 20.5 ) # niet in boek
    # print( 10 + c1 )

# operanden links en rechts zijn meestal niet uitwisselbaar
#   zoals bijvoorbeeld sub integer 4-5, of add string 'a'+'b'
#        resultaat niet gelijk bij 5-4  of            'b'+'a'

# als geen ondersteuning bij linker operand
#   return NotImplemented
# dan controle rechter operand
#   met __rxxx__() methodes

if 0:
    markeer( '21.3 B' )

    # listing2105.py
    class Quaternion:
        def __init__( self, a, b, c, d ):
            self.a, self.b, self.c, self.d = a, b, c, d
        def __repr__( self ):
            return "({},{}i,{}j,{}k)".format( self.a, self.b,
                self.c, self.d )
        def __add__( self, n ):
            if isinstance( n, int ) or isinstance( n, float ):
                return Quaternion( n+self.a, self.b, self.c, self.d )
            elif isinstance( n, Quaternion ):
                return Quaternion( n.a + self.a, n.b + self.b, \
                    n.c + self.c, n.d + self.d )
            return NotImplemented
        def __radd__( self, n ):
            return self.__add__( n )
            # return self + n  # niet in boek, ook mogelijk

    c1 = Quaternion( 3, 4, 5, 6 )
    print( 10 + c1 )

    # c1 += 5.5 # niet in boek
    # c1 += c1
    # print( c1 )

# ook verkorte operatoren += *= etc mogelijk
# met __ixxx__() methoden
# zoals __iadd___()

# in-place operators
# https://docs.python.org/3/library/operator.html#in-place-operators

# als niet aanwezig
#   dan probeert Python gewone operatoren
# daarom toevoegen meestal niet nodig

#-------
# Opgave

if 0:
    markeer( '21.3 Opgave' )

    class Quaternion:
        def __init__( self, a, b, c, d ):
            self.a, self.b, self.c, self.d = a, b, c, d
        def __repr__( self ):
            return "({},{}i,{}j,{}k)".format( self.a, self.b,
                self.c, self.d )
        def __add__( self, n ):
            if isinstance( n, int ) or isinstance( n, float ):
                return Quaternion( n+self.a, self.b, self.c, self.d )
            elif isinstance( n, Quaternion ):
                return Quaternion( n.a + self.a, n.b + self.b, \
                    n.c + self.c, n.d + self.d )
            return NotImplemented
        def __radd__( self, n ):
            return self.__add__( n )
        def __sub__( self, n ):
            if isinstance( n, int ) or isinstance( n, float ):
                return Quaternion( self.a-n, self.b, self.c, self.d )
            elif isinstance( n, Quaternion ):
                return Quaternion( self.a-n.a, self.b-n.b, \
                    self.c-n.c, self.d-n.d )
            return NotImplemented
        def __rsub__( self, n ):
            # alleen nodig voor int en float
            #  gebruikt __sub__ voor Quaternion links of rechts
            return Quaternion(n, 0, 0, 0) - self

    c1 = Quaternion( 3, 4, 5, 6 )
    c2 = Quaternion( 1, 2, 3, 4 )
    print( c1 - c2 )
    print( c1 - 10 )
    print( 10 - c1 )
    c1 += c2
    # print( c1 += c2 ) # invalid syntax
    print( c1, c2 )

#=============================================================#
# 21.4 Eénwaardige operatoren

# __neg__()    -       negatie
# __pos__()    +       unaire plus
# __invert__() ~       bitsgewijze not
# __abs__()    abs()   absolute waarde
# __int__()    int()   integer conversie
# __float__()  float() float conversie
# __round__()  round() int of float afronding, optioneel met decimalen
# __bytes__()  bytes() bytes conversie

if 0:
    markeer( '21.4' )

    # listing2106.py
    class Quaternion:
        def __init__( self, a, b, c, d ):
            self.a, self.b, self.c, self.d = a, b, c, d
        def __repr__( self ):
            return "({},{}i,{}j,{}k)".format( self.a, self.b,
                self.c, self.d )
        def __neg__( self ):
            return Quaternion( -self.a, -self.b, -self.c, -self.d )
        def __abs__( self ):
            return Quaternion( abs( self.a ), abs( self.b ),
                abs( self.c ), abs( self.d ) )
        def __bytes__( self ):
            return self.__str__().encode( "utf-8" )

    c1 = Quaternion( 3, -4, 5, -6 )
    print( c1 )
    print( -c1 )
    print( abs( c1 ) )
    print( bytes( c1 ) )

    # print( +c1 ) # niet in boek, TypeError

# beter geen __int__(), __float__(), __round__() methodes
#   implementatie niet zinvol

#=============================================================#
# 21.5 Sequenties

# eerder aantal sequentie classes besproken
#   tuple, list, dict en set
#     en ook str strings

# serie elementen
# benaderen met index of key

# kan zelf sequentie class implementeren

# __len__()      len()  integer aantal elementen
# __getitem__()  [key]  element van key (of index) argument
# __setitem__()  [key]= toekennen van een waarde aan een element
# __delitem__()  del    verwijderen van element
# __missing__()         via __getitem__() als key (of index) niet bestaat
#                         vooral voor subclasses van dict, zie hoofdstuk 22
# __contains__() in     bool element bestaat

if 0:
    markeer( '21.5' )

    # listing2107.py
    class FilippineWoord:
        def __init__( self, woord, index, vraag ):
            self.woord = woord
            self.index = index
            self.vraag = vraag

    class Filippine:
        def __init__( self, naam, woorden ):
            self.naam, self.woorden = naam, woorden
        def __len__( self ):
            return len( self.woorden )
        def __getitem__( self, n ):
            # if n < 0:  # niet in boek
            #     raise IndexError('Index moet >= 0 zijn')
            return self.woorden[n]
        def __setitem__( self, n, waarde ):
            self.woorden[n] = waarde
        def __delitem__( self, n ):
            del self.woorden[n]
        def toon( self ):
            print( self.naam )
            for i in range( len( self ) ):
                print( "{}. {}".format( i+1, self[i].vraag ),
                    end = " " )
                for j in range( len( self[i].woord ) ):
                    if j == self[i].index:
                        print( "* ", end="" )
                    else:
                        print( "_ ", end="" )
                print()
        def oplossing( self ):
            s = ""
            for i in range( len( self ) ):
                s += self[i].woord[self[i].index]
            return s

    puzzel = Filippine(
        "De Monty Python en de Heilige Graal Filippine",
        [ FilippineWoord( "ANTHRAX", 5,
            "Sir Galahad bestormde kasteel" ),
        FilippineWoord( "BORS", 2, "Een konijn doodde Sir" ),
        FilippineWoord( "TIM", 0, "De wijze tovenaar heet" ),
        FilippineWoord( "HERBERT", 0,
            "De erfgenaam van het Moeras Kasteel is prins" ),
        FilippineWoord( "ZWALUW", 4,
            "Een kokosnoot is te zwaar voor een Europese" ),
        FilippineWoord( "MINSTREELS", 5,
            "De ridders aten Robins" ) ] )

    puzzel.toon()

    # print( puzzel.oplossing() )

# voor sequenties bestaat
#   nog een belangrijke methode __iter__()
# wordt besproken in
#   hoofdstuk 23 Iteratoren en Generatoren

# kan soms methodes
#   __add__() en __mul__() implementeren
# zoals bijvoorbeeld bij str

#-------
# Opgave

if 0:
    markeer( '21.5 Opgave A' )

    # listing2108.py
    class Zin:
        def __init__( self, words ):
            self.words = words
        def __repr__( self ):
            return " ".join( self.words )

    s = Zin( [ "Er", "is", "slechts", "een", "ding", "ter",
    "wereld", "erger" "dan", "beroddeld", "worden", "en",
    "dat", "is", "niet", "beroddeld", "worden" ] )
    print( s )
    print( len( s ) )
    print( s[7] )
    s[7] = "prettiger"
    print( "beroddeld" in s )

if 0:
    markeer( '21.5 Opgave B' )

    # komma ontbreekt tussen "erger" en "dan"
    class Zin:
        def __init__( self, words ):
            self.words = words
        def __repr__( self ):
            return " ".join( self.words )
        def __len__( self ):
            return len(self.words)
        def __getitem__( self, index ):
            # print('in getitem')
            return self.words[index]
        def __setitem__( self, index, waarde ):
            self.words[index] = waarde
        def __contains__( self, waarde ): # kan zonder
            return waarde in self.words

    s = Zin( [ "Er", "is", "slechts", "een", "ding", "ter",
    "wereld", "erger" "dan", "beroddeld", "worden", "en",
    # "wereld", "erger", "dan", "beroddeld", "worden", "en",
    "dat", "is", "niet", "beroddeld", "worden" ] )
    print( s )
    print( len( s ) )
    print( s[7] )
    s[7] = "prettiger"
    print( "beroddeld" in s )
    
    # geen komma tussen "erger" en "dan", met beter leesbaar
    print( s )

#=============================================================#
# Wat je geleerd hebt

# Operator overloading
# Overloaden van vergelijkingsoperatoren middels __eq__(), __ne__(), __gt__(),
#   __ge__(), __lt__(), en __le__()
# NotImplemented
# __bool__()
# Overloaden van operatoren voor berekeningen middels __add__(), __sub__(),
#   __mul__(), __truediv__(), __floordiv__(), __mod__(), __pow__(), __lshift__(),
#   __rshift__(), __and__(), __or__(), en __xor__()
# Rechtshandige versies van overload methodes voor berekeningen
# Verkorte versies van overload methodes voor berekeningen
# Overloaden van eenwaardige operatoren __neg__(), __pos__(), __invert__(),
#   __abs__(), __int__(), __float__(), __round__(), en __bytes__()
# Overloaden van operatoren voor sequentie classes __len__(), __getitem__(),
#   __setitem__(), __delitem__(), __missing__(), en __contains__()

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 21.1
# Opgave 21.2
# Opgave 21.3
# Opgave 21.4

###############################################################
# 22 Overerving

#=============================================================#
# 22.1 Class overerving

if 0:
    markeer( '22.1 A' )

    # listing2201.py
    class Persoon:
        def __init__( self, voornaam, achternaam, leeftijd ):
            self.voornaam = voornaam
            self.achternaam = achternaam
            self.leeftijd = leeftijd
        def __repr__( self ):
            return "{} {}".format( self.voornaam, self.achternaam )
        def minderjarig( self ):
            return self.leeftijd < 18

    class Student( Persoon ):
        pass

    albert = Student( "Albert", "Applebaum", 19 )
    # albert = Persoon( "Albert", "Applebaum", 19 ) # niet in boek
    print( albert )
    print( albert.minderjarig() )

#-------------------------------------------------------------#
# 22.1.1 "Is een" relaties

# stel vraag
# <subclass> is een <superclass>?

# Appel subclass van Fruit

# Fruit geen subclass van Fruitmand

#-------
# Opgave

# overervingsrelatie tussen:

#  Atoom en Molecuul?

#  Voertuig en Auto?

#---------
# Antwoord

# molecuul bestaat uit atomen maar is geen atoom
#   en een atoom is geen molecuul
#   wel bepaalde zoals He maar niet in het algemeen
#   geen overervingsrelatie

# auto is een voertuig
#   auto kan subclass zijn van voertuig

#-------------------------------------------------------------#
# 22.1.2 Uitbreiden en overschrijven

if 0:
    markeer( '22.1.2' )

    # listing2202.py
    class Persoon:
        def __init__( self, voornaam, achternaam, leeftijd ):
            self.voornaam = voornaam
            self.achternaam = achternaam
            self.leeftijd = leeftijd
        def __repr__( self ):
            return "{} {}".format( self.voornaam, self.achternaam )
        def minderjarig( self ):
            return self.leeftijd < 18

    class Student( Persoon ):
        def __init__( self, voornaam, achternaam,
            leeftijd, programma ):
            super().__init__( voornaam, achternaam, leeftijd )
            # Persoon.__init__( self, voornaam, achternaam, leeftijd ) # niet in boek
            self.cursussen = []
            self.programma = programma
        def minderjarig( self ):
            return self.leeftijd < 21
        def inschrijven( self, cursus ):
            self.cursussen.append( cursus )

    albert = Student( "Albert", "Applebaum", 19, "CSAI" )
    # albert = Persoon( "Albert", "Applebaum", 19 ) #, "CSAI" ) # niet in boek
    print( albert )
    print( albert.minderjarig() )
    print( albert.programma )
    albert.inschrijven( "Toepassingen van rationaliteit" )
    albert.inschrijven( "Verweer tegen de zwarte kunsten" )
    print( albert.cursussen )

#-------------------------------------------------------------#
# 22.1.3 Meervoudige overerving

# is ingewikkeld
# beter niet gebruiken

#=============================================================#
# 22.2 Interfaces

if 0:
    markeer( '22.2 A' )

    # listing2203.py
    class Voertuig:
        def __init__( self ):
            self.startpunt = []
            self.eindpunten = []
            self.werkwoord = ""
            self.naam = ""
        def __str__( self ):
            return self.naam
        def isStartpunt( self, p ):
            return NotImplemented
        def isEindpunt( self, p ):
            return NotImplemented
        def snelheid( self, p1, p2 ):
            return NotImplemented
        def reisWerkwoord( self ):
            return NotImplemented

# niet in boek
# in Python hebben alle classes
#   object als hoogste niveau superclass
# ook class en functie zijn afgeleid van object

if 0:
    markeer( '22.2 B' )

    a = object()
    print(isinstance(a, object), a, type(a))
    b = 1
    print(isinstance(b, object), b, type(b))
    c = 1.0
    print(isinstance(c, object), c, type(c))
    d = '1'
    print(isinstance(d, object), d, type(d))
    class e:
        pass
    print(isinstance(e, object), e, type(e))
    f = e()
    print(isinstance(f, object), f, type(f))
    g = object
    print(isinstance(g, object), g, type(g))
    f = type
    print(isinstance(f, object), f, type(f))
    g = print
    print(isinstance(g, object), g, type(g))
    
# in diverse andere programmeertalen
#   is inheritance noodzakelijk
#   voor sommige constructies
#   vanwege het type systeem
# daardoor ontstaan soms
#   een grote onoverzichtelijke hiërarchie
#   zoals bijvoorbeeld bij Java of C#
# Python werkt met duck typing
#   kan van verschillende classes
#     dezelfde methode gebruiken
#   zonder dat ze een superclass hebben
# uit https://en.wikipedia.org/wiki/Duck_typing

if 0:
    markeer( '22.2 C' )

    class Duck:
        def swim(self):
            print("Duck swimming")

        def fly(self):
            print("Duck flying")

    class Whale:
        def swim(self):
            print("Whale swimming")

    for animal in [Duck(), Whale()]:
        animal.swim()
        animal.fly()

# voor afscherming van attributen
#   in andere talen public protected private
#   alleen de public attributen en methoden
#     kunnen buiten de class gebruikt worden
#     maakt wijzigingen eenvoudiger
#   de protected alleen in de class en subclasses
#   de private alleen in de class
# in Python geen public protected private
#   kan wel attribuut of method naam
#   met 2 underscore karakters __ laten beginnen
#   Python zet dan class naam ervoor
#     is niet echt afgeschermd

if 0:
    markeer( '22.2 E' )

    class Test:
        def __init__(self):
            self.x = 1
            self._y = 2
            self.__z = 3
        def __repr__(self):
            return f'{self.x},{self._y},{self.__z}'

    test = Test()
    print(test)
    test.x = 11
    test._y = 12
    test.__z = 13
    print(test)
    print(test.__z)
    print(test._Test__z)

    test._Test__z = 23
    print(test)
    print(test.__z)
    print(test._Test__z)

    # print('dir()', dir(test))
    # print()

    # print('__dir__()', test.__dir__())
    # print()

    # print('vars', vars(test))
    # print('__dict__', test.__dict__)

# niet in boek
# in Python ook static en class methods
# zie 20.5 D hierboven
# zie https://realpython.com/instance-class-and-static-methods-demystified/

# Object Oriëntatie is belangrijk
#   maar niet de oplossing voor elk probleem
# zo lijkt er bijvoorbeeld een mapping te zijn
#   van classes naar SQL database tabellen
#   inheritance maakt deze mapping lastig
# zie https://stackoverflow.com/questions/3579079/how-can-you-represent-inheritance-in-a-database

#=============================================================#
# Wat je geleerd hebt

# - Overerving
# - Overschrijven
# - Class calls
# - super()
# - Meervoudige overerving
# - Interfaces

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 22.1 
# Opgave 22.2
# Opgave 22.3

#---------------------------------------------------------------------#

# Script: Expeditie-door-H23.py (1)
#         Uittreksel uit en bewerking van
#         ProgrammeursleerlingExtra.py, 2024-01-04 15:11 (2)
# Datum : 2024-01-04, Theo van Haandel
#         2024-01-18, Meindert Meindertsma
#         2024-01-24  (1) in (2) hoofdstuk 23 en appendix X 23
    
###############################################################
# 23 Iteratoren en Generatoren

#=============================================================#
# 23.1 Iteratoren

if 0:
    markeer( '23.1 A' )
    
    # listing2301.py
    for i in [1,2,3,4]:
        print( i, end=" " )
    print()
    for i in ( "pi", 3.14, 22/7 ):
        print( i, end=" ")
    print()
    for i in range( 3, 11, 2 ):
        print( i, end=" ")
    print()
    for c in "Hallo":
        print( c, end=" " )
    print()
    for key in { "appel":1, "banaan":3 }:
        print( key, end=" " )

if 0:  # niet in boek: next(<iterator>)
    markeer( '23.1 B' )

    # print( next( range( 1, 5 ) ) )
    try:
        print( next( range( 1, 5 ) ) )
    except TypeError as exc:
        print('TypeError:', exc)
    print( next( iter( range( 1, 5 ) ) ) )
    
if 0:
    markeer( '23.1 C' )
    
    iterator = iter( ["appel", "banaan", "kers"] )
    print( next( iterator , "END" ) )
    print( next( iterator , "END" ) )
    print( next( iterator , "END" ) )
    print( next( iterator , "END" ) )

if 0:
    markeer( '23.1 D' )
    
    iterator = iter( ["appel", "banaan", "kers"] )
    for fruit in iterator:
        print( fruit )

#-------------------------------------------------------------#
# 23.1.1 Iterabele objecten

if 0:
    markeer( '23.1.1 A' )
    
    # listing2302.py
    class Fibo:
        def __init__( self ):
            self.seq = [1,1,2,3,5,8,13,21,34,55]
        def __iter__( self ):
            return self
        def __next__( self ):
            if len( self.seq ) > 0:
                return self.seq.pop(0)
            raise StopIteration()

    fseq = Fibo()
    for n in fseq:
        print( n, end=" " )

if 0:
    markeer( '23.1.1 B' )
    
    # listing2303.py
    class Fibo:
        def __init__( self ):
            self.seq = [1,1,2,3,5,8,13,21,34,55]
            self.index = -1
        def __iter__( self ):
            return self
        def __next__( self ):
            if self.index < len( self.seq )-1:
                self.index += 1
                return self.seq[self.index]
            raise StopIteration()
        def reset( self ):
            self.index = -1

    fseq = Fibo()
    for n in fseq:
        print( n, end=" " )
    print()
#    fseq.reset()
    for n in fseq:
        print( n, end=" " )

if 0:
    markeer( '23.1.1 C' )
    
    # listing2304.py
    class Fibo:
        def reset( self ):
            self.nr1 = 0
            self.nr2 = 1
        def __init__( self, maxnum=1000 ):
            self.maxnum = maxnum
            self.reset()
        def __iter__( self ):
            return self
        def __next__( self ):
            if self.nr2 > self.maxnum:
                raise StopIteration()
            nr3 = self.nr1 + self.nr2
            self.nr1 = self.nr2
            self.nr2 = nr3
            return self.nr1

    fseq = Fibo()
    for n in fseq:
        print( n, end=" " )
    print()
#    fseq.reset()
    for n in fseq:
        print( n, end=" " )

#-------
# Opgave

if 0:
    markeer( '23.1.1 Opgave' )
    
    class Kwadraat:
        def reset( self ):
            self.num = 1
        def __init__( self, maxnum=10 ):
            self.maxnum = maxnum
            self.reset()
        def __iter__( self ):
            return self
        def __next__( self ):
            if self.num > self.maxnum:
                raise StopIteration()
            num = self.num
            self.num += 1
            return num ** 2

    kseq = Kwadraat()
    for n in kseq:
        print( n, end=" " )
    print()
    kseq.reset()
    for n in kseq:
        print( n, end=" " )

#-------------------------------------------------------------#
# 23.1.2 Gedelegeerde iteratie

if 0:
    markeer( '23.1.2 A' )
    
    # listing2305.py
    class FiboIterable: # niet in boek: beter naam FiboIterator
        def __init__( self, seq ):
            self.seq = seq
#        def __iter__(self):
#            return self
        def __next__( self ):
            if len( self.seq ) > 0:
                return self.seq.pop(0)
            raise StopIteration()

    class Fibo:
        def __init__( self, maxnum=1000 ):
            self.maxnum = maxnum
        def __iter__( self ):
            nr1 = 0
            nr2 = 1
            seq = []
            while nr2 <= self.maxnum:
                nr3 = nr1 + nr2
                nr1 = nr2
                nr2 = nr3
                seq.append( nr1 )
            return FiboIterable( seq )

    fseq = Fibo()
    for n in fseq:
        print( n, end=" " )
    print()
    for n in fseq:
        print( n, end=" " )

# niet in boek
# hierboven in listing2305.py
# naam FiboIterable is niet goed gekozen
# want het is geen iterable, heeft geen __iter__ methode
# class duidelijker met naam FiboIterator
# zoals ook een list_iterator bij een list

if 0:
    markeer( '23.1.2 B' )

    # niet in boek
    lst = [1,1,2,3,5,8,13,21,34,55]
    print( type( lst ) )
#    print( next( lst ) )
    lstitor = iter(lst)
    print( type( lstitor ) )
    print( next( lstitor ) )
    
#    print( next( [1,1,2,3,5,8,13,21,34,55] ) )
#    print( next( iter([1,1,2,3,5,8,13,21,34,55]) ) )
#    fibit = FiboIterable( [1,1,2,3,5,8,13,21,34,55] )
#    print( next( iter(fibit) ) )    
#    print( next( fibit ) )    
#    print( next( fibit ) )    
#    print( next( fibit ) )
#   for n in fibit:
#        print( n, end=" " )

# iterator die los staat van een iterable
#    3 voordelen in boek 23.1.2:
# 1) meer instanties mogelijk
#    die tegelijkertijd in gebruik zijn
#      zoals bij geneste for loop
# 2) geen reset nodig
# 3) automatisch uit geheugen

# niet in boek, ook nog:
# 4) iterable hoeft dan niet te veranderen
#      tijdens het itereren
#    iterable kan dan immutable zijn
#      zoals een int of tuple
#    iterator is mutable
#      zoals bij een for
#      moet huidige positie bijhouden

if 0:
    markeer( '23.1.2 C' )

    # niet in boek

    # str, tuple, list en range hebben een gedelegeerde iterator
    # iterable = 'xyz'
    iterable = ( 1, 3, 7 ) # tuple
    # iterable = [ x + 1 for x in (2, 5, 8) ] # list comprehension
    # iterable = range(3)

    # zie ook hieronder 23.2.1 Generator expressies
    # een generator kan maar 1 keer doorlopen worden
    #  is daarom zelf zijn eigen iterator
    # iterable = ( x + 2 for x in (3, 7, 9) ) # generator expressie

    # zip kan ook maar 1 keer doorlopen worden
    #   is zijn eigen iterator
    # iterable = zip( 'txt', [ 2, 5, 11] )

    # enumerate niet in boek
    # kan ook maar 1 keer doorlopen worden
    #   is zijn eigen iterator
    # iterable = enumerate( [ 10, 20, 30 ] )

    print( id( iterable ), type( iterable ) )
    print( id( iter( iterable )), type( iter( iterable ) ) )
    for x in iterable:
        for y in iterable:
            print( x, y )

if 0:
    markeer( '23.1.2 D' )

    # niet in boek
    # for loop nagemaakt met while
    # 2024-01-19 Theo van Haandel

    # ingebouwde for
    for i in 1, 2, 8:
        print( i )
    # for i in range(5):
    #     print( i )
    # for i, (j, ch) in enumerate( [ ( 3, 'a' ), ( 4, 'b' ) ] ):
    #     print( i, j, ch )
    else:
        print( 'in for else' )

    # emuleer for loop met while
    iterable = 1, 2, 8
    # iterable = range(5)
    # iterable = enumerate( [ ( 3, 'a' ), ( 4, 'b' ) ] )

    iterator = iter( iterable )
    stop = False
    while not stop:
        try:
            nx = next( iterator )
        except StopIteration:
            stop = True # geen break vanwege else
        else:
            i = nx
            print( i )
            # i, (j, ch) = nx
            # print( i, j, ch )
    else:
        print( 'in while else' )

if 0:
    markeer( '23.1.2 D' )

    # niet in boek, voor gevorderden
    # Script: for-emulatie.py
    # Datum : 2024-01-21 & 22, Meindert Meindertsma


    #============#
    #  FUNCTIES  #  
    #============#

    #---- Theo's for-emulatie ----#
    def emul(pakketje, bron, original=True, remove=False ):
        bronobject = eval( bron )
        iterError  = TypeError if original else AttributeError
        if remove:
            volgende = bronobject.__class__.__next__
            nl       = "\n          "
        else:
            nl       = ""

        try:
            iterator = iter( bronobject )
        except Exception as e:
            print( f"prewhile: {e.__class__.__name__}:", e )
            return

        stop = False
        print( "while   :", end=" " )
        while not stop:
            try:
                nx = next( iterator ) if original else iterator.__next__()
            except StopIteration:
                stop = True # geen break vanwege else
            except iterError as e:
                print( f"{nl}TypeError:",
                       f"'{bronobject.__class__.__name__}' object is not iterable" )
                break
            except Exception as e:
                print( f"{nl}{e.__class__.__name__}:", e )
                break
            else:
                exec( f'{pakketje} = nx; print({pakketje}, end=" -- ")' )
                if remove:
                    del bronobject.__class__.__next__
        else:
            print( "STOP" )

        if remove:
            bronobject.__class__.__next__ = volgende

    #---- Python's for-statement ----#
    def voor( pakketje, bron, remove=False ):
        bronobject    = eval( bron )
        for_statement = f'''
for {pakketje} in bronobject:
    print( {pakketje}, end=" -- " )
    if remove:
        del bronobject.__class__.__next__
else:
    print( "STOP" )
'''
        if remove:
            volgende = bronobject.__class__.__next__
            nl       = "\n          "
        else:
            nl       = ""

        print( "for     :", end= " " )
        try:
            exec( for_statement )
        except Exception as e:
            print( f"{nl}{e.__class__.__name__}:", e )

        if remove:
            bronobject.__class__.__next__ = volgende


    #---- Combinatie van beide ----#
    def samen( pakketje, bron, remove=False ):
        emul( pakketje, bron, True, remove )
        emul( pakketje, bron, False, remove )
        voor( pakketje, bron, remove )


    #===========#
    #  CLASSES  #  
    #===========#

    #---- Niet iterabel, geen iterator ----#
    class Type1_Bron:
        def __init__(self, naam, lijst):
          self.naam  = naam
          self.lijst = lijst

    #---- Formeel iterabel, feitelijk niet, geen iterator ----#
    class Type2_Bron(Type1_Bron):
        def __iter__(self):
            return self

    #---- Niet iterabel, wel een iterator ----#
    class Type3_Bron(Type1_Bron):
        def __next__(self):
            if len(self.lijst) > 0:
                return self.lijst.pop(0)
            raise StopIteration()

    #---- Feitelijk iterabel, tevens een iterator ----#
    class Type4_Bron(Type2_Bron, Type3_Bron):
        pass

    #---- Formeel iterabel, feitelijk niet, wel een iterator ----#
    class Type5_Bron(Type3_Bron):
        def __iter__(self):
            return self.lijst

    #---- Feitelijk iterabel, maar
    #     afgifte overgedragen aan externe iterator ----#
    class Type6_Bron(Type1_Bron):
        def __iter__(self):
            return Type4_Bron("anoniem", [10*x for x in self.lijst])

    #---- Feitelijk een iterabele iterator, maar
    #     afgifte toch overgedragen aan externe iterator ----#
    class Type7_Bron(Type3_Bron, Type6_Bron):
        pass

    #---- Iterabele iterator ----#
    class Type8_Bron(Type2_Bron):
        def __next__(self):
            if len(self.lijst) > 0:
                return self.lijst.pop(0)
            raise StopIteration()


    #===============#
    #  AAN DE SLAG  #
    #===============#

    print("\n----- 1, 2, 8 -----")
    pakketje = "i"
    bron     = "1, 2, 8"
    samen( pakketje, bron )

    print("\n----- range(5) -----")
    pakketje = "i"
    bron     = "range(5)"
    samen( pakketje, bron )

    print("\n----- enumerate( [ ( 3, 'a' ), ( 4, 'b' ) ] ) -----")
    pakketje = "i, (j, ch)"
    bron     = "enumerate( [ ( 3, 'a' ), ( 4, 'b' ) ] )"
    samen( pakketje, bron )

    print("\n----- Type1_Bron -----")
    pakketje = "i"
    bron     = "Type1_Bron( 'bron1', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Type2_Bron -----")
    pakketje = "i"
    bron     = "Type2_Bron( 'bron2', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Type3_Bron -----")
    pakketje = "i"
    bron     = "Type3_Bron( 'bron3', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Type4_Bron -----")
    pakketje = "i"
    bron     = "Type4_Bron( 'bron4', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Type5_Bron -----")
    pakketje = "i"
    bron     = "Type5_Bron( 'bron5', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Type6_Bron -----")
    pakketje = "i"
    bron     = "Type6_Bron( 'bron6', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Type7_Bron -----")
    pakketje = "i"
    bron     = "Type7_Bron( 'bron7', [1, 2, 3, 4] )"
    samen( pakketje, bron )

    print("\n----- Iterabele iterator waarvan __next__ wordt verwijderd -----")
    pakketje = "i"
    bron     = "Type8_Bron( 'bron8', [1, 2, 3, 4] )"
    samen( pakketje, bron, True )

#-------------------------------------------------------------#
# 23.1.3 zip()

if 0:
    markeer( '23.1.3 A' )

    z = zip( [1,2,3], [4,5,6], [7,8,9] )
    for x in z:
        print( x )
        
    # niet in boek
    print()
    print( zip( [1,2,3], [4,5,6], [7,8,9] ) )
    print( next( zip( [1,2,3], [4,5,6], [7,8,9] ) ) )

if 0:
    markeer( '23.1.3 B' )

    # listing2306.py
    class Dubbel:
        def __init__( self ):
            self.seq = [2*x for x in range( 1, 11 )]
        def __iter__( self ):
            return self
        def __next__( self ):
            return self.seq.pop(0)

    # seq = zip( range( 1, 11 ), Dubbel(), [3*x for x in range(1,11)] )
    seq = zip( range( 1, 11 ), Dubbel(), [ 3 * x for x in range( 1, 11 )] )
    for x in seq:
        print( x )

#-------
# Opgave

if 0:
    markeer( '23.1.3 Opgave' )

    seq = zip( range( 1, 11 ), [ x ** 2 for x in range( 1, 11 ) ] )
    for x in seq:
        print( x )

if 0:
    markeer( '23.1.3 C' )

    # enumerate niet in boek
    # enumerate is een ingebouwde functie net als zip
    # werkt ongeveer als zip met range als eerste argument
    #  maar enumerate hoeft len niet te weten
    #  en heeft optioneel start argument

    src = [ 31, 22 ]
    # src = [ ( 3, 'a' ), ( 4, 'b' ) ]
    # src = 'txt', { 3.1 }
    # src = range( 11, 15 )
    # src = [ x * x for x in range( 3 ) ]

    # generator expressie
    # maar 1 keer te doorlopen
    # heeft geen len
    # src = ( x * x for x in range( 5 ) )
    def rangeZonderEinde( start=0 ):
        val = start
        while True:
            yield val
            val += 1

    if 1:
        seq = zip( range( len(src) ), src ) # geeft TypeError bij generator
        # seq = zip( range( 10000 ), src ) # werkt wel met generator
        # seq = zip( rangeZonderEinde( ), src ) # werkt ook met generator
        # seq = zip( src, src ) # test
        for x in seq:
            print( 'zip', x )

    if 1:
        seq = enumerate( src )
        # seq = enumerate( src, 3 )
        for x in seq:
            print( 'enumerate', x )

    # nagemaakte enumerate
    def enumerate2( seq, start=0 ):
        val = start
        for item in seq:
            yield val, item
            val += 1

    seq = enumerate2( src )
    # seq = enumerate2( src, 25 )
    for x in seq:
        print( 'enumerate2', x )

#-------------------------------------------------------------#
# 23.1.4 reversed()

if 0:
    markeer( '23.1.4' )

    fruitlist = ["appel", "peer", "kers", "banaan"]
    for fruit in reversed( fruitlist ):
        print( fruit )
        
    # niet in boek
    print()
    print( reversed( fruitlist ) )
    print( next( reversed( fruitlist ) ) )
#    print( next( list( reversed( fruitlist ) ) ) )

#-------------------------------------------------------------#
# 23.1.5 sorted()

if 0:
    markeer( '23.1.5' )

    fruitlist = ["appel", "peer", "kers", "banaan"]
    for fruit in sorted( fruitlist ):
        print( fruit )
        
    # niet in boek
    print()
    print( sorted( fruitlist ) )
    print( next( sorted( fruitlist ) ) )

#=============================================================#
# 23.2 Generatoren

if 0:
    markeer( '23.2 A' )

    # listing2307.py
    def fibo( maxnum ):
        nr1 = 0
        nr2 = 1
        while nr2 <= maxnum:
            nr3 = nr1 + nr2
            nr1 = nr2
            nr2 = nr3
            yield nr1

    fseq = fibo( 1000 )
#    print( next( fseq ), "weggekaapt" )
#    print( fseq.__next__(), "weggekaapt" )
    for n in fseq:
        print( n, end=" " )
#        if n > 8: break
    print()
#    print( next( fseq ) )
#    print()
#    for n in fseq:
#    fseq = fibo( 1000 ):
#    for n in fseq:
    for n in fibo( 1000 ):
        print( n, end=" " )
        
    # niet in boek
    print()
    print( fseq )

if 0:
    markeer( '23.2 B' )
    
    # niet in boek
    # generator functies
    #   verder uitproberen
    def yieldtest():
        yield 27

    def yieldtest0():
        if False:
            yield 28

    def yieldtest1():
        var = 33
        yield var
        var *= 2
        print( 'in yieldtest1 komt nu', var )
        yield var
        for i in range( 3 ):
            print( 'binnen', i)
            yield i

    def yieldtest2():
        var = 23
        while True:
            print( 'yieldtest2', var )
            yield var
            var *= 2

    # tst = yieldtest()
    # tst = yieldtest0()
    tst = yieldtest1()
    # tst = yieldtest2()
    print( tst )
    for i, y in enumerate(tst):
        print( 'buiten', y )
        if i == 8:
            break
        # input( 'verder' )

#-------------------------------------------------------------#
# 23.2.1 Generator expressies

if 0:
    markeer( '23.2.1 A' )

    # seq = (x*x for x in range( 11 ))
    seq = ( x * x for x in range( 11 ) )
    # seq = [ x * x for x in range( 11 ) ]
    for x in seq:
        print( x, end=" " )
        
    # niet in boek
    print( '\n' )
    print( seq )

if 0:
    markeer( '23.2.1 B' )

    # niet in boek
    # mag haakjes weglaten bij generator expressie
    # bij functie aanroep die iterator verwacht

    # abc = '-'.join( 'a', 'b', 'c' )
    abc = '-'.join( ( 'a', 'b', 'c' ) )
    print( abc )
    jn = '+'.join( str( u ) for u in range( 3 ))
    print( jn )

    # kan alleen met 1 argument
    # zp = zip( v + 1 for v in range( 3 ),
    #           w * 2 for w in range( 3 ) )
    zp = zip( ( v + 1 for v in range( 3 ) ),
              ( w * 2 for w in range( 3 ) ) )
    print( *zp )

#=============================================================#
# 23.3 itertools module

#-------------------------------------------------------------#
# 23.3.1 chain()

if 0:
    markeer( '23.3.1' )

    from itertools import chain

    seq = chain( [1,2,3], [11,12,13,14], [x*x for x in range(1,6)] )
#    seq = chain( [1,2,3], (11,12,13,14), (x*x for x in range(1,6)) )
    for item in seq:
        print( item, end=" ")
        
    # niet in boek
    print( '\n' )
    print( seq )
#    print( next( seq ) )
    print( next( chain( [1,2,3], [11,12,13,14], [x*x for x in range(1,6)] ) ) )

#-------------------------------------------------------------#
# 23.3.2 zip_longest()

if 0:
    markeer( '23.3.2' )

    from itertools import zip_longest

    seq = zip_longest( "appel", "framboos", "banaan", fillvalue=" " )
#    print( next( seq ), ' weggekaapt' )  # niet in boek
    for item in seq:
        print( item )

#-------------------------------------------------------------#
# 23.3.3 product()

if 0:
    markeer( '23.3.3 A' )

    from itertools import product

    seq = product( [1,2,3], "ABC", ["appel","banaan"] )
#    print( next( seq ), ' weggekaapt' )  # niet in boek
    for item in seq:
        print( item )

if 0:
    markeer( '23.3.3 B' )

    # niet in boek
    print( "Geneste loops:" )

    for x in [1,2,3]:
        for y in "ABC":
            for z in ["appel", "banaan"]:
                print( (x, y, z) )

if 0:
    markeer( '23.3.3 C' )

    # niet in boek
    print( "Geneste loops:" )

    collectie = [ (x, y, z) for x in [1,2,3] for y in "ABC" for z in ["appel", "banaan"] ]
    for groepje in collectie: print( groepje )

#-------------------------------------------------------------#
# 23.3.4 permutations()

if 0:
    markeer( '23.3.4' )

    from itertools import permutations

    seq = permutations( [1,2,3], 2 )
#    print( next( seq ), ' weggekaapt' )  # niet in boek
    for item in seq:
        print( item )

#-------------------------------------------------------------#
# 23.3.5 combinations()

if 0:
    markeer( '23.3.5' )

    from itertools import combinations

    seq = combinations( [1,2,3], 2 )
#    print( next( seq ), ' weggekaapt' )  # niet in boek
    for item in seq:
        print( item )

#-------------------------------------------------------------#
# 23.3.6 combinations_with_replacement()

if 0:
    markeer( '23.3.6' )

    from itertools import combinations_with_replacement

    seq = combinations_with_replacement( [1,2,3], 2 )
#    print( next( seq ), ' weggekaapt' )  # niet in boek
    for item in seq:
        print( item )

#=============================================================#
# Wat je geleerd hebt

# - Iteratoren
# - Iterabelen
# - __iter__(), __next__(), en StopIteration
# - Verschillende manier om iterabele objecten te implementeren
# - zip()
#   niet in boek: ook reversed() en sorted()
# - Generatoren
# - yield
# - Generator expressies
# - Van de itertools module de functies chain(), zip_longest(), product(),
#   permutations(), combinations(), en combinations_with_replacement()

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 23.1
# Opgave 23.2
# Opgave 23.3
# Opgave 23.4
# Opgave 23.5
# Opgave 23.6
# Opgave 23.7

#---------------------------------------------------------------------#

###############################################################
# 24 Command Line Verwerking

# zie ook hoofdstuk 15 Besturingssysteem

#=============================================================#
# 24.1 De command line

# Python programma's starten met
#  python <programmanaam>.py

# werkt alleen als systeem python kan vinden
#   en .py in huidige directory staat
# kan anders volledige pad opgeven

# niet in boek
# kan met -V versienummer opvragen
#  python -V

# kan bij Windows ook py.exe loader gebruiken
# met name handig bij meer versies in gebruik
#  py <programmanaam>.py

# bij Linux en macOS vaak python3 nodig
# omdat python nog naar versie 2 verwijst
#  python3 <programmanaam>.py

#-------------------------------------------------------------#
# 24.1.1 Batch verwerking

# als vaak aanpassing Python programma nodig
# om verschillende bestanden
#  in verschillende directories
#  te verwerken

# soms beter aanroepen vanuit command shell
# door opdrachten te zetten in
#   Windows batch .bat
#   Mac of Linux willekeurige naam

# moet dan wel kunnen doorgeven
#   welk bestand of directory

#-------------------------------------------------------------#
# 24.1.2 Command line argumenten

# kan argumenten meegeven bij aanroepen python:
# python <programmanaam>.py <argument_1> <argument_2> ... <argument_n>

# argumenten met spaties tussen dubbele quotes ".."
# argumenten met dubbele quotes meestal \" of "" van maken

#-------------------------------------------------------------#
# 24.1.3 sys.argv

# beschikbaar na import sys module
# list van strings
# iedere string is een command line argument

# minimaal 1 argument op index 0
# argv[0] bevat meestal naam van Python bestand
#   dat wordt uitgevoerd
#   afhankelijk van aanroep inclusief pad zoals meegegeven
# argv[0] is lege string in de IDLE Shell

if 0:
    markeer( '24.1' )

    # niet in boek
    import sys

    print( 'argv', len(sys.argv) )
    for arg in sys.argv:
        print( arg )

#=============================================================#
# 24.2 Flexibele command line verwerking

# testen vanuit editor
# dan lastig met command line te werken

# gaat beter met globale variabelen
# die door command line argumenten
#   overschreven kunnen worden
# meteen bij start daarop controleren

# kan dan tijdens ontwikkelen
#   globale variabele wijzigen
#   of vragen aan gebruiker
#   als niet meegegeven op command line

# aantal command line argumenten
#  kan zo ook variabel zijn
# gebruikt default globale waarde
#  als niet meegegeven

if 0:
    markeer( '24.2' )

    # listing2401.py
    import sys

    # 3 variabelen die de command line parameters bevatten
    invoer = "input.txt"
    uitvoer = "output.txt"
    shift = 3

    # Verwerken van command line parameters
    # (werkt met 0, 1, 2, of 3 parameters)
    if len( sys.argv ) > 1:
        invoer = sys.argv[1]
    if len( sys.argv ) > 2:
        uitvoer = sys.argv[2]
    if len( sys.argv ) > 3:
        try:
            shift = int( sys.argv[3] )
        # except TypeError: # fout in boek? int() geeft ValueError
        except ValueError:  # misschien wel goed bij oudere Python versies
            ## nakijken
            print( sys.argv[3], "is geen getal." )
            sys.exit(1)

    # niet in boek
    print( invoer )
    print( uitvoer )
    print( shift )
    
#-------------------------------------------------------------#
# 24.2.1 sys.exit()

# eerder in hoofdstuk 6 Condities

# kan een int argument meekrijgen
# zoals hierboven sys.exit(1)

# als geen waarde meegegeven
# dan exit waarde 0

# niet in boek

# kan ook niet int waarde meegeven
# dan exit waarde 1
# en print conversie naar stderr

if 0:
    markeer( '24.2.1 A' )

    import sys

    if 0:
        sys.exit( 'exit tekst' ) # SystemExit:
    if 0:
        sys.exit( 4.7 )
    if 0:
        sys.exit( [ 1, 2 ] )
    if 1:
        class Test:
            def __str__( self ):
                return 'Test class'
                # return # exceptions
        sys.exit( Test() )

    print( 'Naar stderr', file=sys.stderr )
    print( 'Naar stdout', file=sys.stdout )
    print( 'Ook naar stdout' )  # sys.stdout is default

# wordt terug doorgegeven aan aanroepend programma
# zoals batch file

# niet in boek
# bij Windows command line
#   py bestaatniet.py
#   echo %errorlevel%
# bij Linux en macOS
#   python3 bestaatniet.py
#   echo $?

# sommige systemen waarde beperkt
#   van 0 tot en met 255
# gewoonte om alleen die te gebruiken

# niet in boek
# sys.exit() gebruikt
#   raise SystemExit
#   om programma te stoppen
# zodat try en finally statements
#   nog worden afgehandeld
# SystemException kan afgevangen worden
#   met try except
# als deze exception niet wordt afgehandeld
#   dan wordt er geen stack trace geprint
#   en wordt de exit waarde doorgegeven

if 0:
    markeer( '24.2.1 B' )

    import sys
    
    def test():
        print( 'In test' )
        # sys.exit( ) # 0
        sys.exit( 7 ) # 7
        # sys.exit( "exit tekst" ) # 1

    def test2():
        print( 'In test2' )
        try:
            test()
        finally:
            print( 'In finally test2' )

    if 1:
        test()
        # test2()
    else:
        try:
            test()
            # test2()
        except SystemExit as exc:
            print( 'SystemExit:', exc)
            # input( 'Enter om te stoppen' )

#-------------------------------------------------------------#
# 24.2.2 argparse

# handig bij groter aantal argumenten
# regelt ook help info

# zie https://docs.python.org/3/library/argparse.html#example

#=============================================================#
# Wat je geleerd hebt

# - Command-line argumenten
# - sys.argv
# - Flexibele command-line verwerking

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 24.1

###############################################################
# 25 Reguliere Expressies

# Script: Expeditie-door-H25.py
#         Uittreksel uit en bewerking van
#         ProgrammeursleerlingExtra.py, 2024-02-07 09:21
# Datum : 2024-02-07, Theo van Haandel
#         2024-02-22, Meindert Meindertsma
# weer overgenomen in ProgrammeursleerlingExtra.py 2024-02-29

#=============================================================#
# 25.1 Reguliere expressies met Python

# meta-tekens: . ^ $ * + ? { } [ ] \ | ( )

#-------------------------------------------------------------#
# 25.1.1 De re module

if 0:
    markeer( '25.1.1' )

    # listing2501.py
    import re

    pAplus = re.compile( r"a+" )
    lAplus = pAplus.findall( "aardvarken" )
    print( lAplus )

#-------
# Opgave

if 0:
    markeer( '25.1.1 Opgave' )

    import re

    for woord in 'zonder', 'AA', 'áäà', 'aaaa', 'azaaqaaaa':
        pAplus = re.compile( r"a+" )
        print( '---', pAplus, "is een object van de class", type( pAplus ) )
        lAplus = pAplus.findall( woord )
        print( woord, lAplus )

# ruwe raw string r''

# re.compile 2e optionele argument
#  re.I  ignore  geen onderscheid hoofd- en kleine letters
#  re.S  space   verwerk ook newlines
#  re.M  meta    ^ begin en $ einde op iedere regel in plaats hele string
# kan combineren met | zoals: re.I | re.M

#-------------------------------------------------------------#
# 25.1.2 Verkorte compilatie

if 0:
    markeer( '25.1.2' )

    import re

    lAplus = re.findall( r"a+", "aardvarken" )
#    lAplus = re.findall( r"A+", "aardvarken", re.I )

    print( lAplus )

#-------------------------------------------------------------#
# 25.1.3 Match objecten

if 0:
    markeer( '25.1.3 A' )

    import re

    m = re.search( r"a+", "Kijk uit voor het aardvarken!" )
    print( "{} gevonden op index {}".format( m.group(), m.start() ) )

# methods van m match object
#   group() gevonden patroon
#   start() index start van patroon
#   end()   index einde van patroon

if 0:
    markeer( '25.1.3 B' )

    import re

    m = re.match( r"a+", "Kijk uit voor het aardvarken!" )
#    m = re.search( r"a+", "Kijk uit voor het aardvarken!" )
    if m:
        print( "{} start de string".format( m.group() ) )
    else:
        print( "Het patroon staat niet aan de start van de string" )

#-------------------------------------------------------------#
# 25.1.4 Lists van matches

if 0:
    markeer( '25.1.4' )

    # listing2502.py
    import re

    mlist = re.finditer( r"a+",
        "Kijk uit! Een gevaarlijk aardvarken is ontsnapt!" )
    print(mlist)
#    print( iter( mlist) )  # check of mlist iterabel is
#    print( next( mlist) )  # check of mlist een iterator is
    for m in mlist:
        print( "{} gevonden bij index {} tot index {}.".format(
            m.group(), m.start(), m.end() ) )

#=============================================================#
# 25.2 Reguliere expressies schrijven

#-------------------------------------------------------------#
# 25.2.1 Reguliere expressies met vierkante haken

if 0:
    markeer( '25.2.1' )

    # listing2503.py
    import re

    slist = re.findall( r"b[aeiou]ll", "Bill Gates en Uwe Boll \
dronken Red Bull bij het voetballen in Campbell." )
    print( slist )

#-------
# Opgave

if 0:
    markeer( '25.2.1 Opgave' )

    import re

    slist = re.findall( r"[Bb][aeiou]ll", "Bill Gates en Uwe Boll \
dronken Red Bull bij het voetballen in Campbell." )

#     slist = re.findall( r"[Bb][a-eiou]ll", "Bill Gates en Uwe Boll \
# dronken Red Bull bij het voetballen in Campbell." )

#    slist = re.findall( r"b[aeiou]ll", "Bill Gates en Uwe Boll \
# dronken Red Bull bij het voetballen in Campbell.", re.I )

    print( slist )

# binnen '[ ]'
#  '-' ook tekens ertussen
#    zoals '[a-dqx-z]' equivalent met '[abcdqxyz]'
#  '^' meteen na '[' dan tegengestelde
#    zoals '[^0-9]' alle tekens behalve cijfers
#
# re.I als alternatief voor [Bb]

#-------------------------------------------------------------#
# 25.2.2 Speciale tekens

# meest gebruikte:

# \b    Woord begrenzing (breedte nul)
# \B    Geen woord begrenzing (breedte nul)
# \d    Cijfer [0-9]
# \D    Geen cijfer [^0-9]
# \n    Nieuwe regel (newline)
# \r    Return
# \s    Spatie (inclusief tabulatie)
# \S    Geen spatie
# \t    Tabulatie
# \w    Alfanumeriek teken [A-Za-z0-9_]
# \W    Geen alfanumeriek teken [^A-Za-z0-9_]
# \/    Voorwaartse slash
# \\    Backslash
# \"    Dubbel aanhalingsteken
# \'    Enkel aanhalingsteken
# ^     Start van een string (breedte nul)
# $     Einde van een string (breedte nul)
# .     Ieder teken

# ()    Groeperen
# |     Groep keuze

# '(appel|banaan|peer)' is 'appel' of 'banaan' of 'peer'
# '(appel)|(banaan)|(peer)' is 'appel' of 'banaan' of 'peer'
# 'appel banaan|peer'   is 'appel banaan' of 'peer'
# 'appel (banaan|peer)' is 'appel banaan' of 'appel peer'

# sommige speciale tekens werken niet (of anders) binnen '[ ]'

if 0:
    markeer( '25.2.2 Voorbeeld' )  # niet in boek

    import re

    print( re.findall(r"[\^a\-b\s[\]]", "^a-b cz[]") )
    print( re.findall(r"[-^ab\s[\]]"  , "^a-b cz[]") )
    print( re.findall(r"[a^b\s[\]-]"  , "^a-b cz[]") )

#-------------------------------------------------------------#
# 25.2.3 Herhaling

# *     Nul of meer keer
# +     1 of meer keer
# ?     Nul of 1 keer
# {p,q} Minstens p en hoogstens q keer
# {p,}  Minstens p keer
# {p}   Precies p keer

# herhaling gulzig greedy, zo vroeg en lang mogelijk

if 0:
    markeer( '25.2.3 A' )

    # listing2504.py
    import re

#   mlist = re.finditer( r"ba+.","Schaap zegt 'baaaaah' tot Ali Baba. ba\nbab",
#                         re.S )
    mlist = re.finditer(r"ba+","Schaap zegt 'baaaaah' tot Ali Baba.")
    for m in mlist:
        print( "{} is found at {}.".format(m.group(), m.start()))

#-------
# Opgave

if 0:
    markeer( '25.2.3 Opgave A' )

    import re

    mlist = re.finditer(r"[Bb]a+","Schaap zegt 'baaaaah' tot Ali Baba.")
#    mlist = re.finditer( r"ba+",
#                         "Schaap zegt 'baaaaah' tot Ali Baba.",
#                         re.I )
    for m in mlist:
        print( "{} is found at {}.".format( m.group(), m.start() ) )

#-------
# Opgave

if 0:
    markeer( '25.2.3 Opgave B' )

    import re

    for tekst in ( "Schaap zegt 'baaaaah' tot Ali Baba.",
                   'Brabantse rabarber',
                   'ABaabaaaaZ' ):
        print( tekst )
        mlist = re.finditer( r"([Bb]a+)+", tekst )
        for m in mlist:
            print( "{} is found at {}.".format( m.group(), m.start() ) )
        print()

if 0:
    markeer( '25.2.3 B' )

    # listing2505.py
    import re

    mlist = re.finditer( r"a+", "Schaap zegt 'baaaaah' tot Ali Baba." )
    for m in mlist:
        print( "{} is found at {}.".format( m.group(), m.start() ) )

#-------
# Opgave

if 0:
    markeer( '25.2.3 Opgave C' )

    import re

    mlist = re.finditer( r"a*", "Schaap zegt 'baaaaah' tot Ali Baba." )
    for m in mlist:
        print( "{} is found at {}.".format( m.group(), m.start() ) )

#-------------------------------------------------------------#
# 25.2.4 Oefening

#-------
# Opgave

if 0:
    markeer( '25.2.4 Opgave A' )

    # listing2506.py
    import re

    # List van strings die worden gebruikt voor testen.
    slist = [ "aaabbb", "aaaaaa", "abbaba", "aaa", "bEver ottEr",
        "tango samba rumba", " hello world ", " Hello World " ]

    # Reguliere expressies die moeten worden ingevuld.
    relist = [
        r"",  # 1. Alleen a's gevolgd door alleen b's, inclusief ""
        r"",  # 2. Alleen a's, inclusief ""
        r"",  # 3. Alleen a's en b's, willekeurige volgorde, incl. ""
        r"",  # 4. Precies drie a's
        r"",  # 5. Noch a's noch b's, maar "" is toegestaan
        r"",  # 6. Een even aantal a's (en niks anders)
        r"",  # 7. Precies twee woorden, ongeacht spaties
        r"",  # 8. Bevat een woord dat op "ba" eindigt
        r""   # 9. Bevat een woord dat begint met een hoofdletter
    ]
    for s in slist:
        print( s, ' : ' , sep= ' ' , end= ' ' )
        for i in range( len( relist ) ):
            m = re.search( relist[i], s )
            if m:
                print( i+1, end= ' ' )
        print()

# correcte uitvoer:
# aaabbb: 1 3
# aaaaaa: 1 2 3 6
# abbaba: 3 8
# aaa: 1 2 3 4
# bEver ottEr: 7
# tango samba rumba: 8
#  hello world : 5 7
#  Hello World : 5 7 9

if 0:
    markeer( '25.2.4 Opgave B' )

    import re

    # List van strings die worden gebruikt voor testen.
    slist = [ "aaabbb", "aaaaaa", "abbaba", "aaa", "bEver ottEr",
        "tango samba rumba", " hello world ", " Hello World ",
        "bas", "" ] # extra tests

    # Reguliere expressies die moeten worden ingevuld.
    relist = [
        r"^a*b*$",    # 1. Alleen a's gevolgd door alleen b's, inclusief ""
        r"^a*$",      # 2. Alleen a's, inclusief ""
        r"^[ab]*$",   # 3. Alleen a's en b's, willekeurige volgorde, incl. ""
        r"^aaa$",     # 4. Precies drie a's
        r"^[^ab]*$",  # 5. Noch a's noch b's, maar "" is toegestaan
        r"^(aa)+$",   # 6. Een even aantal a's (en niks anders)
        r"^\s*\w+\s+\w+\s*$", # 7. Precies twee woorden, ongeacht spaties
        # r"\w*ba",     # 8. Bevat een woord dat op "ba" eindigt, fout
        r"\w*ba\b",   # 8. Bevat een woord dat op "ba" eindigt
        r"\b[A-Z]\w*" # 9. Bevat een woord dat begint met een hoofdletter
    ]
    for s in slist:
        print( s, ' : ' , sep= ' ' , end= ' ' )
        for i in range( len( relist ) ):
            m = re.search( relist[i], s )
            if m:
                print( i+1, end= ' ' )
        print()

# uitvoer:
# aaabbb: 1 3
# aaaaaa: 1 2 3 6
# abbaba: 3 8
# aaa: 1 2 3 4
# bEver ottEr: 7
# tango samba rumba: 8
#  hello world : 5 7
#  Hello World : 5 7 9
# bas :
#   :   1 2 3 5

if 0:
    markeer( '25.2.4 Opgave B -- alternatieve oplossing', False )

    import re

    # List van strings die worden gebruikt voor testen.
    slist = [ "aaabbb", "aaaaaa", "abbaba", "aaa", "bEver ottEr",
        "tango samba rumba", " hello world ", " Hello World ",
        "bas", "" ] # extra tests

    # Reguliere expressies die moeten worden ingevuld.
    relist = [
        r"^a+b*$",    #! 1. Alleen a's gevolgd door alleen b's, inclusief ""
        r"^a*$",      #  2. Alleen a's, inclusief ""
        r"^[ab]*$",   #  3. Alleen a's en b's, willekeurige volgorde, incl. ""
        r"^aaa$",     #  4. Precies drie a's
        r"^[^ab]*$",  #  5. Noch a's noch b's, maar "" is toegestaan
        r"^(aa)*$",   #! 6. Een even aantal a's (en niks anders)
        r"^\W*\w+\W+\w+\W*$", #! 7. Precies twee woorden, ongeacht spaties
        r"ba\b",      #! 8. Bevat een woord dat op "ba" eindigt
        r"\b[A-Z]"    #! 9. Bevat een woord dat begint met een hoofdletter
    ]
    if 1:
        for s in slist:
            print( s, ' : ' , sep= ' ' , end= ' ' )
            for i in range( len( relist ) ):
                m = re.search( relist[i], s )
                if m:
                    print( i+1, end= ' ' )
            print()

    # Uitgesplitste presentatie
    if 0:
        print( ' ' * 23, 'andere presentatie\n' )
        for i in range(0, len( relist ) ):
            print( f'Reguliere expressie {i+1} = "{relist[i]}":' )
            for s in slist:
                if m := re.search( relist[i], s ):
                    gevonden = f'segment [{m.start(0)}:{m.end(0)}] = "{m.group(0)}"' \
                               f' voldoet aan re-{i+1} "{relist[i]}"'
                else:
                    gevonden = ""
                print( f''' {f'"{s}"' :19s}:''', gevonden )
            print()

# uitvoer:
# aaabbb: 1 3
# aaaaaa: 1 2 3 6
# abbaba: 3 8
# aaa: 1 2 3 4
# bEver ottEr: 7
# tango samba rumba: 8
#  hello world : 5 7
#  Hello World : 5 7 9
# bas :
#   :   1 2 3 5 6

#=============================================================#
# 25.3 Groeperen

if 0:
    markeer( '25.3' )

    # listing2507.py
    import re

    pDatum = re.compile( r"(\d{1,2})-(\d{1,2})-(\d{4})" )
    m = pDatum.search( "In antwoord op uw schrijven van 25-3-2015, \
heb ik besloten een huurmoordenaar op u af te sturen." )
    if m:
        print( "Datum: {}; dag: {}; maand: {}; jaar: {}".format(
                m.group(0), m.group(1), m.group(2), m.group(3) ) )

#-------------------------------------------------------------#
# 25.3.1 findall() en groepen

if 0:
    markeer( '25.3.1' )

    # listing2508.py  -- rapportage over gegevenstypen
    #                    toegevoegd aan code in boek
    import re

    pDatum = re.compile( r"(\d{1,2})-(\d{1,2})-(\d{4})" )
#    pDatum = re.compile( r"\d{1,2}-\d{1,2}-\d{4}" )
#    """
    datumlist = pDatum.findall( "In antwoord op uw schrijven van \
25-3-2015, heb ik op 27-3-2015 besloten u verder te negeren." )

    # for datum in datumlist:
    #     print( datum )

    print( pDatum   , "\n    type =", type( pDatum    ) )
    print( datumlist, "\n    type =", type( datumlist ) )
    for datum in datumlist:
        print( datum, "\n    type =", type( datum     ) )
    """
    for x in pDatum.finditer( "In antwoord op uw schrijven van \
    25-3-201599, heb ik op 27-3-2015 besloten u verder te negeren." ):
        print(x, x[0], x[1], x[2], x[3])
#    """

#-------------------------------------------------------------#
# 25.3.2 Benoemde groepen

if 0:
    markeer( '25.3.2' )

    # listing2509.py
    import re

    pDatum = re.compile(
        r"(?P<dag>\d{1,2})-(?P<maand>\d{1,2})-(?P<jaar>\d{4})" )
    m = pDatum.search( "In antwoord op uw schrijven van 25-3-2015, \
heb ik besloten een zingend telegram op u af te sturen." )
    if m:
        print( "dag is {}".format( m.group( 'dag' ) ) )
        print( "maand is {}".format( m.group( 'maand' ) ) )
        print( "jaar is {}".format( m.group( 'jaar' ) ) )

if 0:
    markeer( '25.3.2 -- kortere formulering' )

    # listing2509.py anders
    import re

    pDatum = re.compile(
        r"(?P<dag>\d{1,2})-(?P<maand>\d{1,2})-(?P<jaar>\d{4})" )
    m = pDatum.search( "In antwoord op uw schrijven van 25-3-2015, \
heb ik besloten een zingend telegram op u af te sturen." )
    if m:
        print( f"dag is {m['dag']}" )
        print( f"maand is {m['maand']}" )
        print( f"jaar is {m['jaar']}" )
#        print( f"jaar is {m[0]}" )
#        print( f"jaar is {m[1]}" )

# Dit is mogelijk gemaakt door de definitie van de
# instance methode Match.__getitem__ in de re module.
# In werkelijkheid steekt deze module veel complexer
# in elkaar, maar logisch gezien zou het grofweg
# neerkomen op:
#
#     def __getitem__(self, key):
#         return self.group(key)
#
# (zie ook hoofdstuk 21)

#-------------------------------------------------------------#
# 25.3.3 Refereren binnen een reguliere expressie

if 0:
    markeer( '25.3.3 A' )

    # listing2510.py
    import re

    m = re.search( r"(\S).*\1", "Monty Python's Flying Circus" )
    if m:
        print( "{} komt twee keer voor in de string".format(
            m.group(1) ) )
    else:
        print( "Geen match gevonden." )

if 0:
    markeer( '25.3.3 B' )

    # test ook andere strings
    import re

    for tekst in ( "Monty Python's Flying Circus",
                   'abcdefgd', 'aAbB cCdD eEfF' ):
        print( tekst )
        m = re.search( r"(\S).*\1", tekst )
        if m:
            print( "{} komt twee keer voor in de string\n".format(
                m.group(1) ) )
        else:
            print( "Geen match gevonden." )

#-------
# Opgave

if 0:
    markeer( '25.3.3 Opgave A' )

    import re

    m = re.search( r"(\S).*\1.*\1", "Monty Python's Flying Circus" )
    if m:
        print( "{} komt drie keer voor in de string".format(
            m.group(1) ) )
#            m ) )
    else:
        print( "Geen match gevonden." )

#-------
# Opgave

"""Kun je de reguliere expressie wijzigen
zodat het een patroon beschrijft waarbij
twee tekens twee keer voorkomen?"""

# Wat bedoelt Spronck hiermee?
#
#   1. Precies twee tekens precies twee keer binnen de hele string?
#   2. Precies twee tekens precies twee keer binnen een willekeurige substring?
#   3. Precies twee tekens minimaal twee keer binnen de hele string?
#   4. Precies twee tekens minimaal twee keer binnen een willekeurige substring?
#   5. Minimaal twee tekens precies twee keer binnen de hele string?
#   6. Minimaal twee tekens precies twee keer binnen een willekeurige substring?
#   7. Minimaal twee tekens minimaal twee keer?
#
# Voor welke interpretaties kun je een voorbeeld bedenken waar
# niet aan wordt voldaan, terwijl er wel aan ten minste één
# van de andere voorwaarden wordt voldaan?
# Voor interpretatie 7 is dat in elk geval niet mogelijk.

if 0:
    markeer( '25.3.3 Opgave B' )

    # lastige opgave, 2 delen
    import re

    rexs = (
    # r"(\S).*\1.*(\S).*\2",  # aaaa, aabb
    # r"(\S).*(\S).*\1.*\2",  # aaaa, abab
    # r"(\S).*(\S).*\2.*\1",  # aaaa, abba

    # 3 vorige gecombineerd met |
    # match aaaa, aabb, abab, abba
    # oplossing eerste deel
    r"((\S).*\2.*(\S).*\3)|((\S).*(\S).*\5.*\6)|((\S).*(\S).*\9.*\8)",

    # niet greedy, gebruik .*? --- niet in boek
    # resultaat van deel voor | is niet beste voor Monty tekst
    # r"((\S).*?\2.*?(\S).*?\3)|((\S).*?(\S).*?\5.*?\6)|((\S).*?(\S).*?\9.*?\8)",


    ## eigenaardige ?!?
    # r"((\S).*\2.*){2}",  # ??  wel aabb, niet aaaa waarom niet??
    # r"((\S).*\2.*)((\S).*\4.*)", # wel aaaa, werkt niet hetzelfde
    # r"(\S).*\1.*(\S).*\2.*",     # wel aaaa

    
    # gebruik (?!..) niet in boek
    # r"(\S).*(?!\1)(\S)",  # 2 verschillende

    # r"(\S).*(?!\1)(\S).*\1.*\2", # abab
    # r"(\S).*(?!\1)(\S).*\2.*\1", # abba
    # r"(\S).*\1.*(?!\1)(\S).*\2", # aabb

    # 3 vorige gecombineerd met |
    # match aabb, abab, abba, niet aaaa
    # r"((\S).*(?!\2)(\S).*\2.*\3)|"  # literals zonder komma
    # r"((\S).*(?!\5)(\S).*\6.*\5)|"  # vormen samen 1 string
    # r"((\S).*\8.*(?!\8)(\S).*\9)",

    # niet greedy, gebruik .*? niet in boek
    # oplossing tweede deel
#    r"((\S).*?(?!\2)(\S).*?\2.*?\3)|"  # literals zonder komma
#    r"((\S).*?(?!\5)(\S).*?\6.*?\5)|"  # vormen samen 1 string
#    r"((\S).*?\8.*?(?!\8)(\S).*?\9)",
    )
    teksts = (
         "Monty Python's Flying Circus",
         # 'aa', 'ab', ' a a ', ' a b ',
         'aaaa', 'aabb', 'abab', 'abba',
         ' a a a a ', ' a a b b ', ' a b a b ', ' a b b a ',
        )

    for rex in rexs:
        print( rex )
        for tekst in teksts:
            print( ' ', '"' + tekst + '"', end=' ' )
            m = re.search( rex, tekst )
            if m:
                print( "gevonden:", '"' + m.group(0) + '"',
                # ' '.join(f"[{i}]={m}" for i, m in enumerate(m.groups()) if m)
                       )
            else:
                print( "geen match" )
        # input( 'Enter voor volgende' )

#=============================================================#
# 25.4 Vervangen

if 0:
    markeer( '25.4' )

    import re

    # code in boek geeft warning in Python 3.12
    #   geen warning in oudere versies
    # SyntaxWarning: invalid escape sequence '\g'

    # originele code op bladzijde 309 in boek versie 1.1.1
    # deze regel heeft een fout, een bug
    # s = re.sub( r"([iy])z(eert)", "\g<1>s\g<2>", "Of je nu \
    
    # oplossing: gebruik dubbele backslash "\\g..."
    # s = re.sub( r"([iy])z(eert)", "\\g<1>s\\g<2>", "Of je nu \

    # of gebruik een raw string r"..."
    s = re.sub( r"([iy])z(eert)", r"\g<1>s\g<2>", "Of je nu \
categorizeert, rationalizeert, of analyzeert, je moet \
een s gebruiken!" )
    print( s )

#=============================================================#
# Wat je geleerd hebt

# - Wat reguliere expressies zijn
# - Welke meta-tekens in reguliere expressies gebruikt kunnen worden
# - Hoe reguliere expressies in Python gebruikt worden, via de re module
# - Compileren van reguliere expressies met re.compile()
# - Wat match objecten zijn
# - Zoeken naar patronen middels match(), search(), findall(), en finditer()
# - Vervangen van patronen met de sub() methode
# - Het gebruik van vierkante haken in reguliere expressies om verschillende mogelijk-
#   heden voor een teken te representeren
# - Gebruik van speciale tekens in reguliere expressies, waarvan veel beginnen met een
#   backslash
# - Herhalen van subpatronen in reguliere expressies via herhalingsoperatoren
# - Groeperen van subpatronen middels haakjes
# - Het gebruik van groepen om resultaten te ontrafelen
# - Refereren binnen een patroon
# - Mensen op zelfingenomen wijze het gebruik van reguliere expressies aanbevelen

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 25.1
# Opgave 25.2
# Opgave 25.3
# Opgave 25.4
# Opgave 25.5
# Opgave 25.6

###############################################################
# 26 Bestandsformaten

#=============================================================#
# 26.1 CSV

if 0:
    markeer( '26.1 A' )

    fp = open( "pc_inventory.csv" )
    print( fp.read ().strip () )
    fp.close ()

if 0:
    markeer( '26.1 B' )

    # niet in boek
    # genereer naw.csv bestand
    # voor code hieronder
    naw = '''\
Naam,Straat,Postcode,Plaats
Eva,Meidoornkade 24,3992 AE,Houten
Sam,Henri Dunantplein 4,3731 CL,De Bilt
Jan,Kenaupark 23,2011 MR,Haarlem
Lisa,Brennerbaan 150,3524 BN,Utrecht
'''
    with open('naw.csv', 'w') as fw:
        fw.write(naw)

if 0:
    markeer( '26.1 C' )

    # niet in boek
    # 01_print_csv.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - print CSV file
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-21  initial release

    import  os;     os.system('clear')

    def main():
        with open('naw.csv') as f:
            print(f.read().strip())

    main()

#-------------------------------------------------------------#
# 26.1.1 CSV reader()

if 0:
    markeer( '26.1.1 A' )

    # listing2601.py

    from csv import reader

    fp = open( "pc_inventory.csv", newline = '' )
    csvreader = reader ( fp )
    for regel in csvreader:
        print( regel )
    fp.close ()

# reader() argumenten
#  delimiter-<teken>
#  quotechar-<teken>

if 0:
    markeer( '26.1.1 B' )

    # niet in boek
    # 02_csv_reader.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using csv.reader
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-21  initial release

    import  os;     os.system('clear')
    import  csv

    def main():
        with open('naw.csv') as f:
            csv_reader = csv.reader(f)
            for regel_list in csv_reader:
                # print(regel_list)
                print(''.join(f'{x:22}' for x in regel_list))


    main()

#-------------------------------------------------------------#
# 26.1.2 CSV writer()

# ook delimiter en quotechar

# - csv.QUOTE_ALL
# - csv.QUOTE_MINIMAL
# - csv.QUOTE_NONNUMERIC
# - csv.QUOTE_NONE

if 0:
    markeer( '26.1.2 A' )

    # listing2602.py
    from csv import writer

    fp = open( "pc_writetest.csv", "w", newline = '' )
    csvwriter = writer( fp )
    csvwriter.writerow( ["FILM", "SCORE"] )
    csvwriter.writerow( ["Monty Python and the Holy Grail", 8] )
    csvwriter.writerow( ["Monty Python's Life of Brian", 8.5] )
    csvwriter.writerow( ["Monty Python's Meaning of Life", 7] )
    fp.close()

if 0:
    markeer( '26.1.2 B' )

    # niet in boek
    # 03_csv_writer.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using csv.writer
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-21  initial release

    import  os;     os.system('clear')
    import  csv

    data = [["FILM",                              "SCORE"],
            [" Monty Python and the Holy Grail ", 8],
            [" Monty Python ' s Life of Brian ",  8.5],
            [" Monty Python ' s Meaning of Life", 7]]


    def main(csv_data):
        with open('monty.csv', 'w') as f:
            csv.writer(f).writerows(csv_data)
        print('done')


    main(data)

#-------
# Opgave

if 0:
    markeer( '26.1.2 Opgave' )

    import csv
    
    with open( "pc_writetest.csv", newline = '' ) as fp:
        csvreader = csv.reader( fp )
        for regel in csvreader:
            # print( regel )
            print( *regel )

#=============================================================#
# 26.2 Pickling

if 0:
    markeer( '26.2 A' )

    # listing2603.py
    from pickle import dump

    kaaswinkel = [ ("Roquefort", 12, 15.23) ,
    ("White Stilton", 25, 19.02) , ("Cheddar", 5, 0.67) ]

    fp = open( "pc_cheese.pck", "wb" )
    dump( kaaswinkel , fp )
    fp.close()

    print( "uitvoer gereed" )

if 0:
    markeer( '26.2 B' )

    # listing2604.py
    from pickle import load

    fp = open( "pc_cheese.pck", "rb" )
    buffer = load( fp )
    fp.close()
    print( type( buffer ) )
    print( buffer )

if 0:
    markeer( '26.2 C' )

    # listing2605.py
    from pickle import dump, load

    class Punt:
        def __init__( self, x, y ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({} ,{})".format( self.x, self.y )

    p = Punt( 2, 5 )
    fp = open( "pc_point.pck", "wb" )
    dump( p, fp )
    fp.close()

    fp = open( "pc_point.pck", "rb" )
    q = load( fp )
    fp.close()

    print( type( q ) )
    print( q )

if 0:
    markeer( '26.2 D' )

    # niet in boek
    # 04_pickle_dump.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using pickle.dump
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-21  initial release

    import  os;     os.system('clear')
    import  pickle

    data = [["FILM",                              "SCORE"],
            ("Monty Python and the Holy Grail", 8),
            ("Monty Python ' s Life of Brian",  8.5),
            ("Monty Python ' s Meaning of Life", 7)]


    def main(data):
        with open('monty.pck', 'wb') as f:
            pickle.dump(data, f)
        print('done')


    main(data)

if 0:
    markeer( '26.2 E' )

    # niet in boek
    # 04_pickle_load.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using pickle.load
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-22  initial release

    import  os;     os.system('clear')
    import  pickle


    def loadData(fn):
        with open(fn, 'rb') as f:
            return(pickle.load(f))


    def showData(data):
        # print(data)
        for record in data:
                # print(record)
                print(f'{record[0]:40}  {record[1]}')


    def main():
        data = loadData('monty.pck')
        showData(data)


    main()

if 0:
    markeer( '26.2 F' )

    # niet in boek
    # 05_pickle_classes.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - pickle and objects
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-22  initial release

    import  os;     os.system('clear')
    import  pickle

    # a two dimensional point
    class Punt :

        def __init__ (self, x, y):
            self.x = x
            self.y = y


        # a tuple representation  of x and y
        def __repr__(self):
            return f'({self.x}, {self.y})'
        

    # save obj in fn using pickle format
    def pickleSave(fn, obj):
        with open(fn, 'wb') as f:
            pickle.dump(obj, f)
     

    # return obj decoded from pickle fn
    def pickleLoad(fn):
        with open(fn, 'rb') as f:
            return(pickle.load(f))


    def main():
        p1 = Punt(2, 5)
        pickleSave('f1.pck', p1)
        p2 = pickleLoad('f1.pck')
        print('p2:', type(p2), p2)


    main()

#=============================================================#
# 26.3 JavaScript Object Notation (JSON)

if 0:
    markeer( '26.3 A' )

    # listing2606.py
    from json import dump, load

    kaaswinkel = [ ("Roquefort", 12, 15.23),
    ("White Stilton", 25, 19.02) , ("Cheddar", 5, 0.67) ]

    fp = open( "pc_cheese.json", "w" )
    dump( kaaswinkel , fp )
    fp.close ()

    fp = open( "pc_cheese.json", "r" )
    buffer = load( fp )
    fp.close ()

    print( type( buffer ) )
    print( buffer )

if 0:
    markeer( '26.3 B' )

    # niet in boek
    # 06_json_dump.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using json.dump
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-21  initial release

    import  os;     os.system('clear')
    import  json

    data = [["FILM",                              "SCORE"],
            ("Monty Python and the Holy Grail", 8),
            ("Monty Python ' s Life of Brian",  8.5),
            ("Monty Python ' s Meaning of Life", 7)]


    def main(data):
        with open('monty.json', 'w') as f:
            json.dump(data, f)
        print('done')


    main(data)

if 0:
    markeer( '26.3 C' )

    # niet in boek
    # 07_json_dump2.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using json.dump formatted
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-21  initial release

    import  os;     os.system('clear')
    import  json

    data = {'date': '2024-02-22',
            'section':
                [["FILM",                              "SCORE"],
                ("Monty Python and the Holy Grail", 8),
                ("Monty Python ' s Life of Brian",  8.5),
                ("Monty Python ' s Meaning of Life", 7)]}


    def main(data):
        with open('monty2.json', 'w') as f:
            json.dump(data, f, sort_keys=True, indent=4)
        print('done')


    main(data)

if 0:
    markeer( '26.3 D' )

    # niet in boek
    # 08_json_load.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using json.load
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-22  initial release

    import  os;     os.system('clear')
    import  json


    def loadData(fn):
        with open(fn) as f:
            return(json.load(f))


    def showData(data):
        # print(data)
        for item in data:
            print(item)
        

    def main():
        data = loadData('monty.json')
        showData(data)


    main()

#=============================================================#
# 26.4 HTML en XML

if 0:
    markeer( '26.4 A' )

    # niet in boek
    # 09_html.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten - using json.load
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-22  initial release

    import  os;             os.system('clear')
    import  urllib.request  as      urlr
    from    bs4             import  BeautifulSoup

    url = 'https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/eurofxref-graph-usd.en.html'


    def showTags(soup):
        for child in soup.descendants:
            if child.name:
                print(child.name)


    def main():
        with urlr.urlopen(url) as response:
            body = response.read().decode()

        with open('test.html', 'w') as f:
            f.write(body)                           # chartData.push({ date: new Date(2024,1,21), rate: 1.0809 });

        with open("test.html") as f:
            soup = BeautifulSoup(f, 'html.parser')

        # print(soup)
        # showTags(soup)
        # print(soup.title)

        script_tags = soup.find_all('script')
        print(type(script_tags))

        print(script_tags[3].text)
        print('done')


    main()

if 0:
    markeer( '26.4 B' )

    # niet in boek
    # 10_html_xch.py

    # Pieter Spronck - Hoofdstuk 26 - Bestandsformaten html
    #    Get historical us dollar - euro exchange rate 
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-02-22  initial release

    import  os;             os.system('clear')
    from    urllib.request  import urlopen

    url = 'https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/eurofxref-graph-usd.en.html'


    def nextWord(target, source):
        words = source.split()
        for idx, word in enumerate(words):
            if word == target:
                return words[idx+1]
            

    def getHtml(url):
        with urlopen(url) as response:
            return  response.read().decode()


    def main():
        print('Historical (last year) search of exchange rate euro to US-dollar')
        date = input('Which date, format = 2024,1,21 (months 0..11)? ...')
        if not date: 
            date = '2024,1,21'
        html = getHtml(url).split()
        for idx, ln in enumerate(html):
            if ln.find(date) >= 0: break
        # print(ln)
        print(f'exchange rate euro to US dollar: {html[idx+2]}')


    main()

#=============================================================#
# Wat je geleerd hebt

# - Lezen en schrijven van CSV bestanden met de csv module
# - Pickling met de pickle module
# - Lezen en schrijven van JSON bestanden met de json module
# - De beschikbaarheid van hulpmiddelen voor web schrapen

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 26.1
# Opgave 26.2


###############################################################
# 27 Diverse Nuttige Modules

#=============================================================#
# 27.1 datetime

if 0:
    markeer( '27.1 A' )

    from datetime import datetime
    print( datetime.now() )

if 0:
    markeer( '27.1 B' )

    # listing2701.py
    from datetime import datetime, timedelta

    ditjaar = datetime.now().year
    xmasditjaar = datetime ( ditjaar, 12, 25, 23, 59, 59 )
    dezedag = datetime.now()
    dagen = xmasditjaar - dezedag

    if dagen.days < 0:
        print( "Kerst komt volgend jaar weer." )
    elif dagen.days == 0:
        print( "Het is Kerst!" )
    else:
        print( "Slechts", dagen.days, "dagen tot Kerst!" )

# niet in boek

# code met datum en tijd
#   zonder datetime module
#   wordt snel lastig vanwege complicaties,
#   bijzonderheden en uitzonderingen

# schrikkeljaar, schrikkeldag, schrikkelmaand
#   leap year, leap day, leap month

# zomertijd  daylight saving time, DST

# tijdzone  time zone, UTC, GMT

# locale in strftime en strptime
#   formattering en namen afhankelijk van taal of cultuur

# schrikkelseconde  leap second
# niet in datetime module
#   deels wel in time module

# andere kalender zoals Juliaanse
# niet in datetime module
#   gaat uit van Gregoriaanse kalender
#     ook voor ver verleden en verre toekomst

if 0:
    markeer( '27.1 C' )

    # niet in boek

    # class date(year, month, day)

    import datetime

    if 0:
        dat = datetime.date( 2024, 3, 14 )
        # dat = datetime.date.today() # geen now() voor date
        # dat = datetime.date.now() # bestaat niet, AttributeError
        
        print( 'datum', dat)
        print( 'jaar maand dag', dat.year, dat.month, dat.day )

        # date, time en datetime zijn immutable
        if 0:
            dat.year = 2025 # AttributeError, not writable

        # dag van de week
        if 0:
            # maandag 0, zondag 6
            print( 'weekday', dat.weekday() )
            # maandag 1, zondag 7
            print( 'isoweekday', dat.isoweekday() )

        # iso kalender jaar, week nummer, dag van de week
        #   week 1 is 1e week met donderdag
        #   week 1 tot en met 52 sommige jaren tot en met 53
        if 0:
            yr, wk, wkd = dat.isocalendar()
            print( 'isocalendar', yr, wk, wkd )

    # 1 <= maand <= 12

    # 1 <= dag <= 28..31
    # afhankelijk van jaar en maand
    if 0:
        dat2 = datetime.date( 2024, 2, 29 )
        # dat2 = datetime.date( 2023, 2, 29 ) # ValueError
        print( 'datum2', dat2)

    # grenzen aan jaar
    if 0:
        print( 'datum min', datetime.date.min )
        print( 'datum max', datetime.date.max )

    # ordinal
    if 0:
        ord1 = datetime.date.today().toordinal()
        print( ord1, datetime.date.fromordinal(ord1) )
        ord2 = datetime.date( 2024, 1, 1).toordinal()
        print( ord2, datetime.date.fromordinal(ord2) )

        # datord1 = datetime.date.fromordinal( 0 ) # ValueError
        datord1 = datetime.date.fromordinal( 1 )
        datord2 = datetime.date.fromordinal( 2 )
        datord3 = datetime.date.fromordinal( 2024 )
        print( 'datum ord1', datord1 )
        print( 'datum ord2', datord2 )
        print( 'datum ord3', datord3 )

    # vanaf timestamp
    if 0:
        datstamp1 = datetime.date.fromtimestamp( 0 )
        datstamp2 = datetime.date.fromtimestamp( 24 * 60 * 60 )
        print( 'datum stamp1', datstamp1 )
        print( 'datum stamp2', datstamp2 )

    # iso formaat
    # https://en.wikipedia.org/wiki/ISO_8601
    if 0:
        datiso1 = datetime.date.fromisoformat( '2024-03-14' )
        # datiso1 = datetime.date.fromisoformat( '' ) # ValueError
        datiso2 = datetime.date.fromisoformat( '20240314' )
        datiso3 = datetime.date.fromisoformat( '2024-W11-4' )
        # datiso3 = datetime.date.fromisoformat( '2024123' ) # ValueError
        print( 'datum iso1', datiso1, datiso1.isoformat() )
        print( 'datum iso2', datiso2, datiso2.isoformat() )
        print( 'datum iso3', datiso3, datiso3.isoformat() )

if 0:
    markeer( '27.1 D' )

    # niet in boek

    # class time(hour=0, minute=0, second=0, microsecond=0,
    #            tzinfo=None, *, fold=0)

    import datetime

    if 1:
        # tim = datetime.time.now() # AttributeError, wel in datetime
        tim = datetime.time()
        # tim = datetime.time( 14 )
        # tim = datetime.time( 25 ) # ValueError
        # tim = datetime.time( 14, 20 )
        # tim = datetime.time( 14, 20, 60 ) # ValueError
        # tim = datetime.time( 21, 20, 19, 18 )
        # fold is 0 of 1
        # alleen invloed bij overgang zomer naar wintertijd
        #   dan 2x dezelfde tijd
        # tim = datetime.time( 21, 20, 19, 18, fold=1 )

        print( 'tijd', tim )
        print( 'uur minuut seconde microseconde',
               tim.hour, tim.minute, tim.second, tim.microsecond )

    # iso formaat
    # https://en.wikipedia.org/wiki/ISO_8601
    if 0:
        timiso1 = datetime.time.fromisoformat( '01:02:03' )
        # timiso1 = datetime.time.fromisoformat( '' ) # ValueError
        timiso2 = datetime.time.fromisoformat( '11:12:13:123456' )
        timiso3 = datetime.time.fromisoformat( 'T043210' )
        print( 'tijd iso1', timiso1, timiso1.isoformat() )
        print( 'tijd iso2', timiso2, timiso2.isoformat() )
        print( 'tijd iso3', timiso3, timiso3.isoformat() )

if 0:
    markeer( '27.1 E' )

    # niet in boek

    # class datetime(year, month, day,
    #                hour=0, minute=0, second=0,
    #                microsecond=0, tzinfo=None, *, fold=0)

    #   1 <= month <= 12, 1 <= day <= 28..31

    #   datetime is combinatie van date en time
    #     praktischer dan date of time afzonderlijk
    #       geen overflow problemen
    #         bij vorige of volgende dag

    import datetime

    dag = datetime.datetime.now()
    # dag = datetime.datetime( 2024, 3, 14 )
    # dag = datetime.datetime( 2024, 3, 14, 13, 22, 5 )
    # dag = datetime.datetime( 2024, 3, 14, 13, 22, 5, 654321 )
    print( 'dag', dag )
    print( 'y', dag.year, 'mo', dag.month, 'd', dag.day,
           'h', dag.hour, 'mi', dag.minute, 's', dag.second,
           'us', dag.microsecond )

    if 0:
        # class timedelta(days=0, seconds=0, microseconds=0,
        #                 milliseconds=0, minutes=0, hours=0,
        #                 weeks=0)
        #   verschil 2 time, date of datetime instances
        #   bewaart days, seconds, microseconds
        #     genormalizeerd zodat alleen days negatief kan zijn

        dag2 = datetime.datetime( 2024, 2, 29 )
        # dag2 = datetime.datetime( 2023, 2, 29 ) # ValueError
        print( 'dag2', dag2 )
        duur = dag - dag2
        print( 'duur', duur )

        duur2 = datetime.timedelta( days=3, hours=1 ) 
        duur2 = datetime.timedelta( days=2, hours=25 )
        print( 'duur2', duur2 )
        dag3 = dag2 + duur2
        print( 'dag3', dag3 )

    if 0:
        print( 'weekday', dag.weekday() )
        print( 'isoweekday', dag.isoweekday() )
        isocal = dag.isocalendar()
        print( 'isocalendar year', isocal.year,
               'week', isocal.week,
               'weekday', isocal.weekday)
        print( dag.tzinfo )

if 0:
    markeer( '27.1 F' )

    # niet in boek

    # date, time, datetime hebben
    #   methode strftime en
    #     __format__ voor gebruik in f-strings
    #   converteer object instance
    #     naar geformatteerde string

    import datetime
    import locale

    # zie ook de lijst met format codes
    # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
    formats = ( '%a %A',       # weekday abbreviated and full name
                '%b %B',       # month abbreviated and full name
                '%c',          # date and time
                '%x',          # date
                '%X',          # time
              )

    # format afhankelijk van huidige locale
    locs = ( None, '', 'en_GB', 'nl_NL', 'en_US', 'de_DE' )

    dats = ( datetime.datetime.now(),
             # datetime.datetime( 2024, 3, 14 ),
             # datetime.datetime( 2024, 3, 14, 13, 22, 5 ),
           )
                               
    curloc = locale.getlocale()
    if 0:
        print( 'locale begin', curloc )

    for format in formats:
        print( format )
        for loc in locs:
            print( '  ', loc if loc != '' else "''" )
            for dat in dats:
                if loc != None:
                    locale.setlocale( locale.LC_ALL, loc )
                print( ' ' * 4, dat.strftime( format ) )
                print( ' ' * 4, f'{dat:{format}}' )

    locale.setlocale( locale.LC_ALL, curloc )
    if 0:
        curloc = locale.getlocale()
        print( 'locale end', curloc )

if 0:
    markeer( '27.1 G' )

    # niet in boek

    # datetime.strptime class methode
    #   converteer string waarde volgens
    #     met bepaald formattering
    #     naar datetime object

    import datetime

    dattim = datetime.datetime.strptime(
        '14/03/24 23:59:59.999999',
        '%d/%m/%y %H:%M:%S.%f')
    print( dattim )

    dattim = datetime.datetime.strptime(
        '31/12/2023 0:0:0.0',
        # '31/12/2023 0:0:0.1',
        '%d/%m/%Y %H:%M:%S.%f')
    print( dattim )

if 0:
    markeer( '27.1 H' )

    # niet in boek

    #   bij time en datetime
    #   als tzinfo in gebruik dan aware
    #     gebruikt offset ten opzichte van UTC
    #   anders naive
    #     negeert tijdzone timezone

    # class tzinfo
    #   abstract base class
    #   gebruikt door time en datetime

    # class timezone(offset, name=None)
    #   subclass van tzinfo
    #   offset is timedelta
    #     ten opzichte van UTC
    
    import datetime

    if 0:
        dag = datetime.datetime.now()
        # dag = datetime.datetime( 2024, 4, 14 )
        # dag = datetime.datetime( 2024, 3, 14, 13, 22, 5 )
        print( 'dag zonder timezone', dag )
        print( '  utcoffset', dag.utcoffset() )
        print( '  dst', dag.dst() )

        tzdag = dag.astimezone()
        print( 'dag met timezone', tzdag )
        print( '  utcoffset', tzdag.utcoffset() )
        print( '  dst', tzdag.dst() )

        # is timezone met vaste offset
        #   ten opzichte van UTC
        tzinf = tzdag.tzinfo
        print( 'timezone', tzinf, type(tzinf) )

    if 0:
        hrs = 1
        # hrs = 0
        # hrs = -4
        delta = datetime.timedelta( hours=hrs )
        tza = datetime.timezone( delta )
        print( 'tz', tza )

    # in zoneinfo module
    # class zoneinfo.ZoneInfo(key)
    #   subclass van tzinfo
    #   key is IANA timezone
    #     zoals 'Europe/Amsterdam'

    if 0:
        # https://pypi.org/project/tzdata/
        # voor handmatige installatie
        #   download tzdata-2024.1-py2.py3-none-any.whl
        #     dit is een zip bestand
        #   maak een tzdata subdirectory
        #     in de directory waarin ..Extra.py staat
        #   zet daarin een kopie van de tzdata directory
        #     uit de .whl, met bijvoorbeeld bestand
        #     tzdata/zoneinfo/Europe/Amsterdam
        try:
            import tzdata

            print( tzdata, type( tzdata ) )
        except ImportError:
            print( 'Module tzdata not found' )

    import zoneinfo

    if 1:
        # gebruikt tzdata zelfs zonder import
        # zoneinfo module probeert import tzdata te doen
        #   geen foutmelding als dit niet lukt
        # te zien door install tzdata als in lokale dir
        #   alleen rename lokale tzdata dir heeft effect

        if 0:
            print( zoneinfo.available_timezones() )

        if 0:
            for tzi in zoneinfo.available_timezones():
                if 'Amsterdam' in tzi:
                # if 'Europe' in tzi:
                    print( tzi )

    if 1:
        zi = zoneinfo.ZoneInfo( 'Europe/Amsterdam' )
        # zi = None

        # zo 29 okt 2023 van zomertijd naar standaard wintertijd
        #   om 3:00 uur zomertijd klok terug naar 2:00 uur wintertijd
        #     het is 2 keer 2:30 uur
        # zomer = datetime.datetime( 2023, 10, 28, 2, 30, tzinfo=zi )
        zomer = datetime.datetime( 2023, 10, 29, 2, 30, tzinfo=zi )
        # winter = datetime.datetime( 2023, 10, 30, 2, 30, tzinfo=zi )
        winter = datetime.datetime( 2023, 10, 29, 2, 30, fold=1, tzinfo=zi )

        print( 'zomer', zomer  )
        print( ' utcoffset', zomer.utcoffset() ) # , type(zomer.utcoffset() ) )
        print( ' dst', zomer.dst() ) # , type( zomer.dst() ) )
        print( 'winter', winter )
        print( ' utcoffset', winter.utcoffset() )
        print( ' dst', winter.dst() )

        # geen extra uur met timedelta
        # timedelta gebruikt muurtijd wall time
        # zie ook
        # https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
        delta = winter - zomer
        print( 'timedelta', delta ) # , type(delta) )

        # na omzetten naar UTC heeft timedelta wel extra uur
        winterUtc = winter - winter.utcoffset()
        zomerUtc = zomer - zomer.utcoffset()
        delta2 = winterUtc - zomerUtc
        print( 'timedelta2', delta2, type(delta2) )

#=============================================================#
# 27.2 collections

if 0:
    markeer( '27.2 A' )

    # listing2702.py
    from collections import Counter

    data = [ "appel", "banaan", "appel", "banaan", "appel", "kers" ]
    c = Counter( data )
    print( c )
    print( c.most_common( 1 ) )

    data2 = [ "mango", "kers", "kers", "kers", "kers" ]
    c.update( data2 )
    print( c )
    print( c.most_common() )

if 0:
    markeer( '27.2 B' )

    from collections import deque

    dq = deque( [ 1, 2, 3 ] )
    dq.appendleft( 4 )
    dq.extendleft( [ 5, 6 ] )
    print( dq )

    # niet in boek
    # deque
    #   sneller dan list
    #     bij operaties aan begin
    #     deque O(1)
    #     list O(n), schuift items op
    #   is thread safe

if 0:
    markeer( '27.2 C' )

    # niet in boek
    # namedtuple( typename, field_names,
    #             rename=False, defaults=None, module=None)

    # maakt een nieuwe subclass van tuple met naam typename
    # velden te benaderen via naam en index
    # handig als dezelfde tuple vaak wordt gebruikt

    import collections
    Point = collections.namedtuple( 'Point', ( 'x', 'y' ) )
    p1 = Point( 0, 0 )
    p2 = Point( x = 1, y = 1 )
    print( p1 )
    print( p1.x, p1.y )
    print( p1[0], p1[1] )

    # p1.x = 10 # AttributeError, tuple is immutable
    print( 'type', type(p1) )
    print( 'is tuple', isinstance( p1, tuple ) )

    print( p2 )
    print( p2.x, p2.y )
    print( p2[0], p2[1] )

    print( p1 > p2 )
    for i in p1:
        print( i )
    for z in zip( p1, p2 ):
        print( z )

if 0:
    markeer( '27.2 D' )

    # niet in boek
    # defaultdict( default_factory=None, ...)
    #   subclass van dict
    
    # soms handiger dan setdefault methode van dict
    # uit voorbeeld documentatie
    # https://docs.python.org/3/library/collections.html#defaultdict-examples

    import collections
    
    s = [ ( 'a', 1 ), ( 'b', 2 ),
          ( 'a', 3 ), ( 'b', 4 ), ( 'c', 5 ) ]

    dd = collections.defaultdict(list)
    for k, v in s:
        dd[k].append( v )
    print( sorted( dd.items() ) )

    dt = { }
    for k, v in s:
        dt.setdefault( k, [] ).append( v )
    print( sorted( dt.items() ) )

if 0:
    markeer( '27.2 E' )

    # niet in boek
    # bag of multiset in andere programmeertalen
    # lijkt ook op Counter
    # uit voorbeeld documentatie
    # https://docs.python.org/3/library/collections.html#defaultdict-examples

    import collections
    
    txt = 'anagram'

    dd = collections.defaultdict(int)
    for ch in txt:
        dd[ch] += 1
    print( sorted( dd.items() ) )

    cntr = collections.Counter(txt)
    print( sorted( cntr.items() ) )

if 0:
    markeer( '27.2 F' )

    # niet in boek
    # ChainMap( *maps )
    #   combineert meerdere mappings zoals dicts
    #   zonder nieuwe te maken
    #   bewaart mappings in een list
    # sneller bij alleen zoeken

    # uit voorbeeld documentatie
    # https://docs.python.org/3/library/collections.html#chainmap-examples-and-recipes

    import builtins
    import collections
    
    # print( pylookup )
    a = 10
    b = 20
    pylookup = collections.ChainMap(locals(), globals(),
                                    vars(builtins))
    print( 'buiten test', pylookup['a'], pylookup['b'] )

    def testlookup():
        a = 11
        pylookup = collections.ChainMap(locals(), globals(),
                                        vars(builtins))
        print( 'in test', pylookup['a'], pylookup['b'] )

    testlookup()

# niet in boek
# OrderedDict
#   niet echt nodig
#   ingebouwde dict onthoudt volgorde
#     vanaf versie 3.7

# niet in boek
# UserDict
# UserList
# UserString
#   wrappers niet echt nodig
#   kan net zo goed rechtstreeks
#     inherit van dict, list of str gebruiken
    
#=============================================================#
# 27.3 urllib

if 0:
    markeer( '27.3 A' )

    # listing2703.py

    from urllib.request import urlopen
    from urllib.error import HTTPError, URLError
    from sys import exit

    try:
        u = urlopen( "http://www.python.org" )
    except HTTPError as e:
        print( "HTTP Error", e )
        # sys.exit()  # fout in boek, NameError: name 'sys' is not defined
        exit()
    except URLError as e:
        print( "URL error", e )
        # sys.exit()  # fout in boek, NameError: name 'sys' is not defined
        exit()

    for i in range( 5 ):
        tekst = u.readline()
        print( tekst )

    u.close()

if 0:
    markeer( '27.3 B' )

    # niet in boek

    # code hierboven geeft soms onleesbare regels
    # komt door gzip encoded resultaat
    # te zien met info() methode
    #   daarin soms Content-Encoding: gzip
    #     gezien bij http://www.python.org
    # zie ook
    # https://stackoverflow.com/questions/40201104/decoding-urllib-request-response

    # op macOS geeft urlopen bij een https link
    #   soms een foutmelding: URL error
    #   certificate verify failed: unable to get local issuer certificate
    # oplossing: ga naar Macintosh HD > Programma's >
    #  Python 3.11 map (of de versie die wordt gebruikt) >
    #  dubbelklik op het document "Install Certificates.command"
    #  dit installeert de juiste "Root Certificates for the SSL module"
    # zie ook
    # https://stackoverflow.com/questions/50236117/scraping-ssl-certificate-verify-failed-error-for-http-en-wikipedia-org
    # https://stackoverflow.com/questions/42098126/mac-osx-python-ssl-sslerror-ssl-certificate-verify-failed-certificate-verify

    from urllib.request import urlopen
    from urllib.error import HTTPError, URLError
    from sys import exit

    try:
        u = urlopen( "http://www.python.org" )
        # u = urlopen( "https://www.python.org" )
        # u = urlopen( "https://programmeren.hcc.nl" )
    except HTTPError as e:
        print( "HTTP Error", e )
        sys.exit()
    except URLError as e:
        print( "URL error", e )
        sys.exit()

    print( u.info() )

    enc = u.headers.get( 'Content-Encoding' )
    data = u.read()
    u.close()

    print( 'content encoding', enc )
    if 1:
        if enc == 'gzip':
            print( 'gzip decompress' )
            import gzip
            data = gzip.decompress( data )
    print()

    lines = data.split( b'\n' )
    for line, i in zip(lines, range( 5 )):
        print( line )

    # urllib in standaard lib
    #   is ietwat ingewikkeld in gebruik
    #   met name als iets geavanceerder nodig is
    #   zoals hierboven soms gzip decompress nodig
    #     de requests module doet zit vanzelf
    
    # kan ook betere, alternatieve modules gebruiken
    # niet in standaard lib
    #   urllib3
    #     https://urllib3.readthedocs.io
    #   requests
    #     is gebaseerd op urllib3
    #     https://requests.readthedocs.io

#=============================================================#
# 27.4 glob

if 0:
    markeer( '27.4 A' )

    from glob import glob

    glist = glob( "*.pdf" )
    for name in glist:
        print( name )

#-------
# Opgave

if 0:
    markeer( '27.4 Opgave' )

    from glob import glob

    glist = glob( "*.py" )
    for name in glist:
        print( name )

if 0:
    markeer( '27.4 B' )

    # niet in boek
    
    import glob

    iglb = glob.iglob( '*.txt', recursive=True ) # werkt niet recursief
    # iglb = glob.iglob( '.*', recursive=True ) # werkt niet recursief
    # iglb = glob.iglob( '**.txt', recursive=True ) # werkt niet recursief
    # iglb = glob.iglob( '**/*.txt', recursive=True ) # werkt wel recursief
    # iglb = glob.iglob( '**/*.txt', recursive=True,
    #                    root_dir = '..' ) # root / duurt te lang
    for glb in iglb:
        print( glb )

    ## todo: hidden uitproberen
    #   .* niet op Windows
    # in documentatie:
    #   "If include_hidden is true, “**” pattern will match hidden directories."
    # https://docs.python.org/3/library/glob.html#glob.glob
    # in Python discussions:
    #   "(new) glob documentation could be improved"
    # https://discuss.python.org/t/new-glob-documentation-could-be-improved/19054

#=============================================================#
# 27.5 statistics

if 0:
    markeer( '27.5' )

    # listing2704.py
    from statistics import mean, median, mode, stdev, variance, \
        StatisticsError

    data = [ 4, 5, 1, 1, 2, 2, 2, 3, 3, 3 ]
    # data = [ ]
    # data = [ 0 ]
    # data = [ 0, -10 ]
    # data = [ 4, 4, 5, 5, 1, 1, 3, 3, 2, 2 ]

    print( "gemiddelde:", mean( data ) )
    print( "mediaan:", median( data ) )

    try:
        print( "modus:", mode( data ) )
    except StatisticsError as e:
        print( e )
    print( "st.dev.: {:.3f}".format( stdev( data ) ) )
    print( "variantie: {:.3f}".format( variance ( data ) ) )

    # niet in boek
    import statistics
    print( "multimodus:", statistics.multimode( data ) )

    # mode is in versie 3.8 gewijzigd
    #   geeft geen StatisticsError meer
    #     als geen unieke mode
    #     maar returnt eerste
    # in versie 3.8 multimode toegevoegd
    #    returnt lijst

#=============================================================#
# Wat je geleerd hebt

# - datetime module
# - collections module
# - urllib module
# - glob module
# - statistics module

#=============================================================#
# voor antwoorden zie Appendix F en X
# Opgave 27.1
# Opgave 27.2


###############################################################
# Aanvulling 0 Hedy

# voor echte beginners
# die nog niet eerder hebben geprogrammeerd

# boek de Programmeursleerling soms nog te moeilijk
# Hedy is eenvoudiger

# https://hedy.org

# ontwikkeld door Felienne Hermans
# professor Vrije Universiteit Amsterdam
# docent Lyceum Kralingen
# https://github.com/Felienne
# schrijver boek The Programmer's Brain
# https://www.felienne.com/book
# video Hedy Introduction Talk
# https://www.youtube.com/watch?v=EdqT313rM40&t=2s

# leren programmeren zonder eerdere ervaring

# bedoeld voor docenten en kinderen vanaf 10 jaar
#   ook bruikbaar voor volwassenen

# alleen webbrowser en internetverbinding nodig

# meertalig, in het Nederlands, Engels en meer

# je leert concepten geleidelijk, gradueel
#   18 levels, voor elk een mogelijkheid erbij

# eindigt als subset van standaard Python
#   dan wel overschakelen naar Engels nodig

# gratis en Open Source https://github.com/hedyorg/hedy

# ook offline te gebruiken

# online uitproberen
# https://hedy.org/hedy

# documentatie
# https://hedy.org/for-teachers/manual

# wiki
# https://github.com/hedyorg/hedy/wiki

# offline Hedy
# https://github.com/hedyorg/hedy/wiki/Offline-Hedy


# 18 Levels

# commando's
#  en       nl       opmerkingen
#                    Engelse commando's werken ook
#                      als taal Nederlands is ingesteld

# Level 1 Print en vraag
#  print    print    druk tekst af
#  ask      vraag    invoer van gebruiker
#  echo     echo     afdrukken tekst met invoer, alleen level 1
#  play     speel    noot C1, D1, E1, F1, G1, A2, B2, ..., B10, C10
#  forward  vooruit  schildpad afstand
#  turn     draai    schildpad links of rechts
#  color    kleur    geen uitleg
#  #        #        commentaar, nog geen uitleg in level 1

# Level 2 Variabelen
#  is       is       waarde opslaan in variabele
#  print    print    tekst en ook inhoud variabelen afdrukken
#  ask      vraag    invoer naar variabele, is er voor
#  sleep    slaap    wachten
#  turn     draai    schildpad graden
#  color    kleur    schildpad lijn

# Level 3 Lijstjes
#  is           is              item1, item2, item3, .. lijst in variabele
#  at random    op willekeurig  random item uit lijst
#  add to       voeg toe aan    aan lijst
#  remove from  verwijder uit   uit lijst

# Level 4 Aanhalingstekens
#  print    print    aanhalingstekens 'tekst' nodig
#  ask      vraag    aanhalingstekens nodig (hoge komma's, apostrofs, quotes)
#                    kan naast 'tekst' ook "tekst" gebruiken, geen uitleg
#  is       is       geen aanhalingstekens nodig
#  clear    wis      wis alle tekst

# Level 5 Als en anders
#  if       als        met of zonder anders
#  else     anders     als nodig ervoor
#  in       in         element in lijst
#  pressed  ingedrukt  wacht op toets

# Level 6 Berekeningen
#                    gehele getallen, geen kommagetallen
#  +        +        optellen
#  -        -        aftrekken
#  *        *        vermenigvuldigen
#  /        /        delen, niet door 0
#  =        =        is kan ook nog
#  play     speel    noot ook 1 tot en met 70

# Level 7 Herhaal
#  repeat   herhaal  met keer
#  times    keer     herhaal nodig ervoor

# Level 8 Inspringen
#  repeat   herhaal  regel(s) erna inspringen nodig
#  if       als      regel(s) erna inspringen nodig
#  else     anders   regel(s) erna inspringen nodig

# Level 9 Nestelen
#  repeat   herhaal  extra inspringen binnen andere herhaal als anders
#  if       als      extra inspringen binnen andere herhaal als anders
#  else     anders   extra inspringen binnen andere herhaal als anders

# Level 10 Voor lijstje
#  for in        voor in          voor item in lijst
#                                 regel(s) erna inspringen nodig

# Level 11 Voor bereik
#  for range to  voor bereik tot  voor getal in bereik begin tot eind
#                                 regel(s) erna inspringen nodig

# Level 12 Kommagetallen
#                      ook kommagetallen met punt
#  +        +          optellen, ook voor tekst samenvoegen
#                      aanhalingstekens 'tekst' altijd nodig
#  define   definieer  maak functie, regel(s) erna inspringen nodig
#  call     roep       gebruik functie

# Level 13 En en of
#  and      en         waar alleen als links waar en rechts waar
#  or       of         waar als links of rechts of allebei waar
#  with     met        argument bij definieer en roep

# Level 14 Kleiner en groter
#  <        <          kleiner dan, niet voor tekst
#  >        >          groter dan, niet voor tekst
#  <=       <=         kleiner dan of gelijk aan, niet voor tekst
#  >=       >=         groter dan of gelijk aan, niet voor tekst
#  ==       ==         gelijk aan
#  !=       !=         ongelijk aan
#  return   return     antwoord terug in functie met definieer

# Level 15 Zolang
#  while    zolang     regel(s) erna inspringen nodig

# Level 16 Vierkante haakjes
#  [ ]      [ ]            lijst
#  [random] [willekeurig]  random item uit lijst
#  False    False          niet waar
#  True     True           waar

# Level 17 Alsanders en dubbele punt
#  elif     alsanders      combinatie van anders als, erna inspringen nodig
#  :        :              bij commando's waarna inspringen nodig

# Level 18 Ronde haakjes
# print(,)  print(,)       haakjes nodig bij print en bereik
# range(,)  bereik(,)      in plaats van bereik tot
# input()   invoer()       in plaats van vraag
# def(,)    def(,)         in plaats van definieer met
#                          geen roep meer nodig, wel functienaam()


# Engelse code in Hedy level 18 werkt grotendeels zoals Python code
# enkele verschillen:
# 1) print: in Hedy geen spaties ertussen, in Python met spaties
#      print('Hello', 'World')
#    Hedy:
#      HelloWorld
#    Python:
#      Hello World
# 2) lijst: andere index eerste item
#      lst = [ 'a', 'b' ]
#      print(lst[1])
#    Hedy:
#      a
#    Python:
#      b
# 3) range: in Hedy met laatste erbij, in Python zonder laatste
#  for i in range(1, 3):
#     print(i)
# Hedy:
#  1
#  2
#  3
# Python:
#  1
#  2


# Muziek melodie tekst en noten

# If You're Happy and You Know It
# https://en.wikipedia.org/wiki/If_You%27re_Happy_and_You_Know_It

# Ik zag twee beren
# https://nl.wikipedia.org/wiki/Ik_zag_twee_beren

# Kortjakje
# https://nl.wikipedia.org/wiki/Kortjakje
# https://jostiband.nl/wp-content/uploads/2021/05/000-Oefenstuk-D-1e-stem-Kortjakje.pdf

# My Bonnie Lies over the Ocean
# https://en.wikipedia.org/wiki/My_Bonnie_Lies_over_the_Ocean
# https://www.free-notes.net/cgi-bin/noten_Song.pl?song=My+Bonnie+Lies+Over+The+Ocean

# Old MacDonald Had a Farm
# https://en.wikipedia.org/wiki/Old_MacDonald_Had_a_Farm
# https://www.bethsnotesplus.com/2013/05/old-mcdonald.html
# https://noobnotes.net/old-macdonald-had-a-farm-traditional/

# Slaap, kindje, slaap
# https://nl.wikipedia.org/wiki/Slaap,_kindje,_slaap

# Wat zullen we doen met de dronken zeeman
# What shall we do with the drunken sailor
# https://nl.wikipedia.org/wiki/What_Shall_We_Do_With_The_Drunken_Sailor
# https://en.wikipedia.org/wiki/Drunken_Sailor

# De Wielen van de bus
# https://genius.com/Kinderliedjes-de-wielen-van-de-bus-lyrics
# https://www.kinderliedjes.info/de-wielen-van-de-bus/

# Yankee Doodle
# https://en.wikipedia.org/wiki/Yankee_Doodle
# https://www.free-notes.net/cgi-bin/noten_Song.pl?song=Yankee+Doodle

# Spelletjes

# Blackjack
# https://nl.wikipedia.org/wiki/Blackjack

# Galgje
# https://nl.wikipedia.org/wiki/Galgje


# wijzigen voorgrondkleur van keywords
# zoals 'print' en 'vraag'
# default is paars met zwarte achtergrondkleur
# slecht leesbaar, weinig contrast

# installeer extensie voor wijzigen CSS
# via menu naar chrome webstore
# of naar Add-ons en thema's
# installeer bijvoorbeeld Stylus
# Stylus instellingen
#   vastzetten op werkbalk
# naar https://hedy.org/hedy
# op werkbalk Stylus kiezen
# nieuwe stijl
# voor style met kleur geel
# voeg volgende regels toe zonder #

# .ͼq {color: #ffff00;}
# code {color: #ffff00;}
# .command-highlighted {color: #ffff00;}

# voor # commentaar
# .ͼx {color: #c5c1ce;}

# zorg dat instelling "URL's beginnend met" in orde is:
# https://hedy.org/hedy

# dan Opslaan
# style kleur meteen gewijzigd

# vanaf level 4 voor 'tekst' en wis commando
# .ͼ14 {color: #ffc0ff;}
# .ͼt {color: #c0ffff;}

# hoe zelf bepalen welke style nodig
# in webbrowser
# naar https://hedy.org/hedy
# open developer tools met F12
# tab Elementen Elements of Inspector
# click links naast Elementen Elements of Inspector tab op pijl
#   of gebruik Ctrl+Shift+C
#   om element te selecteren
# klik in selectie mode op 'print' in code blok
#   in Elementen Elements of Inspector verschijnt bijbehorend HTML element
# <span class="ͼq">print</span>
# klik op class=..
# rechts bij Stijlen Styles of Dit element
# .ͼq color: #ff6188;
# klik rechts daarvan op generated.css:1 of inline:1
# gaat naar Elementen
#   of naar Sources of Stijleditor
# daarbij staat
# .ͼq {color: #ff6188;}
# wijzig hex waarde naar bijvoorbeeld ffff00
#   kan editen door dubbel klikken op waarde
# waarde blijft niet bewaard
#   bij verder browsen of verversen pagina
# naar https://hedy.org/hedy
# op werkbalk Stylus kiezen
# nieuwe stijl
# toevoegen regel
# .ͼq {color: #ffff00;}
# werkt ook op andere keywords in code blok
# doe hetzelfde voor 'print' in tekst
# <code>print</code>
# toevoegen in Stylus
# code {color: #ffff00;}
# en voor 'print' in foutmeldingen
# <span class="command-highlighted">ask</span>
# toevoegen
# .command-highlighted {color: #ffff00;}


###############################################################
# Aanvulling 1 memoryview en complex

if 0:
    markeer( 'Aanvulling 1 A' )

    # memoryview.py

    # Memoryviews in Python
    # Provided by Anthony Vogelaar, Perth Scotland
    # rev 0.10  2024-03-21  initial release

    import  os;         os.system('clear')

    # Built-in Python data_types
    # https://www.w3schools.com/python/python_datatypes.asp

    # memoryview
    # https://docs.python.org/3/library/stdtypes.html#memoryview

    # for items with known length i.e. bytes and bytearray

    # mv1 = memoryview(b'abcdefg')
    # print('mv1:', type(mv1), mv1)
    # print('mv1[0]:',   mv1[0])
    # print('mv1[0]:',   mv1[0], chr(mv1[0]))
    # mv1[1] = 50
    # print('mv1[1:3]:', mv1[1:3])
    # print('mv1[1:3]:', bytes(mv1[1:3]))

    print()
    mv2 = memoryview(bytearray('abcdefg', 'ascii'))
    print('mv2:', type(mv2), mv2)
    print('mv2[0]:',   mv2[0])
    print('mv2[0]:',   mv2[0], chr(mv2[0]))
    mv2[1] = 50
    print('mv2[1:3]:', mv2[1:3])
    print('mv2[1:3]:', bytearray(mv2[1:3]))
    x = bytearray('BC', 'ascii'); mv2[1:3] = x
    print(bytearray(mv2))
    y = memoryview(b'AA');        mv2[1:3] = y
    print(bytearray(mv2))

if 0:
    markeer( 'Aanvulling 1 B' )

    # memoryview_arrays.py

    # Python memoryview on arrays
    # Provided by Anthony Vogelaar, Perth Scotland
    # rev 0.10  2024-03-20

    import  os;     os.system('clear')
    from    array   import  array               # a built-in module, see https://docs.python.org/3/library/array.html

    a = array('i', [1, 2, 3, 4, 5])             # on 64 bit os under linux i = signed int a 4 byte
    print(type(a), a.itemsize, a)

    mv = memoryview(a)
    print(bytes(a))
    a[2] = 100
    print(bytes(a).hex(' '))
    a[2] = 0x12345678
    print(bytes(a).hex(' '))

if 0:
    markeer( 'Aanvulling 1 C' )

    # py_complex.py

    # zie ook complex.pdf

    # Python complex numbers
    # Provided by Anthony Vogelaar, Perth Scotland
    # rev 0.10  2024-03-20  initial release

    # https://docs.python.org/3.11/library/cmath.html

    import  os;     os.system('clear')
    import  math
    import  cmath

    # print('sqrt( 4):', math.sqrt( 4))
    # print('sqrt( 1):', math.sqrt( 1))
    # print('sqrt( 0):', math.sqrt( 0))
    # print('sqrt(-1):', math.sqrt(-1))
    # print('sqrt(-1):', cmath.sqrt(-1))

    # print()
    # z1 = 3+4j;                      print('z1:', type(z1), z1)
    # x=3; y=4; z2=complex(x, y);     print('z2:', type(z2), z2)

    # print()
    # z3 = 3+4j; print('z3     :', z3)
    # print('z3.real:', z3.real)
    # print('z3.imag:', z3.imag)
    # print('z3.r   :', abs(z3))
    # print('z3.phi :', cmath.phase(z3), 'radians')
    # print('z3.phi :', math.degrees(cmath.phase(z3)), ' degrees')


    # print()
    z3 = 3+4j; print('z3:', z3)
    v3 = cmath.polar(z3);   print('v3:', v3)
    z4 = cmath.rect(*v3);   print('z4:', z4)
    print(f'z4: {z4:.4f}')

###############################################################
# Aanvulling 2 *args, **kwargs, * en /

if 0:
    markeer( 'Aanvulling 2 A' )

    # print functie heeft variabel aantal argumenten
    #   hoe eigen soortgelijke functie maken

    # zie ook
    # https://realpython.com/python-kwargs-and-args/

    if 0:
        print()
        print( 1 )
        print( 1, 2, 'a' )
        print( 1, 2, 'b', -7, 1j, [], () )

    # *args in def
    #   variabel aantal positionele argumenten
    def Print( *args ):
        if 0:
            print( 'args', type(args) ) # tuple
        for arg in args:
            print( arg, end=' ' )
        print()
        
    if 0:
        Print()
        Print( 1 )
        Print( 1, 2, 'a' )
        Print( 1, 2, 'b', -7, 1j, [], () )

    if 0:
        # kan *args ook gebruiken bij aanroep

        args = 2, 4, 'z', set()
        # args = [ 4, 10.7, 1.1j ] # iterabele

        print( args )
        print( *args ) # unpacking operator
    
        Print( args )
        Print( *args )

if 0:
    markeer( 'Aanvulling 2 B' )

    # kan *args combineren met andere positionele

    def Print( val, tst, *args ):

    # def Print( val, tst=32, *args ): # met default waarde
    # def Print( val=-6, tst=32, *args ):

    # def Print( *args, val, tst ): # *args moet achter positionele staan
    #   TypeError: Print() missing 2 required keyword-only arguments

    # def Print( *args0, *args1 ):  # gebruik *args bij def maar 1 keer mogelijk
    #   * argument may appear only once

        if 0:
            print( val, end = ' ' )
            print( tst, end = ' ' )
            for arg in args:
                print( arg, end=' ' )
            print()
        if 1:
            print( 'val', val, end = ' ' )
            print( 'tst', tst, end = ' ' )
            for i, arg in enumerate( args ):
                print( 'arg' + str( i ), arg, end=' ' )
            print()
        if 0:
            print( val, tst, *args ) # gebruik *args eenvoudiger
        if 0:
            print( *args, val, tst ) # andere volgorde hier wel mogelijk
            print( tst, *args, val )
        if 0:
            print( val, tst, *args, *args ) # 2 keer *args hier wel mogelijk

                 # wel goed met default waarde
    # Print()    # TypeError: missing 2 required positional arguments
    # Print( 9 ) # TypeError: missing 1 required positional argument
    # Print( 3, 1, 'z' )
    # Print( 0, 5, 'd', -7, 1j, [], () )

    if 0:
        vb = 7, 8, 9
        # vb = [], (), {}

        # Print( 0, vb, 5 )
        # geen verband argumenten meegeven met gebruik parameters

        # val 0, tst eerste van vb, rest vb en 5 in *args
        Print( 0, *vb, 5 ) 

        # val eerste van vb, tst tweede van vb, rest in *args
        # Print( *vb, 21, 5, *reversed( vb ) ) 

if 0:
    markeer( 'Aanvulling 2 C' )

    # * args naam is gebruikelijk, andere mogelijk
    def eersteRegel( *  bestandsnamen ):
        for naam in bestandsnamen:
            with open( naam ) as fin:
                print( naam, fin.readline(), end='' )

    eersteRegel( 'pcinput.py' )
    namen = ( 'pc_rose.txt', 'pc_woodchuck.txt',
                  'pc_jabberwocky.txt' )
    eersteRegel( * namen )

if 0:
    markeer( 'Aanvulling 2 D' )

    def func( abc, nop ):
        print( abc, nop )

    func( 1, 2 )         # positional arguments
    func( 9, nop=0 )
    func( abc=5, nop=7 ) # keyword arguments

    # func( 3, abc=4 ) # TypeError got multiple values for argument 'abc'
    # func( abc=1, 2 ) # SyntaxError positional argument follows keyword argument
    # func( nop=7, 3 ) # SyntaxError positional argument follows keyword argument

if 0:
    markeer( 'Aanvulling 2 E' )

    def Print( *args ): # *args werkt niet met keyword argumenten
        print( *args )

    # Print( a=10 ) # TypeError unexpected keyword argument
    # Print( args=5 ) # TypeError unexpected keyword argument

    Print( 8, 9, 10 )
    Print( *(8, 9, 10) )

if 0:
    markeer( 'Aanvulling 2 F' )

    # **kwargs in def
    #   variabel aantal keyword argumenten
    def Print( **kwargs ):
        if 0:
            print( 'kwargs', type(kwargs) ) # dict
        if 1:
            for key, val in kwargs.items():
                print( key, val )
        if 0:
            print( kwargs )

    if 0:
        Print()
        # Print( a=10 )
        # Print( 17 ) # TypeError  takes 0 positional arguments but 1 was given
        # Print( a=10, c=[], d=(), e=-3.14 )

    if 0:
        # kan **kwargs ook gebruiken bij aanroep

        kwArgs = dict( x=10, y=11, z=12 )
        # kwArgs = {}
        # kwArgs = { 'q':'Q', 'r':'R', 's':'S', 't':'T' }
        Print( **kwArgs ) # unpacking operator

if 0:
    markeer( 'Aanvulling 2 G' )

    def test( var, tst, **kwargs ):
    # def test( var, tst=2, **kwargs ):
        print( var, tst, kwargs )

    # test( 0 ) # TypeError: test() missing 1 required positional argument: 'tst'
    test( 0, 1 )
    # test( 9, 10, 22 ) # TypeError  takes 2 positional arguments but 3 were given
      # met default TypeError takes from 1 to 2 positional arguments but 3 were given

    # test( 1, 2, 3, zz=4 ) # TypeError  takes 2 positional arguments but 3 were given
    # test( 44, tst=12, abc=33 )
    # test( var=1, tst=2, abc=3 )
    # test( tst=1, var=2, abc=3 )
    # test( abc=3, tst=1, var=2 )

if 0:
    markeer( 'Aanvulling 2 H' )

    # packing en unpacking operators
    
    # zie
    # https://realpython.com/lessons/tuple-assignment-packing-unpacking/

    it = 2, 7, 12
    it2 = range(5)

    # kan niet los gebruikt worden
    # *it     # SyntaxError  can't use starred expression here
    # a = *it # SyntaxError  can't use starred expression here

    # gebruik bij aanroepen functies
    if 0:
        print( *it )

    # en in expressies
    #   operators zijn intern
    #   aanroepen naar (speciale) functies
    if 0:
        *it,

    if 0:
        a = *it,
        print(a)
    
    if 0:
        print( [ it ] )
        print( [ *it ] )

    if 0:
        combi = *it, *it2
        print( combi )

    if 0:
        combi2 = *it, 5, 7
        print( combi2 )

    if 0:
        combi3 = *it2, (5, 7)
        print( combi3 )
        
    # ook bij assignment
    # *a = it # SyntaxError  starred assignment target must be list or tuple
        
    if 0:
        *a, = it
        print( a )  # a is list

    if 0:
        *a, b = it2
        print( a, b )  # a is list

    if 0:
        a, *b, c = it2
        print( a, b, c )  # b is list

if 0:
    markeer( 'Aanvulling 2 I' )

    # print wrapper
    def Print( *args, **kwargs ):

    # **kwargs moet laatste zijn
    # def Print( **kwargs, xyz ): # SyntaxError arguments cannot follow var keyword argument
    # **kwargs mag niet 2x gebruikt worden
    # def Print( **kwargs0, **kwargs1 ): # SyntaxError arguments cannot follow var keyword argument

        if 0:
            print( args )
            print( kwargs )
        if 1:
            print( *args, **kwargs )

    print( 10, 11, 12, sep='-' )
    Print( 10, 11, 12, sep='-' )

if 0:
    markeer( 'Aanvulling 2 J' )

    # print wrapper met log file
    logkw = 'log'
    filekw = 'file'
    def Print( *args, **kwargs ):
        if logkw in kwargs:
            logname = kwargs[ logkw ]
            del kwargs[ logkw ]
            if 0:
                with open( logname, 'a') as logf:
                    # TypeError
                    # got multiple values for keyword argument 'file'
                    print( *args, **kwargs, file=logf )
            if 1:
                curfile = kwargs.get( filekw )
                # print(curfile, kwargs)
                with open( logname, 'a' ) as logf:
                    kwargs[ filekw ] = logf
                    print( *args, **kwargs )
                if curfile:
                    kwargs[ filekw ] = curfile
                else:
                    del kwargs[ filekw ]
        print( *args, **kwargs ) # print kent geen log=

    lognm = 'log1.txt'
    if 0:
        import os
        os.unlink( lognm )

    Print( 10, 20, 30, log=lognm)
    Print( 41, 42, 43, log=lognm)
    Print( 70, 80, 90, log=lognm)

    if 0:
        def logPrint( *args, **kwargs ):
            Print( *args, log=lognm, **kwargs )

        logPrint( 110, 120, 130 )
        logPrint( 141, 142, 143 )
        logPrint( 170, 180, 190 )

    if 0:
        import sys
        Print( 8, 7, 6, log=lognm, file=sys.stderr)
        Print( 111, 222, 333, file=sys.stderr)

    if 0:
        print( 'file', lognm )
        with open( lognm ) as logr:
            print( logr.read() )

if 0:
    markeer( 'Aanvulling 2 K' )

    # print output wrapper met file=
    class MinFile:
        def __init__( self, tag ):
            self.tag = tag
        if 0:
            def write( self, data ):
                print( self.tag, repr(data) )
        if 0:
            def flush( self ):
                print( self.tag, '<flush>' )
            

    minf = MinFile( 'minf' )
    print( 1, 2, 3, file=minf, sep='--', end='!' )
    print( 4, 5, 6, file=minf, flush=True )
    # print( (), [], {}, file=minf, flush=True )

if 0:
    markeer( 'Aanvulling 2 L' )

    # / en * zijn speciale parameters in def
    # bepalen waar positionele en keyword argumenten

    # links van / alleen positioneel
    # rechts van / positioneel of keyword, default

    # links van * positioneel of keyword, default
    # rechts van * alleen keyword

    # zie ook
    # https://realpython.com/python-asterisk-and-slash-special-parameters/    

    # / en * ook gebruikt in documentatie
    # zie bijvoorbeeld
    # https://docs.python.org/3/library/stdtypes.html#list.sort
    # https://docs.python.org/3/library/stdtypes.html#bytes.translate

    # def slash00( / ): # SyntaxError  invalid syntax
    #     pass

    # def slash01( /, a ): # SyntaxError  at least one argument must precede /
    #     pass

    # def slash02( /, **kwargs ):  # SyntaxError  at least one argument must precede /
    #     pass

    # def slahs03( **kwargs, / ):  # SyntaxError  arguments cannot follow var-keyword argument
    #     pass

    def slash1( a, / ):
        print( a )

    if 0:
        slash1( 1 )

        # slash1( a=1 )
        #   TypeError  got some positional-only arguments passed as keyword arguments

    def slash2( a, /, b ):
        print( a, b )

    if 0:
        slash2( 1, 2 )
        slash2( 11, b=12 )
        # slash2( a=21, b=22 )
        #   TypeError: got some positional-only arguments passed as keyword arguments


    # def star00( * ): # SyntaxError  named arguments must follow bare *
    #     pass

    # def star01( *, *args ): # SyntaxError  * argument may appear only once
    #     pass

    # def star02( *args, * ): # SyntaxError  invalid syntax
    #     pass

    def star03( *args ):
        print( *args )

    if 0:
        star03()
        star03( 1, 2, 3 )
        # star03( a=10 )
        #   TypeError  got an unexpected keyword argument


    # def star04( a, * ): # SyntaxError  named arguments must follow bare *
    #     pass

    def star05( a, *args ):
        print( a, *args )

    if 0:
        # star05()
        #   TypeError  star05() missing 1 required positional argument
        star05( 1 )
        star05( 1, 2, 3, 4 )
        
    def star10( *, a ):
        print( a )

    if 0:
        # star10( 1 )
        #   TypeError   takes 0 positional arguments but 1 was given
        star10( a=1 )

    def star11( *args, a ):
        print( *args, a )

    if 0:
        # star11( 1 )
        #   TypeError  missing 1 required keyword-only argument
        star11( a=1 )
        star11( 1, 2, 3, a=1 )

    def star20( a, *, b ):
        print( a, b )

    if 0:
        # star20( 41, 42 )
        #   TypeError  takes 1 positional argument but 2 were given
        star20( 51, b=52 )
        star20( a=61, b=62 )

    def star21( a, *args, b ):
        print( a, *args, b )

    if 0:
        # star21( 41, 42 )
        #   TypeError  missing 1 required keyword-only argument
        star21( 51, b=52 )
        star21( 51, 52, 53, b=54 )
        star21( a=61, b=62 )

    # def mix0( *, / ): # SyntaxError  / must be ahead of *
    #     pass

    # def mix1( /, * ):   # SyntaxError  at least one argument must precede /
    #     pass

    def mix2( a, /, *, b ):
        print( a, b )

    if 0:
        # mix2( 71, 72 )
        #   TypeError  takes 1 positional argument but 2 were given
        mix2( 71, b=72 )
        # mix2( a=71, b=72 )
        #   TypeError  got some positional-only arguments passed as keyword arguments

    def mix3( a, /, b, *, c ):
        print( a, b, c )

    if 0:
        # mix3( 81, 82, 83 )
        #   TypeError  takes 2 positional arguments but 3 were given
        mix3( 91, 92, c=93 )
        mix3( 101, b=102, c=103 )
        # mix3( a=111, b=112, c=113 )
        #   TypeError  got some positional-only arguments passed as keyword arguments
    
###############################################################
# Aanvulling 3 Pip, PyPI en virtuele environments

# standaard Python, CPython
#   wordt beheerd en ondersteund
#   door de Python Software Foundation (PSF)
# PSF beheert ook pip en PyPI

# bespreken hier alleen standaard van PSF
#   en alleen onder Windows
## todo: voeg Linux, macOS toe

# CPython met modules van PSF, zoals pip
#   vormen de standaard Python distributie
# er zijn ook alternatieve distributies
#   niet van PSF, soms deels commercieel
# zoals
#   ActiveState Python
#   Anaconda met conda, ondersteunt ook andere talen
# zie https://wiki.python.org/moin/PythonDistributions

# pip
#   installatie programma
#   wordt ontwikkeld in apart project
#   bij elke Python release wordt
#     de meest recent pip versie meegeleverd
#   tijdens installatie Python
#     optioneel pip installeren
#   ook gebruikt voor uploaden packages
#     niet besproken

# Website        https://pip.pypa.io/
# Read the Docs  https://readthedocs.org/projects/pip/
# Wikipedia      https://en.wikipedia.org/wiki/Pip_(package_manager)
# GitHub         https://github.com/pypa/pip/

# download standaard installatie van Python website
# https://www.python.org/
#   download latest 3.12.3
# https://www.python.org/downloads/release/python-3123/
#   andere releases
# https://www.python.org/downloads/

# kies Windows installer (64-bit) Recommended
# https://www.python.org/ftp/python/3.12.3/python-3.12.3-amd64.exe
# melding:
# De app die u probeert te installeren, is niet
#   door Microsoft geverifieerd
# Toch installeren
# Customize installation
#   checkbox pip keuze

# installatie 3.12 niet gedaan
#  verder met 3.11.4 al geinstalleerd
#   nieuwere 3.11.9 installeren

# na eerdere installatie
# Windows instellingen settings (Win+I)
#   Apps, Geïnstalleerde apps
#   Apps zoeken intypen Py
#     Python 3.8.2 en 3.10.4 en Python launcher
# soms geen 3.11 in lijst
#   kan instellingen niet openen als Admin
# configuratiescherm control panel
#   Programma's en onderdelen
#     Python 3.8.2 en 3.10.4 en Python launcher
# configuratiescherm control panel, Apps >
#   als Administrator uitvoeren
#   Programma's en onderdelen
#     Python 3.8.2 en 3.10.4 en Python launcher
# soms geen 3.11 in lijst

# download 3.11.9 installer
#   installeer voor alle gebruikers
#   Admin nodig
# daarna wel 3.11.9 aanwezig
#   in geinstalleerde Apps en configuratiescherm

# kan installingen wijzigen in Instellingen
#   Apps, Geïnstalleerde apps
# of configuratiescherm na selectie
#   rechtermuisknop menu
# of door nog een keer installatie programma
#   uit te voeren

# bij installatie voor alle gebruikers
# installatie in C:\Program Files\Python311

# pip.exe in
# C:\Program Files\Python311\Scripts

# open command prompt cmd.exe
# run pip
# "c:\Program Files\Python311\Scripts\pip.exe"
# geeft help info zonder args

# > pip list
# pip        24.0
# setuptools 65.5.0

# > pip show
# Warning Error Please provide a package name

# > pip show pip
# info over pip

# > pip freeze
# lege lijst

# kan met pip package installeren

# > pip install PySide6
# Defaulting to user installation because normal site-packages is not writeable
# Warning not on path

# > pip list
# PySide6 en _Addons en _Essentials in list
#   ook shiboken6 erin

# > pip freeze
# lijst met daarin namen met == versienummer

# handiger om geen packages in Program Files te installeren
#   of user directory
# beter bij project

# > pip uninstall PySide6
# Would remove 2 items
# Would not remove: lange lijst
# Proceed Y
#   uninstalled

# > pip list
# nog delen PySide6 aanwezig

# > pip.exe uninstall shiboken6 PySide6_Addons PySide6_Essentials

# > pip list
# alleen nog pip en setuptools

# pip haalt modules en packages online
# standaard vanaf PyPI website


# PyPI, Python Package Index
#   website met heel veel Python modules
#   die niet standaard bij Python meegeleverd worden
# https://pypi.org/

# kan op deze website naar packages zoeken
#   soms met ongeveer dezelfde naam
# soms lastig juiste te vinden

# zoek naar Qt dan niet duidelijk
#   1804 projecten
# zoek naar PySide6 dan wel duidelijk
#   1e van 365 projecten

# zoek naar PIL onduidelijk
#  1698 projecten
# zoek naar Pillow
#  1e van 2107 projecten

# bij elk project staat meer info
# hoe installeren met pip install
# datum laatste versie
# beschrijving
# homepage
# licentie
# python versies

# vaak geven Wikipedia of Google betere resultaten
#   maar soms ook niet
# soms handiger via website van project
# zoeken in lijsten met veel gebruikte projecten
#   zoek naar: awesome python


# virtuele environments
#   modules downloaden en gebruiken zonder
#   de standaard Python installatie te wijzigen

# venv is de standaard module om in Python
#   met virtuele environments te werken
# er zijn nog veel andere soortgelijke systemen
# zoals virtualenv, pyenv, virtualvenvwrapper
#   de meeste zijn niet van PSF
#   pyvenv is verwijderd, zat bij Python 3.3-3.7

# handig om bij elk project
# waarbij niet standaard python modules gebruikt worden
# een eigen bijbehorende virtuele environment te maken

# een virtuele environment komt in een eigen directory
# met een zelf te bepalen naam
#   gebruikelijk is: Venv of venv
# dit is wel wat verwarrend
#   daarom hier: testvenv

# run in de project directory:
# > py -m venv testvenv

# dit runt de venv module als programma
# maakt testvenv directory
# met daarin:
#   testvenv\Include
#   testvenv\Lib
#   testvenv\Lib\site-packages
#   testvenv\Scripts
#   testvenv\pyvenv.cfg

# in site-packages komen de te installeren modules
#   staan al pip en setuptools in

# pyvenv.cfg bevat instellingen
# waaronder een verwijzing naar de installatie van Python
#   die wordt gebruikt door deze venv

# in Scripts staan bat en exe files
# python.exe en pythonw.exe
#   zijn loaders, gebruiken de Python installatie
# pip.exe, pip3.exe, pip3.11.exe
#   run pip voor wijzigen venv
# activate, activate.bat, Activate.ps1
#   scripts, veranderen environment
#     activate is voor bash
#       wanneer activate wordt ingetypt
#         wordt activate.bat uitgevoerd
#     om werken met venv eenvoudiger te maken
#     wijzigen path en prompt
#     zijn niet nodig, kan zonder

# bekijk path en prompt environment variables
# run activate script
# vergelijk verschillen
# > set path
# > set prompt
# voegt environment dirs toe aan begin path

# run testvenv\Scripts\python.exe
#   zonder en met activate maakt niet uit
# >>> import sys
# >>> sys.path
#    testvenv\Lib\site-packages staat in path
# dan is venv actief, daar worden de packages van gehaald

# vergelijk met sys.path bij py.exe
#    of python.exe uit C:\Program Files
# bij python.exe venv niet actief zonder activate
# bij py.exe wordt venv wel gebruikt na activate

# run testvenv\Scripts\python.exe
#   of pythonw.exe of pip.exe
# rename testvenv\pyvenv.cfg
#   .exe programma's in testenv
#     werken dan niet meer
# verwijzing naar Python installatie nodig

# run testvenv\Scripts\pip.exe
#   kan zonder activate
# daarmee modules installeren
#   komen in testvenv\Lib\site-packages

# pip bij Python installatie is vaak niet nieuwste versie
#   dan melding van pip, upgrade met
# python.exe -m pip install --upgrade pip

# testvenv directory kan heel groot worden
# gebruik daarom testvenv alleen voor
#   de virtuele environment met
#     door pip geinstalleerde packages
# en voor geen andere zaken

# na installeren packages
# testvenv\Scripts\pip freeze >requirements.txt
#   in requirements.txt lijst met namen en versies

# kan dan testvenv weghalen
#   door hele testvenv directory te deleten
#   of eerst te renamen naar bijvoorbeeld testvenvOud
#     en veel later pas te wissen

# daarna opnieuw maken
# > py -m venv testvenv
# > testvenv\Scripts\pip install -r requirements.txt
# > testvenv\Scripts\pip list

# in Python cmd line py.exe in Venv

# shortcut properties eigenschappen aanpassen
# default args
#   "C:\Program Files\Python311\pythonw.exe"
#     "C:\Program Files\Python311\Lib\idlelib\idle.pyw"
# 1e wijzigen naar pythonw.exe in venv, activate niet nodig
#   "C:\Users\Windows User\Spronck\bezig\testvenv\Scripts\pythonw.exe"
#      "C:\\Program Files\Python311\Lib\idlelib\idle.pyw"
# kan ook in venv Idle openen met venv
#    python -m idlelib

# in Scripts nog meer .exe programma's na package installatie
#   afhankelijk van package wanneer erbij
# bekijk bijvoorbeeld na install PySide6


# voorbeeld venv voor Whisper
# open source spraakherkenning
# https://openai.com/
# Whisper
# https://openai.com/research/whisper

# How to Use Whisper AI: The Only Guide You Need
# https://www.notta.ai/en/blog/how-to-use-whisper
# Offline speech-to-text that is accurate and free (as in speech)
# https://tinyapps.org/blog/202211020700_text-to-speech.html

# ook cuda toolkit, ffmpeg en voorbeeld mp3 nodig

# rem installeer OpenAI Whisper
# rem op Windows 10 en 11 x64
# rem eerst handmatig ffmpeg.exe installeren in curdir
# rem eerst handmatig MSVC redistributable installeren
# rem eerst handmatig Python 3.11 installeren
# path=c:\Program Files\Python311\;%path%
# rem soms eerst handmatig Git installeren
# rem path=c:\Program Files\Git\bin\;%path%
# python -m venv Venv
# call Venv/Scripts/Activate
# python.exe -m pip install --upgrade pip

# rem PyTorch met cpu
# rem pip install torch torchvision torchaudio
# rem of PyTorch met gpu cuda 12.1
# pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# rem van PyPi.org
# pip install openai-whisper
# rem of van Github
# rem pip install git+https://github.com/openai/whisper.git

###############################################################
# Aanvulling 4 Pillow / PIL

# module om plaatjes te bewerken

# Website        https://python-pillow.org/
# Documentatie   https://pillow.readthedocs.io
# Pdf            https://pillow.readthedocs.io/_/downloads/en/stable/pdf/
# Read the Docs  https://readthedocs.org/projects/pillow/
# Wikipedia      https://en.wikipedia.org/wiki/Python_Imaging_Library
# GitHub         https://github.com/python-pillow/Pillow

# PIL, Python Imaging Library
# auteur Fredrik Lundh, Secret Labs AB
# eerste versie in 1995
#   is gestopt, laatste versie in 2011 voor Python versie 2.7

# Pillow
#   opvolger, fork van PIL
# auteur Jeffrey A. Clark
# eerste versie in 2010
#   huidige versie 10.3.0 van 1 april 2024

# installeren in een virtual environment met pip

# in Windows
# > py -m venv Venv
# > Venv\Scripts\activate
# (Venv) > pip install Pillow
# (Venv) > pip list
#   pillow     10.3.0

if 0:
    markeer( 'Aanvulling 4 A' )

    # controleer installatie

    import PIL

    print( PIL.__doc__ )
    print( PIL.__version__ )
    # print( PIL.__file__ )
    # print( PIL.__path__ )

if 0:
    markeer( 'Aanvulling 4 B' )

    # eerst een directory met plaatjes klaarzetten
    # bijvoorbeeld download plaatjes van
    # https://www.hlevkin.com/hlevkin/06testimages.htm
    # eenvoudiger met Firefox extensie DownThemAll

    # plaatjes bestanden bekijken
    #   in Windows File Explorer
    #   weergeven grote pictogrammen

    import glob
    import os
    import os.path
    import PIL.Image as pilImg
    import PIL.ImageDraw as pilDraw
    import PIL.ImageFont as pilFont

    def imgShow( filename ):
        with pilImg.open( filename ) as img:
            img.show()

    # https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
    def imgInfo( filename ):
        with pilImg.open( filename ) as img:
            print( '  fname ', img.filename )
            print( '  format', img.format )
            print( '  mode  ', img.mode )
            print( '  siz+wh', img.size, img.width, img.height )
            print( '  palet ', img.palette )
            print( '  info  ', img.info )
            try:
                print( '  animtd', img.is_animated )
                print( '  nframs', img.n_frames )
            except AttributeError:
                pass
            print( '  transp', img.has_transparency_data )
            print( '  bands ', img.getbands() )

    def outName( filename, extout ):
        pth, fnam = os.path.split( filename )
        nam, extin = os.path.splitext( fnam )
        pth += '\\out' + extout + '\\'
        os.makedirs( pth, exist_ok=True )
        fname = pth + nam + extout
        print( '  ', fname)
        return fname

    def fixOldJpg( img ):
        if 1:
            # https://stackoverflow.com/questions/21669657/getting-cannot-write-mode-p-as-jpeg-while-operating-on-jpg-image
            if img.mode != 'RGB':
                img = img.convert( 'RGB' )
        return img
        
    def imgSave( filename, extout ):
        fnout = outName( filename, extout )
        with pilImg.open( filename ) as img:
            img = fixOldJpg(img)
            img.save( fnout )
        
    def imgThumb( filename, size ):
        fnout = outName( filename, '.thumb' )
        with pilImg.open( filename ) as img:
            img = fixOldJpg(img)
            img.thumbnail( size )
            # img.save( fnout ) # unknown file extension
            img.save( fnout, 'JPEG' )

    def imgTransform( filename ):
        fnout = outName( filename, '.jpg' )
        with pilImg.open( filename ) as img:
            img = fixOldJpg(img)
            if 0:
                img = img.resize( ( 256, 256 ) )
            if 0:
                img = img.transpose( pilImg.Transpose.FLIP_LEFT_RIGHT )
            if 0:
                img = img.rotate( 45 )
            img.save( fnout )

    def imgMark( filename, txt, font ):
        offset = 10
        fnout = outName( filename, '.png' )
        with pilImg.open( filename ) as img:
            img = fixOldJpg(img)

            textimg = pilImg.new( 'RGB', (0, 0) )
            textdraw = pilDraw.Draw( textimg )
            _, _, textwidth, textheight = textdraw.textbbox((0, 0), text=txt, font=font)

            textimg = pilImg.new( 'RGB', (textwidth, textheight) )
            textdraw = pilDraw.Draw( textimg )
            textdraw.text((0, 0), txt, fill = '#ffffff', font=font)
            textimg.putalpha(48)

            width, height = img.size
            pos = width - textwidth - offset, height - textheight - offset
            img.paste( textimg, pos, mask=textimg )
            img.save( fnout )
    
    imagefiles = r'hlevkin\*'
    # imagefiles = r'3d\*'
    # imagefiles = r'3d.out\*'
    # imagefiles = r'sa\*'
    for ix, img in enumerate( glob.glob( imagefiles ) ):
        if not os.path.isfile( img ):
            continue
        print( ix, img )
        if 0:
            imgShow( img )
            if input( 'Alleen Enter voor volgende, niet leeg voor stop:' ):
                break

        if 0:
            imgInfo( img )

        if 0:
            imgSave( img, '.gif' )
            # imgSave( img, '.png' )
            # imgSave( img, '.jpg' )

        if 0:
            imgThumb( img, (32, 32) )

        if 0:
            imgTransform( img )
        
        if 0:
            font = pilFont.truetype( 'CourierPrime-Regular.ttf', 50 )
            text = 'watermerk'
            imgMark( img, text, font )

###############################################################
# Aanvulling 5 PySide6, Qt for Python

# zie de 14 voorbeeld projecten van Anthony Vogelaar
#   in de python_qt_intro directory
#     in PythonCursusXX.zip

# sommige van deze projecten gebruiken enkele van elkaar
#   afhankelijke modules, daarom kunnen ze niet in
#   ProgrammeursleerlingExtra.py opgenomen worden

###############################################################
# Aanvulling 6 Tkinter

# standaard module voor een grafische gebruikersomgeving
#   GUI, graphical user interface
#  https://docs.python.org/3/library/tkinter.html
#  https://en.wikipedia.org/wiki/Tkinter

# gebaseerd op Tcl/Tk
#   Tk   cross-platform widget toolkit
#     https://en.wikipedia.org/wiki/Tk_(software)
#   Tcl  programmeertaal, interpreter
#     https://en.wikipedia.org/wiki/Tcl

# Tcl/Tk heeft BSD licentie compatibel met Python licentie
#   is wel probleem bij sommige andere GUI modules
#     zoals PySide6 (LGPL) of
#       wxWidgets (LGPL met uitzondering)
#  die daarom nooit standaard bij Python gaan komen

# Tcl/Tk is geschreven in C
#   gecompileerd beschikbaar in Python
#   als dll files via interne _tkinter module

# Tkinter is bij sommige Python installaties niet aanwezig
#   zoals niet bij embedded voor Windows
# vaak extra installatie nodig voor Linux

# Tkinter is de Tk interface
#   van Python naar Tcl/Tk en terug
#   converteert functies en argumenten
#     om geschikt te maken voor verwerken met Tcl/Tk
#   en ook voor callbacks
#     aanroepen van Python functies vanaf Tcl/Tk

# Tkinter bestaat al lang
#   IDLE, de standaard IDE van Python
#     gebruikt Tkinter
# Tcl/Tk maakt veel gebruik van strings
#   dat komt terug in de manier van werken met Tkinter
#     bijvoorbeeld geometry "200x100"

# de widgets in Tk zien er vaak ongebruikelijk of verouderd uit
# daarom is er ook een Ttk (Themed Tk) module
#   met widgets die er moderner uitzien
#   met bijna gelijke functionaliteit

# Tkinter bestaat uit een aantal lagen
#   zoals de meeste cross platform libraries
# een Python programma roept de tkinter module aan
#   tkinter genereert een Tcl/Tk command string
#     en geeft die door aan _tkinter
#   _tkinter roept de Tcl interpreter aan
#   Tcl interpreteert de command string
#     en roept Tk / Ttk aan
#   Tk en Ttk roepen, afhankelijk van het platform,
#     GDI (Win32), Xlib (Linux) of Cocoa (macOS) aan
#   https://en.wikipedia.org/wiki/Graphics_Device_Interface
#   https://en.wikipedia.org/wiki/Xlib
#   https://en.wikipedia.org/wiki/Cocoa_(API)

# meer info over Tkinter

# Tk Best Practices
# https://tkdocs.com
# https://tkdocs.com/tutorial/onepage.html

# Tkinter 8.5 reference van 2013
# https://tkdocs.com/shipman/
# https://tkdocs.com/shipman/tkinter.pdf

# Python GUI Programming With Tkinter
# https://realpython.com/python-gui-tkinter/
# https://realpython.com/quizzes/python-gui-programming-with-tkinter/

# Tcl/Tk 8.6 Manual
# http://www.tcl-lang.org/man/tcl8.6/

if 0:
    markeer( 'Aanvulling 6 A' )

    # minimaal programma
    #   met zichtbaar werkend window
    #   kan verplaatsen, afmetingen wijzigen, afsluiten
    import tkinter

    # Tk is hoogste niveau widget
    #   bevat eigen Tcl interpreter
    root = tkinter.Tk()
    # print( root.master ) # None
    root.mainloop()
    # print( root.master ) # None

if 0:
    markeer( 'Aanvulling 6 B' )

    import tkinter

    # werkt ook
    #   maakt automatisch een Tk instance
    if 1:
        button = tkinter.Button() # nogal klein
    else:
        button = tkinter.Button( text="Knop" )
    if 1:
        # anders button nog niet zichtbaar
        button.grid( column=0, row=0 )
    print( button.master )        # .
    print( button.master.master ) # None
    button.mainloop()

if 0:
    markeer( 'Aanvulling 6 C' )

    import tkinter

    # beter zelf Tk instance maken
    root = tkinter.Tk()
    button = tkinter.Button( root, text="Knop" )
    button.grid( column=0, row=0 )
    print( button.master ) # .
    print( button.master.master ) # None
    root.mainloop()
    # button.mainloop() # kan ook

if 0:
    markeer( 'Aanvulling 6 D' )

    import tkinter

    # place, pack of grid nodig
    #   om button zichtbaar te maken

    root = tkinter.Tk()
    button = tkinter.Button( root, text="Knop" )
    if 1:
        button.place() # niet zichtbaar
    if 0:
        button.place( x=30, y=30 ) # links
    if 0:
        button.pack( ) # in midden
    if 0:
        button.grid( column=0, row=0 ) # links
    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 E' )

    import tkinter

    # grid is meest flexibel
    root = tkinter.Tk()
    button = tkinter.Button( root, text="Knop" )
    button.grid( column=0, row=0 )
    if 0:
        root.rowconfigure( 0, weight=1 )
        root.columnconfigure( 0, weight=1 )
    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 E' )

    import tkinter

    root = tkinter.Tk()
    if 0:
        root.geometry('400x300')
        # root.geometry('-300x-500') # _tkinter.TclError: bad geometry specifier
        # root.geometry('300x300+500+500') # vanaf linksboven
        # root.geometry('300x300-100-100') # vanaf rechtsonder
    if 0:
        label = tkinter.Label( root, text="Label" )
    else:
        label = tkinter.Label( root, text="Label", background='yellow' )
    if 1:
        label.grid( column=0, row=0 )
    else:
        label.grid( column=0, row=0, sticky='N' )
        # label.grid( column=0, row=0, sticky='NE' )
        # label.grid( column=0, row=0, sticky=tkinter.SW )
        # label.grid( column=0, row=0, sticky=tkinter.W + tkinter.E )
        # label.grid( column=0, row=0, sticky='NESW' )
    if 0:
        label['background'] = 'magenta'
    if 0:
        label.configure( background='#cff' )
        # label.configure( background='#ff7733' )
    root.rowconfigure( 0, weight=1 )
    root.columnconfigure( 0, weight=1 )
    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 F' )

    import tkinter

    root = tkinter.Tk()
    label = tkinter.Label( root, text="Label", background='yellow' )
    label.grid( column=0, row=0 )
    if 1:
        root.rowconfigure( 0, weight=1 )
    if 1:
        root.columnconfigure( 0, weight=1 )
        # root.columnconfigure( 0, weight=3 )
    button = tkinter.Button( root, text = "Knop" )
    button.grid( column=1, row=0 )
    if 1:
        # if 1:
        #    root.rowconfigure( 0, weight=1 ) # bij label
        root.columnconfigure( 1, weight=1 )
    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 G' )

    import tkinter

    root = tkinter.Tk()

    entry = tkinter.Entry( root )
    if 1:
        entry.grid( column=0, row=0 )
    else:
        # entry.grid( column=0, row=0, sticky='WE' )
        entry.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
    root.rowconfigure( 0, weight=1 )
    root.columnconfigure( 0, weight=1 )

    button = tkinter.Button( root, text="Knop" )
    if 1:
        button.grid( column=1, row=0 )
    else:
        button.grid( column=1, row=0, padx=10, pady=10 )
    # root.rowconfigure( 0, weight=1 ) # bij entry
    if 1:
        root.columnconfigure( 1, weight=1 )

    label = tkinter.Label( root, text="Label", background='yellow' )
    if 1:
        label.grid( column=2, row=0 )
    else:
        label.grid( column=2, row=0, padx=10, pady=10 )
    # root.rowconfigure( 0, weight=1 ) # bij entry
    if 1:
        root.columnconfigure( 2, weight=1 )

    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 H' )

    import tkinter

    root = tkinter.Tk()

    txtvar = tkinter.StringVar( root )
    entry = tkinter.Entry( root, textvariable=txtvar )
    entry.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
    root.rowconfigure( 0, weight=1 )
    root.columnconfigure( 0, weight=1 )

    def onClick():
        if 1:
            print( 'Knop klik')
        else:
            label.configure( text=txtvar.get() )

    button = tkinter.Button( root, text="Knop", command=onClick )
    button.grid( column=1, row=0, padx=10, pady=10 )
    # root.rowconfigure( 0, weight=1 ) # bij entry
    # root.columnconfigure( 1, weight=1 )

    label = tkinter.Label( root, text="Label", background='yellow' )
    label.grid( column=2, row=0, padx=10, pady=10 )
    # root.rowconfigure( 0, weight=1 ) # bij entry
    # root.columnconfigure( 2, weight=1 )

    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 I' )

    import tkinter

    root = tkinter.Tk()

    txtvar1 = tkinter.StringVar( root )
    entry1 = tkinter.Entry( root, textvariable=txtvar1 )
    entry1.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
    root.rowconfigure( 0, weight=1 )
    root.columnconfigure( 0, weight=1 )

    txtvar2 = tkinter.StringVar( root )
    entry2 = tkinter.Entry( root, textvariable=txtvar2 )
    entry2.grid( column=0, row=1, sticky='WE', padx=10, pady=10 )
    root.rowconfigure( 1, weight=1 )
    # root.columnconfigure( 0, weight=1 ) # bij entry1

    def onClick():
        if 0:
            print( 'Knop klik')
        else:
            label1.configure( text=txtvar1.get() )
            label2.configure( text=txtvar2.get() )

    def onClickIdx(index):
        if index == 1:
            label1.configure( text=txtvar1.get() )
        else:
            label2.configure( text=txtvar2.get() )
            
    button1 = tkinter.Button( root, text="Knop1", command=onClick )
    def click1():
        onClickIdx(1)
    # button1 = tkinter.Button( root, text="Knop1", command=click1) # lambda:onClickIdx(1) )
    button1.grid( column=1, row=0, padx=10, pady=10 )
    # root.rowconfigure( 0, weight=1 ) # bij entry1

    button2 = tkinter.Button( root, text="Knop2", command=onClick )
    # button2 = tkinter.Button( root, text="Knop2", command=lambda:onClickIdx(2) )
    button2.grid( column=1, row=1, padx=10, pady=10 )
    # root.rowconfigure( 1, weight=1 ) # bij entry1

    label1 = tkinter.Label( root, text="Label1", background='yellow' )
    label1.grid( column=2, row=0, padx=10, pady=10 )
    # root.rowconfigure( 0, weight=1 ) # bij entry2

    label2 = tkinter.Label( root, text="Label2", background='yellow' )
    label2.grid( column=2, row=1, padx=10, pady=10 )
    # root.rowconfigure( 1, weight=1 ) # bij entry2

    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 J' )

    import tkinter
    import tkinter.ttk as ttk

    def onClickIdx(index):
        if index == 1:
            label1.configure( text=txtvar1.get() )
        else:
            label2.configure( text=txtvar2.get() )
            
    root = tkinter.Tk()
    root.title('Test Window')
    # frm1 = tkinter.Frame( root )
    # frm1 = tkinter.Frame( root, borderwidth=1, relief=tkinter.SOLID )
    # frm1 = tkinter.Frame( root, borderwidth=1, relief=tkinter.RAISED )
    frm1 = ttk.Frame( root, borderwidth=1, relief=tkinter.RAISED )
    frm1.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
    root.rowconfigure( 0, weight=1 )
    root.columnconfigure( 0, weight=1 )

    txtvar1 = tkinter.StringVar( frm1 )
    entry1 = tkinter.Entry( frm1, textvariable=txtvar1 )
    entry1.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
    frm1.rowconfigure( 0, weight=1 )
    frm1.columnconfigure( 0, weight=1 )

    button1 = tkinter.Button( frm1, text="Knop1", command=lambda:onClickIdx(1) )
    button1.grid( column=1, row=0, padx=10, pady=10 )
    # frm1.rowconfigure( 0, weight=1 ) # bij entry1

    label1 = tkinter.Label( frm1, text="Label1", background='yellow' )
    label1.grid( column=2, row=0, padx=10, pady=10 )
    # frm1.rowconfigure( 0, weight=1 ) # bij entry1

    frm2 = tkinter.Frame( root )
    frm2.grid( column=0, row=1, sticky='WE', padx=10, pady=10 )
    root.rowconfigure( 1, weight=1 )
    root.columnconfigure( 0, weight=1 )

    if 1:
        entry2col, label2col = 0, 2
    else:
        entry2col, label2col = 2, 0
    txtvar2 = tkinter.StringVar( frm2 )
    entry2 = tkinter.Entry( frm2, textvariable=txtvar2 )
    entry2.grid( column=entry2col, row=1, sticky='WE', padx=10, pady=10 )
    frm2.rowconfigure( 1, weight=1 )
    frm2.columnconfigure( entry2col, weight=1 )

    button2 = tkinter.Button( frm2, text="Knop2", command=lambda:onClickIdx(2) )
    # button2 = ttk.Button( frm2, text="Knop2", command=lambda:onClickIdx(2) )
    button2.grid( column=1, row=1, padx=10, pady=10 )
    # frm2.rowconfigure( 1, weight=1 ) # bij entry2

    label2 = tkinter.Label( frm2, text="Label2", background='yellow' )
    label2.grid( column=label2col, row=1, padx=10, pady=10 )
    # frm2.rowconfigure( 1, weight=1 ) # bij entry2

    if 0:
        # timer
        afterCount = [ 0 ]
        def onAfter():
            root.title( str( afterCount[0] ) )
            afterCount[0] += 1
            root.after( 1000, onAfter )
        root.after( 1000, onAfter )

    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 K' )

    # als Aanvulling 6 H met class
    
    import tkinter

    class Gui( tkinter.Tk ):
        def __init__( self ):
            super().__init__()

            self.txtvar = tkinter.StringVar( self )
            self.entry = tkinter.Entry( self, textvariable=self.txtvar )
            self.entry.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
            self.rowconfigure( 0, weight=1 )
            self.columnconfigure( 0, weight=1 )

            self.button = tkinter.Button( self, text="Knop",
                                          command=self.onClick )
            self.button.grid( column=1, row=0, padx=10, pady=10 )
            self.rowconfigure( 0, weight=1 )
            # self.columnconfigure( 1, weight=1 )

            self.label = tkinter.Label( self, text="Label", background='yellow' )
            self.label.grid( column=2, row=0, padx=10, pady=10 )
            self.rowconfigure( 0, weight=1 )
            # self.columnconfigure( 2, weight=1 )

        def onClick( self ):
            if 1:
                print( 'Knop klik')
            else:
                self.label.configure( text=self.txtvar.get() )

    root = Gui()
    root.mainloop()

if 0:
    markeer( 'Aanvulling 6 L' )

    # met Text widget
    
    import tkinter

    class Gui( tkinter.Tk ):
        def __init__( self ):
            super().__init__()

            self.txtvar = tkinter.StringVar( self )
            self.entry = tkinter.Entry( self, textvariable=self.txtvar )
            self.entry.grid( column=0, row=0, sticky='WE', padx=10, pady=10 )
            # self.rowconfigure( 0, weight=1 )
            self.columnconfigure( 0, weight=1 )

            self.button = tkinter.Button( self, text="Knop",
                                          command=self.onClick )
            self.button.grid( column=1, row=0, padx=10, pady=10 )
            # self.rowconfigure( 0, weight=1 )
            # self.columnconfigure( 1, weight=1 )

            self.label = tkinter.Label( self, text="Label", background='yellow' )
            self.label.grid( column=2, row=0, padx=10, pady=10 )
            # self.rowconfigure( 0, weight=1 )
            # self.columnconfigure( 2, weight=1 )

            self.text = tkinter.Text( self )
            # self.text.grid( column=0, row=1 )
            # self.text.grid( column=0, row=1, sticky='WNSE', padx=10, pady=10 )
            self.text.grid( column=0, row=1, sticky='WNSE', padx=10, pady=10,
                            columnspan=3)
            self.rowconfigure( 1, weight=1 )
            self.columnconfigure( 1, weight=1 )

        def onClick( self ):
            if 1:
                print( 'Knop klik')
                # zie 24.1 in https://tkdocs.com/shipman/tkinter.pdf
                print( self.text.get( '1.0', 'end' ) )
            else:
                self.label.configure( text=self.txtvar.get() )

    root = Gui()
    root.mainloop()

###############################################################
# Aanvulling 7 Matplotlib

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_matplotlib directory
#     in PythonCursusXX.zip

###############################################################
# Aanvulling 8 guizero

# guizero is een relatief eenvoudige Python GUI library
#   bedoeld om het maken van een GUI
#   zo gemakkelijk mogelijk te maken

# guizero is gebaseerd op tkinter
#   vormt een laag erboven

# https://lawsie.github.io/guizero/

# Create Graphical User Interfaces with Python
#   door Laura Sachs en Martin O'Hanlon
# free PDF, 156 bladzijden, Raspberry Pi Press
# https://lawsie.github.io/guizero/book/
# https://magpi.raspberrypi.com/books/create-guis

# https://pypi.org/project/guizero/

# installeren en gebruiken
#   bijvoorbeeld met opdrachtprompt cmd.exe

# > py -m venv Venv
# > Venv\Scripts\activate
# (Venv) > pip install guizero
# (Venv) > py -m idlelib ProgrammeursleerlingExtra.py

if 0:
    markeer( 'Aanvulling 8 A' )

    import guizero

    app = guizero.App()
    app.display()
    
if 0:
    markeer( 'Aanvulling 8 B' )

    # command button callback

    import guizero

    app = guizero.App( title='Voorbeeld' )
    # app = guizero.App( 'Example', bg='green' )
    # print( app )
    # app.bg = 'orange'

    text = guizero.Text( app, text='Label tekst' )
    # print( text )

    textbox = guizero.TextBox( app )

    if 0:
        def onKlik():
            # text.text = 'Gedaan'  # andere naam dan bij init
            text.value = 'Klik gedaan'

        button = guizero.PushButton( app, text='Klik', command=onKlik )
    
    app.display()
    
if 0:
    markeer( 'Aanvulling 8 B' )

    # command button callback

    import guizero

    app = guizero.App( title='Voorbeeld' )

    text = guizero.Text( app, text='Label tekst' )

    button = guizero.PushButton( app, text='Hide',
                                 command=text.hide )
    button = guizero.PushButton( app, text='Show',
                                 command=text.show )
    button = guizero.PushButton( app, text='Disable',
                                 command=text.disable )
    button = guizero.PushButton( app, text='Enable',
                                 command=text.enable )
    if 0:
        button = guizero.PushButton( app, text='Destroy',
                                 command=text.destroy )
    if 0:
        # todo: werkt niet na destroy, disable, enable
        def mkText():
            text = guizero.Text( app, text='Label tekst' )

        button = guizero.PushButton( app, text='Maak',
                                 command=mkText )
    
    app.display()

if 0:
    markeer( 'Aanvulling 8 C' )

    # command button callback met args

    import guizero

    app = guizero.App( title='Voorbeeld' )

    text = guizero.Text( app, text='Label tekst' )

    def onKlik( func ):
        func()
    if 0:
        def onKlik( *args ):
            func = args[0]
            func()

    button = guizero.PushButton( app, text='Hide',
                                 command=onKlik, args=(text.hide, ) )
    button = guizero.PushButton( app, text='Show',
                                 command=onKlik, args=[text.show] )
    button = guizero.PushButton( app, text='Disable',
                                 command=onKlik, args=[text.disable] )
    button = guizero.PushButton( app, text='Enable',
                                 command=onKlik, args=[text.enable] )
    app.display()

if 0:
    markeer( 'Aanvulling 8 D' )

    # 1 app meer windows

    import guizero

    app = guizero.App( title='App' )
    text = guizero.Text( app, text='App label' )

    win1 = guizero.Window( app, title='Win1' )
    text1 = guizero.Text( win1, text='Label1' )

    win2 = guizero.Window( app, title='Win2' )
    text2 = guizero.Text( win2, text='Label2' )

    if 0:
        button = guizero.PushButton( app, text='Hide Win1',
                                     command=win1.hide )
        button = guizero.PushButton( app, text='Show Win1',
                                     command=win1.show )
        button = guizero.PushButton( app, text='Show modal Win1',
                                     command=lambda: win1.show(wait = True) )
    
    app.display()
    
if 0:
    markeer( 'Aanvulling 8 E' )

    # default is auto layout
    #   in volgorde van aanmaken
    # positie wijzigen met align

    import guizero

    app = guizero.App( title='Auto layout' ) # , layout='auto' )
    textl = guizero.Text( app, text='left', align = 'left')
    textt = guizero.Text( app, text='top', align = 'top') # todo: staat rechts van centrum waarom?
    textr = guizero.Text( app, text='right', align = 'right')
    textb = guizero.Text( app, text='bottom', align = 'top') # 'bottom')
    if 0:
        textbox = guizero.TextBox( app )

    if 0:
        def onKlik():
            textb.value = 'Klik gedaan'
            if 0:
                textb.align = 'top'
            
            if 0:
                # todo: knop komt rechts van midden, waarom?
                textt.align = 'left'
                textr.align = 'left'
                textb.align = 'left'
        

        button = guizero.PushButton( app, text='Klik', command=onKlik )
    
    app.display()
    
if 0:
    markeer( 'Aanvulling 8 F' )

    import guizero

    app = guizero.App( title='Experiment', layout='auto' )

    # textbox = guizero.TextBox( app )
    # textbox = guizero.TextBox( app, width='fill' )
    # print( app.width )
    # textbox = guizero.TextBox( app, width=app.width // 2 )
    # textbox = guizero.TextBox( app, width=20 ) # karakters, geen pixels
    # print( textbox.width ) # soms 'fill', onhandig

    # listbox = guizero.ListBox( app )
    # listbox = guizero.ListBox( app, height=10 ) # warning
    # listbox = guizero.ListBox( app, width=100, height=100 ) # pixels
    # listbox = guizero.ListBox( app, height='fill' )
    # listbox = guizero.ListBox( app, width=100, height=app.height // 2 )
    app.display()


###############################################################
# Aanvulling 9 PySimpleGUI

# laag boven andere GUI libraries
# om het simpeler te maken

# https://pysimplegui.com

# https://pypi.org/project/PySimpleGUI/

# gebruikt tkinter, Qt, Remi of WxPython
# https://docs.pysimplegui.com/

# Remi
# open source GUI library voor webbrowsers
#   GUI wordt omgezet naar HTML
#   gebruikt webserver
# https://github.com/rawpython/remi
# https://remi.readthedocs.io

# > py -m venv Venv
# > Venv\Scripts\activate
# (Venv) > pip install PySimpleGUI
# (Venv) > py -m idlelib ProgrammeursleerlingExtra.py

if 0:
    # nieuwe versie 5 komt eerste keer met dialog
    # moet akkoord gaan met licentie
    # gratis registreren voor hobbyisten
    # commerciële gebruikers betalen $99
    
    markeer( 'Aanvulling 9 A' )

    import PySimpleGUI

# PySimpleGUI is begin 2024 commercieel geworden
#   niet meer open source vanaf versie 5

# PySimpleGUI 4 will be sunsetted in Q2 2024
# https://docs.pysimplegui.com/en/latest/readme/sunset/

# Hacker News
# https://news.ycombinator.com/item?id=39369353

# (Venv) > pip uninstall PySimpleGUI
# (Venv) > pip install PySimpleGUI==4.60.5

if 0:
    markeer( 'Aanvulling 9 B' )

    import PySimpleGUI

    print( PySimpleGUI.__version__ ) # 4.60.5

if 0:
    markeer( 'Aanvulling 9 C' )

    import PySimpleGUI as gui

    layout = [
        [ gui.Text( 'Label' ) ],
        [ gui.InputText() ],
        [ gui.Button( 'OK' ) ]
        ]
    wnd = gui.Window( 'SimpleGUI', layout )
    while True:
        event, vals = wnd.read()
        if event == gui.WIN_CLOSED:
            break
        print( event, vals )
        if event == 'OK':
            print( 'Clicked OK' )
    wnd.close()

###############################################################
# Aanvulling 10 callbacks, decorators en properties

# callbacks
# gebruikt bij sort voor bepalen key
#   of in GUI code bij events
# veel meer mogelijke toepassingen

# gebruik referentie naar functie
#   aanroepen gebeurt later pas

if 0:
    markeer( 'Aanvulling 10 A' )

    def aanroep( txt, func ):
        print( 'voor', txt )
        func()
        print( 'na', txt )

    if 0:
        def callback1():
            print( 'callback1' )

        def callback2():
            print( 'callback2' )

        def callback3():
            print( 'callback3' )

        aanroep( 2,       callback2 )
        aanroep( 'drie',  callback3 )
        aanroep( '1',     callback1 )
    
    if 0:
        class cb4:
            def __call__( self ):
                print( 'cb4' )

        aanroep( 'cb4',   cb4 )
        aanroep( 'cb4()', cb4() )

        # cb4 # doet verder niks
        # cb4() # maak instance
        # cb4()() # roep __call__ aan

    if 0:
        def maakcb5():
            def callb5():
                print( 'callb5' )
            return callb5

        aanroep( 'maakcb5', maakcb5() )

    if 0:
        aanroep( 'lambda6', lambda : print( 'lmb6' ) )

    if 0:
        for lmb in range( 11, 14 ):
            aanroep( 'lambd' + str(lmb),
                     lambda : print( 'lmbd' + str( lmb ) ) )

    if 0:
        for clb in range( 21, 24 ):
            def calb():
                print( 'clb' + str( clb ) )
            aanroep( 'calb' + str(clb), calb )
            # print( id( calb ) )

    if 0:
        # gebruikt waarde van clb bij aanroep calb
        calbs = [ ]
        for clb in range( 31, 34 ):
            def calb():
                print( 'clb' + str( clb ) )
            calbs.append( calb )
        for idx, calb in enumerate( calbs ):
            aanroep( 'calb' + str(idx), calb )

    if 0:
        # gebruikt waarde van clb bij def calb
        #   worden bewaard in __defaults__ en __kwdefaults__
        calbs = [ ]
        for clb in range( 41, 44 ):
            def calb(clb=clb):
                print( 'clb' + str( clb ) )
            calbs.append( calb )
        for idx, calb in enumerate( calbs ):
            aanroep( 'calb' + str( idx ), calb )

    if 0:
        for cb in range( 51, 54 ):
            def maakcb( cab ):
                def callb():
                    print( 'callb' + str( cab ) )
                return callb
            aanroep( 'maakcb' + str( cb ), maakcb( cb ) )

    if 0:
        maakcbs = [ ]
        for cb in range( 61, 64 ):
            def maakcb( cab ):
                def callb():
                    print( 'callb' + str( cab ) )
                return callb
            maakcbs.append( maakcb( cb ) )
        for idx, maakcb in enumerate( maakcbs ):
            aanroep( 'maakcb' + str( idx ), maakcb )

# decorators
# eenvoudige manier om wrapper om functie te gebruiken

if 0:
    markeer( 'Aanvulling 10 B' )

    def mkwrapper( cb ):
        def wrapper():
            print( 'voor' )
            cb()
            print( 'na' )
        return wrapper

    if 0:
        def func1():
            print( 'func1' )

        func1()

        func11 = mkwrapper( func1 )
        func11()

    if 0:
        def func2():
            print( 'func2' )
        func2 = mkwrapper( func2 )
        func2()

    if 0:
        @mkwrapper
        def func3():
            print( 'func3' )

        func3()

if 0:
    markeer( 'Aanvulling 10 C' )

    if 1:
        def decorator( ):
            print( 'decorator' )

    if 0:
        def decorator( func ):
            print( 'voor' )
            ret = func()
            print( 'na' )
            return ret

    if 0:
        def decorator( func ):
            def wrapper():
                print( 'voor' )
                ret = func()
                print( 'na' )
                return ret
            return wrapper

    if 0:
        def decorator( func ):
            def wrapper( *args ): # **kwargs hier niet nodig
                print( 'voor' )
                ret = func( *args )
                print( 'na' )
                return ret
            return wrapper

    @decorator
    def aftrek( k, l ):
        return k - l
    @decorator
    def optel( a, b, c ):
        return a + b + c

    if 1:
        print( aftrek( optel( 1, 2, 3), 4 ) )

# in functools module
# update_wrapper en @wraps
# zie
# https://docs.python.org/3/library/functools.html#functools.update_wrapper
# https://docs.python.org/3/library/functools.html#functools.wraps
#   handig voor implementeren decorators
#   voor consistente __name__ en __doc__

# zelf maken van decorator wrappers komt maar weinig voor
# wel vaker gebruik van decorators
#   bij diverse modules of packages

# zoals properties
# runt lees en schrijf methods
#   bij benaderen members
# https://docs.python.org/3/library/functions.html#property

if 0:
    markeer( 'Aanvulling 10 D' )

    # zonder property
    class Test:
        def __init__( self, val ):
            self.val = val

        def getval( self ):
            print( 'val is', self.val )
            return self.val

        def setval( self, val ):
            print( 'val wordt', val )
            self.val = val

    tst = Test( 55 )
    print( tst.getval() )
    tst.setval( 66 )
    print( tst.getval() )

    tst.val = 77
    print( tst.getval() )
    print( tst.val )
    
if 0:
    markeer( 'Aanvulling 10 E' )

    # met property
    if 1:
        # fout, roept zichzelf recursief aan
        class Test:
            def __init__( self, val ):
                self.val = val

            def getval( self ):
                print( 'val is', self.val )
                return self.val

            def setval( self, val ):
                print( 'val wordt', val )
                self.val = val

            val = property( getval, setval )
    else:
        class Test:
            def __init__( self, val ):
                self._val = val

            def getval( self ):
                print( 'val is', self._val )
                return self._val

            def setval( self, val ):
                print( 'val wordt', val )
                self._val = val

            val = property( getval, setval )

    tst = Test( 55 )
    print( tst.getval() )
    tst.setval( 66 )
    print( tst.getval() )

    tst.val = 77
    print( tst.getval() )
    print( tst.val )

if 0:
    markeer( 'Aanvulling 10 F' )

    # property met decorators
    class Test:
        def __init__( self, val ):
            self._val = val

        @property
        def val( self ):
            print( 'val is', self._val )
            return self._val
        # val = property( val )

        @val.setter  # method of property
        def val( self, val ):
            print( 'val wordt', val )
            self._val = val

        if 0:
            @val.deleter  # method of property
            def val( self ):
                print( 'deleter' )
                del self._val

    tst = Test( 555 )
    print( tst.val )
    tst.val = 666
    print( tst.val )

    tst.val = 777
    print( tst.val )

    if 0:
        del tst.val
        print( tst.val )
    
# meer voorbeelden in functools
# https://docs.python.org/3/library/functools.html

# @cache, @cached_property, @lru_cache

if 0:
    markeer( 'Aanvulling 10 G' )
    
    import functools
    
    # @functools.cache  # veel sneller met cache
    def fib( n ):
        if n <= 2:
            return 1
        return fib( n-1 ) + fib( n-2 )

    print( fib( 20 ) )
    # print( fib( 38 ) )
    # print( fib( 100 ) )
    
    
# @total_ordering
# @singledispatch, singledispatchmethod

# cmp_to_key

# partial
# reduce

###############################################################
# Aanvulling 11 Interface met Excel

# Excel onderdeel Microsoft office programma's

# Microsoft 365
#  abonnement met extra online mogelijkheden
#    zoals OneDrive
#  licentie geldig zolang geabonneerd

# Office 2021, Office 2019
#   eenmalige aankoop, geen online mogelijkheden
#   licentie onbeperkt geldig
#     maar beperkte tijd updates
# Office 2024 over enkele maanden

# https://www.microsoft.com/nl-nl/microsoft-365/

# https://en.wikipedia.org/wiki/Microsoft_Office#History_of_releases

# licenties vaak goedkoper te krijgen buiten Microsoft om

# Installeren

# installatie bijvoorbeeld via Microsoft Account
# Microsoft account, inloggen login.live.com
# Services & subscriptions, install
# bijvoorbeeld Nederlands, offline installer
#   download O365HomePremRetail.img

# kan de .img mounten met Windows File Explorer
#   rechtermuisknop menu Koppelem
#   maakt nieuwe drive letter zoals Dvd-station
# dan installatie met Setup.exe
#   begint meteen met installeren
#     geen opties instellen mogelijk
#     Excel en andere programma's moeten afgesloten zijn
#       vraagt anders om afsluiten
#   duurt even

# kan maar 1 Office versie tegelijkertijd op zelfde computer

# Install and use different versions of Office on the same PC - Microsoft Support
# https://support.microsoft.com/en-us/office/install-and-use-different-versions-of-office-on-the-same-pc-6ebb44ce-18a3-43f9-a187-b78c513788bf

# Microsoft 365 and Office 2021 on Same PC - Microsoft Community
# https://answers.microsoft.com/en-us/msoffice/forum/all/microsoft-365-and-office-2021-on-same-pc/3151cf4d-6eb2-457a-a1aa-96b6cb5994d4

# Updates

# nakijken versie in Excel
# Bestand, links onder Account
# rechts Productgegevens, Info over Excel
#   na installatie Versie 2304 (build 16327.20264 Klik-en-Klaar)

# Update history for Microsoft 365 Apps (listed by date) - Office release notes | Microsoft Learn
# https://learn.microsoft.com/en-us/officeupdates/update-history-microsoft365-apps-by-date


# nog geen Python opties in standaard Excel
#   menu Formules geen Python optie
#   wel in Insider Beta versie

# Account, knop Microsoft 365 Insider
# aanzetten opties, kiezen Beta kanaal
#   moet akkoord gaan met verzamelen gegevens

# dan Account, knop Bijwerkopties, Nu bijwerken
#   duurt even

# weer nakijken versie in Excel
# Bestand, Account, Info over Excel
#   na update Versie 2406 (build 17726.20006 Klik-en-Klaar)

# Update History for Office Beta Channel - Office release notes | Microsoft Learn
# https://learn.microsoft.com/en-us/officeupdates/update-history-beta-channel

# nu wel Python opties in standaard Excel
#   menu Formules optie Python invoegen
# als wel juiste versie maar nog geen Python optie te zien
#   dan Excel afsluiten en opnieuw starten

# Get started with Python in Excel - Microsoft Support
# https://support.microsoft.com/en-us/office/get-started-with-python-in-excel-a33fbcbe-065b-41d3-82cf-23d05397f53d

# Excel beta
# menu Formules, knop Python invoegen
#   of =py( intypen in cel
# Python code uitproberen

# cel afsluiten met Ctrl+Enter
#   in sommige gevallen Ctrl+Shift+Enter nodig
#   anders lastig om cel invoeren te verlaten

# resultaat is laatste expressie
#   geen return nodig

# probeer bijvoorbeeld in een PY cel

# hex(2 ** 100)

# import sys
# sys.version

# blijkt 3.10.13 te zijn

# Python draait bij Microsoft in de cloud
#   zodat onder andere geen functies met bestanden mogelijk zijn


# boek januari 2000
# Python Programming on Windows
# https://www.oreilly.com/library/view/python-programming-on/1565926218/

# hoofdstuk 9 Integration with Excel
# https://www.oreilly.com/library/view/python-programming-on/1565926218/ch09.html

# voorbeeld code
# https://resources.oreilly.com/examples/9781565926219


# PyWin32

# https://pypi.org/project/pywin32/
# https://github.com/mhammond/pywin32

# online help via
# https://timgolden.me.uk/python/index.html
# https://timgolden.me.uk/pywin32-docs/contents.html
#   win32com maar beperkt

# pip install pywin32
#   install doen voor starten van Idle
#   anders soms foutmelding
#     ImportError: No module named pywintypes

# Release 306 van 2023-03-26
# https://stackoverflow.com/questions/4093041/programmatically-find-the-installed-version-of-pywin32

if 0:
    markeer( 'Aanvulling 11 A' )

    import win32api

    version_info = win32api.GetFileVersionInfo( win32api.__file__, '\\' )
    # print( version_info )
    fileVersionLS = version_info[ 'FileVersionLS' ]
    # print( hex( fileVersionLS ) )
    print( fileVersionLS >> 16 )

if 0:
    markeer( 'Aanvulling 11 B' )

    import win32com.client

    app = win32com.client.Dispatch( 'Excel.Application' )
    app.Visible = True
    
if 0:
    markeer( 'Aanvulling 11 C' )

    import win32com.client

    xl = win32com.client.Dispatch( 'Excel.Application' )
    xl.Visible = True

    xl.Workbooks.Add()
    xl.Cells( 1, 1 ).Value = 'Hallo Excel via Python'
    
    print( xl )
    print( repr(xl) )

    if 0:
        xl.DisplayAlerts = False
    if 0:
        xl.Quit()

# Office applicaties gebruiken OLE/COM/DCOM interface

# dynamisch en statisch

# mogelijk om COM server te maken in Python

# Office Object Model, VBA

# https://learn.microsoft.com/nl-nl/office/vba/api/overview/excel/object-model

# zeer uitgebreid, heel veel objecten
#   elk met veel methods, events en properties
#   (te) veel mogelijkheden

# wat in VBA kan is ook mogelijk met Python


# Microsoft Excel online
#   geen directe Python mogelijkheden
#     wel via REST API (?)


# xlwings

# https://www.xlwings.org/
# https://github.com/xlwings/xlwings

# open source gratis versie
# en ook dure versies voor bedrijven
#   met meer mogelijkheden

# beter gedocumenteerd dan PyWin32
# https://docs.xlwings.org/en/latest/syntax_overview.html

# pip install xlwings
# maakt gebruik van PyWin32

if 0:
    markeer( 'Aanvulling 11 D' )

    import xlwings

    xl = xlwings.App()
    xl.Visible = True
    print( xlwings.apps.active )
    
if 0:
    markeer( 'Aanvulling 11 E' )

    import xlwings

    with xlwings.App() as xl:
        xl.Visible = True
        print( xlwings.apps.active )
    # sluit Excel automatisch

    
# interface via .xlsx bestanden
# geen Excel nodig

# XlsxWriter

# https://xlsxwriter.readthedocs.io/
# https://github.com/jmcnamara/XlsxWriter

# pip install xlsxwriter

if 0:
    markeer( 'Aanvulling 11 F' )

    import xlsxwriter

    with xlsxwriter.Workbook('XlsxWriter.xlsx') as wb:
        ws = wb.add_worksheet()

        data = ( ( 'a', 11 ),
                 ( 'b', 12 ),
                 ( 'c', 13 ),
                 ( 'd', 14 ) )

        for row, (txt, val) in enumerate(data):
            ws.write(row, 0, txt)
            ws.write(row, 1, val)


# interface via .xls bestanden
# geen Excel nodig

# xlwt, xlrd

# https://xlwt.readthedocs.io/en/stable/
# https://github.com/python-excel/xlwt

# pip install xlwt

if 0:
    markeer( 'Aanvulling 11 G' )

    import xlwt

    wb = xlwt.Workbook()
    ws = wb.add_sheet( 'xlwt sheet' )

    data = ( ( 'e', 21 ),
             ( 'f', 22 ),
             ( 'g', 23 ),
             ( 'h', 24 ) )

    for row, (txt, val) in enumerate(data):
        ws.write(row, 0, txt)
        ws.write(row, 1, val)

    wb.save( 'xlwt.xls' )

# https://xlrd.readthedocs.io/en/latest/
# https://github.com/python-excel/xlrd

# pip install xlrd

if 0:
    markeer( 'Aanvulling 11 H' )

    import xlrd

    with xlrd.open_workbook( 'xlwt.xls' ) as wb:
        sh = wb.sheet_by_index( 0 )
        for row in range( sh.nrows ):
            for col in range( sh.ncols ):
                val = sh.cell_value( row, col)
                # print( row, col, val )
                print( val, end=' ')
            print()

# pyexcel
# https://www.pyexcel.org
# https://docs.pyexcel.org

# veel meer bestandsformaten
# gebruikt andere Python modules
# https://docs.pyexcel.org/en/latest/index.html#a-map-of-plugins-and-file-formats

# pip install pyexcel
# pip install pyexcel-xls
# pip install pyexcel-xlsx

if 0:
    markeer( 'Aanvulling 11 I' )

    import pyexcel

    data = ( ( 'i', '31' ),
             ( 'j', '32' ),
             ( 'k', 33 ),
             ( 'l', 34 ) )

    pyexcel.save_as( array=data, dest_file_name='pyexcel.csv' )
    if 0:
        pyexcel.save_as( array=data, dest_file_name='pyexcel.xls' )
    if 0:
        pyexcel.save_as( array=data, dest_file_name='pyexcel.xlsx' )

# meer op Python software voor excel
# https://www.excelpython.org/

# pycel
# PyExcelerate
# formulas
# pylightxl
# xlutils
# openpyxl
# nb2xls
# Koala
# pyxlsb
# XlsPandaFormatter
# xls2slsx
# vb2py
# hotxlfp
# xltable

# ook nog

# Pandas
# https://pandas.pydata.org/
# https://github.com/pandas-dev/pandas
# https://en.wikipedia.org/wiki/Pandas_(software)
# zoals
# https://pandas.pydata.org/docs/reference/api/pandas.ExcelFile.html
# gebruikt in Python for Excel
# https://support.microsoft.com/en-us/office/python-in-excel-dataframes-a10495b2-8372-4f0f-9179-32771fe0dc04


# Libre Office
# https://www.libreoffice.org
# met Python scripting
# https://help.libreoffice.org/latest/en-US/text/sbasic/python/python_programming.html

# Apache Open Office
# https://www.openoffice.org/
# met Python scripting
# https://www.openoffice.org/udk/python/scriptingframework/

###############################################################
# Aanvulling 12 Multitasking

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de multitasking directory
#     in PythonCursusXX.zip

###############################################################
# Aanvulling 13 sqlite3

# sqlite3 is ingebouwde module
# https://docs.python.org/3/library/sqlite3.html

# interface beschreven in PEP 249
# https://peps.python.org/pep-0249/

# interface naar veelgebruikte SQLite library

# https://sqlite.org
# https://en.wikipedia.org/wiki/SQLite
# https://www.sqlitetutorial.net

# embedded, werkt in eigen proces
# geen aparte database server nodig
# info bewaren op hoger niveau dan bestanden

# geschreven in C
# cross platform
# veel interfaces naar diverse programmeertalen

# relatief klein, niet veel code, weinig geheugen nodig
# minder mogelijkheden dan database server software

# public domain licentie
# gratis, wordt heel veel gebruikt

if 0:
    markeer( 'Aanvulling 13 A' )

    import sqlite3

    print( 'SQLite version',
           sqlite3.sqlite_version, sqlite3.sqlite_version_info )

    # deprecated, verdwijnt in Python 3.14
    print( 'interface version',
           sqlite3.version, sqlite3.version_info )

    print( 'DB-API level', sqlite3.apilevel )

    if 0:
        print( 'threadsafety', sqlite3.threadsafety )
        print( '1 single-thread, 2 multi-thread, 3 serialized' )
        if 0:
            sqlite3.threadsafety = 1
            print( 'threadsafety', sqlite3.threadsafety )
            sqlite3.threadsafety = 2
            print( 'threadsafety', sqlite3.threadsafety )
    
if 0:
    markeer( 'Aanvulling 13 B' )

    # zie ook Aanvulling 29 B
    # DuckDB
    
    import sqlite3
    import os

    # dbname = 'voorbeeld.sqlite'
    dbname = ':memory:'

    if 0:
        if os.path.isfile( dbname ):
            os.remove( dbname )
        # input( 'deleted?' )

    con = sqlite3.connect( dbname )
    cur = con.cursor()

    if 0:
        # https://www.sqlite.org/datatype3.html

        # dynamische types
        res = cur.execute( 'create table tabel(a, b, c)' )

        # res = cur.execute( 'Create Table tabel(a, b, c)' )

        # res = cur.execute( 'CREATE TABLE tabel(a, b, c)' )

        # res = cur.execute( 'create table if not exists tabel(a, b, c)' )

        # vaste types ook mogelijk
        # res = cur.execute( 'create table tabel(a text, b text, c text)' )
        # res = cur.execute( 'create table tabel(a text, b text, c integer)' )

        # res = cur.execute( 'create table tabel(a text, b text, c text) strict' )
        # res = cur.execute( 'create table tabel(a text, b text, c integer) strict' )
        print( 'create', res.fetchall() )

    if 0:
        if 0:
            res = cur.execute( 'insert into tabel values ("x", "y", "z")' )
        if 0:
            res = cur.execute( 'insert into tabel values '
                               '("k", "l", "m"),'
                               '("n", "o", "p")' )
        if 0:
            res = cur.execute( 'insert into tabel values '
                               '("k", "l", "m"),'
                               '(11, 12, 13),'
                               '(TRUE, FALSE, True),'
                               '(1.1, 1.2, 1.3),'
                               '(Null, NULL, null),'
                               # https://www.sqlite.org/lang_expr.html
                               # 3. Literal Values, Blob literal
                               "(x'01020304', 0, 0)" # blob, hexadecimal data
                               )

        print( 'insert', res.fetchall() )

    if 0:
        res = cur.execute( 'select * from tabel' )
        print( 'select', res.fetchall() )

    if 0:
        con.commit()

    if 0:
        con.rollback() # impliciet

    if 0:
        # schema table
        # https://www.sqlite.org/schematab.html
        res = cur.execute( 'select * from sqlite_schema' )
        print( 'schema', res.fetchall() )
        
    con.close()

if 0:
    markeer( 'Aanvulling 13 C' )

    import sqlite3
    import pprint

    dbname = 'test2.sqlite'
    # dbname = ':memory:'
    con = sqlite3.connect( dbname )
    cur = con.cursor()
    res = cur.execute( 'create table if not exists boek(jaar, auteur, titel)' )

    if 0:
        res = cur.execute( 'select * from boek' )
        print( 'boek begin', res.fetchall() )

    if 0:
        res = cur.execute( 'select * from boek' )
        print( 'boek begin pprint' )
        pprint.pprint( res.fetchall() )

    if 0:
        boek0 = ( 2023, 'Pieter Spronck', 'De Programmeursleerling'  )
        res = cur.execute( 'insert into boek values(?, ?, ?)', boek0 )

    if 0:
        boek1 = (
        { 'jaar' : 2023, 'auteur' : 'P. Spronck',
          'titel' : 'De Programmeursleerling' }
        )
        res = cur.execute( 'insert into boek values(:jaar, :auteur, :titel)',
                           boek1 )
    if 0:
        boek2 = (
            ( 2015, 'Allen Downey', 'Think Python'  ),
            ( 2023, 'Pieter Spronck', 'De Programmeursleerling'  ),
            ( 2023, 'Pieter Spronck', 'Computational Thinking with Python'  ),
            )
        # sqlite3.ProgrammingError: Error binding parameter 1: type 'tuple' is not supported
        # res = cur.execute( 'insert into boek values(?, ?, ?)', boek2 )

        res = cur.executemany( 'insert into boek values(?, ?, ?)', boek2 )

    if 0:
        boek3 = (
        { 'jaar' : 2015, 'auteur' : 'Allen Downey',
          'titel' : 'Think Python' },
        { 'jaar' : 2023, 'auteur' : 'Pieter Spronck',
          'titel' : 'Computational Thinking with Python' },
        { 'jaar' : 2023, 'auteur' : 'Pieter Spronck',
          'titel' : 'De Programmeursleerling' },
        )
        res = cur.executemany( 'insert into boek values(:jaar, :auteur, :titel)',
                           boek3 )

    if 0:
        # placeholder ? alleen met velden, niet met tabel of kolom namen
        boek2 = ( 'boek', 2023, 'Pieter Spronck', 'De Programmeursleerling'  )
        # sqlite3.OperationalError: near "?": syntax error
        res = cur.execute( 'insert into ? values(?, ?, ?)', boek2 )

    if 0:
        res = cur.execute( 'select * from boek' )
        print( 'boek na', res.fetchall() )

    if 0:
        res = cur.execute( 'select * from boek' )
        print( 'boek na met pprint' )
        pprint.pprint( res.fetchall() )

    if 0:
        res = cur.execute( 'select * from boek' )
        print( 'boek1', res.fetchone() )
        print( 'boek2', res.fetchone() )
        print( 'boek3', res.fetchone() )

        if 0:
            print( 'boekmany0', res.fetchmany() )
        if 0:
            print( 'arraysize', res.arraysize )
            res.arraysize = 2
            print( 'boekmany2', res.fetchmany() )
        if 1:
            # print( 'boekmany3', res.fetchmany( 3 ) )
            print( 'boekmany100', res.fetchmany( 100 ) )

    if 0:
        print( 'for' )
        sql = 'select * from boek'
        # sql = 'select auteur from boek'
        # sql = 'select auteur, titel from boek'
        # sql = 'select auteur, titel, jaar from boek'
        # sql = 'select auteur, titel from boek where jaar=2015'
        res = cur.execute( sql )
        for row in res:
            print( row )

    con.commit()
    con.close()


# HeidiSQL
# https://www.heidisql.com
# https://en.wikipedia.org/wiki/HeidiSQL
# geschreven in Pascal, Delphi
# open source GPLv2 licentie


# DBeaver
# https://dbeaver.io
# https://en.wikipedia.org/wiki/DBeaver
# geschreven in Java
# open source Apache licentie

# zet bij installeren Java checkbox aan
#   als JRE nog niet beschikbaar
# programma download na installatie vanzelf
#   drivers zoals voor SQLite zodra nodig

# https://en.wikipedia.org/wiki/Comparison_of_database_administration_tools

# DBeaver heeft sample SQLite database Chinook.db
# c:\Users\<User>\AppData\Roaming\DBeaverData\
#    workspace6\.metadata\sample-database-sqlite-1\Chinook.db
# copy naar huidige directory

if 0:
    markeer( 'Aanvulling 13 D' )

    import sqlite3
    import pprint

    dbname = 'Chinook.db'
    con = sqlite3.connect( dbname )
    cur = con.cursor()

    if 1:
        # schema table
        # https://www.sqlite.org/schematab.html
        res = cur.execute( 'select * from sqlite_schema' )
        print( 'schema' )
        pprint.pprint( res.fetchall() )

        res = cur.execute( 'select * from sqlite_schema' )
        with open( 'schema.txt', 'w' ) as outf:
            pprint.pprint( res.fetchall(), stream=outf )
        
    con.close()

if 0:
    markeer( 'Aanvulling 13 E' )

    import sqlite3

    dbname = ':memory:'
    con = sqlite3.connect( dbname )
    cur = con.cursor()

    # reserved words, keywords
    # https://www.sqlite.org/lang_keywords.html

    # table is keyword
    # sqlite3.OperationalError: near "table": syntax error
    cur.execute( 'create table table(a, b, c)' )
    # werkt wel goed met ""
    # cur.execute( 'create table "table"(a, b, c)' )

    cur.execute( 'insert into table values (1,2,3),(4,5,6)' ) # syntax error
    # cur.execute( 'insert into "table" values (1,2,3),(4,5,6)' )

    sql = 'select * from table' # syntax error
    # sql = 'select * from "table"'
    
    # res = cur.execute( sql )
    # for row in res:
    #     print( row )

    con.close()

if 0:
    markeer( 'Aanvulling 13 F' )

    import sqlite3

    # https://www.sqlite.org/limits.html
    # text en blob
    
    dbname = ':memory:'
    con = sqlite3.connect( dbname )
    cur = con.cursor()
    cur.execute( 'create table test (col)' )

    if 0:
        # for pwr2 in range(2, 32):
        for pwr2 in range(2, 30):
            txt = 'x' * 2 ** pwr2
            print( 'insert pwr2', pwr2, len( txt ) )
            cur.execute( 'insert into test values (?)', ( txt, ) )

    if 0:
        for pwr10 in range(2, 10):
            # size = 10 ** pwr10     # error pwr10=9
            # size = 10 ** pwr10 - 5 # error pwr10=9
            size = 10 ** pwr10 - 6 # ok pwr10=9
            txt = 'y' * size
            print( 'insert pwr10', pwr10, len( txt ) )
            cur.execute( 'insert into test values (?)', ( txt, ) )

    if 0:
        ch = chr( 8364 ) # euro '€'
        print( ch, ord( ch ), ch.encode('utf8') )
        for pwr10 in range(2, 10):
            # size = 10 ** pwr10 // 3     # error pwr10=9
            # size = 10 ** pwr10 // 3 - 1 # error pwr10=9
            size = 10 ** pwr10 // 3 - 2 # ok
            txt = ch * size
            print( 'insert pwr10', pwr10, len( txt ) )
            cur.execute( 'insert into test values (?)', ( txt, ) )

    if 0:
        # size = 10 ** 9 - 5 # error
        size = 10 ** 9 - 6 # ok
        byt = b'z' * size
        print( 'insert byt', len( byt ) )
        cur.execute( 'insert into test values (?)', ( byt, ) )
        
    # https://www.sqlite.org/datatype3.html
    # integer

    if 0:
        # for pwr2 in range(2, 70):
        for pwr2 in range(2, 64):
            # n = 2 ** pwr2     # error
            n = 2 ** pwr2 - 1 # ok
            print( 'insert int2', pwr2, n )
            cur.execute( 'insert into test values (?)', ( n, ) )
        
    res = cur.execute( 'select * from test' )
    for row in res:
        # print( row, type(row) )
        val = row[0]
        if isinstance( val, str ) or isinstance( val, bytes ):
            print( 'len', len( row[0] ), type( row[0] ) )
        else:
            print( val, type(val) )

    con.close()

if 0:
    markeer( 'Aanvulling 13 G' )

    import sqlite3

    dbname = ':memory:'
    con = sqlite3.connect( dbname )
    cur = con.cursor()
    cur.execute( 'create table test (col)' )

    # str utf8

    # count = 1000
    # count = 55_000
    # count = 100_000
    # txt = ''.join( chr( num ) for num in range( count ) )

    txt = ''.join( chr( num ) for num in range( 0x110000 ) # + 1 )
                  if not 0xd800 <= num <= 0xdfff )

    # 'utf-8' codec can't encode characters in position
    #   55296-57343: surrogates not allowed
    print( hex(55296), hex(57343) ) # 0xd800 0xdfff

    # https://docs.python.org/3/library/sqlite3.html#how-to-handle-non-utf-8-text-encodings
    # Note: The sqlite3 module API does not support strings containing surrogates.

    # https://en.wikipedia.org/wiki/Unicode#Codespace_and_code_points    

    # print( txt )

    cur.execute( 'insert into test values (?)', ( txt, ) )
    res = cur.execute( 'select * from test' )
    for row in res:
        val = row[0]
        print( len( txt ), len( val ), txt == val )

    con.close()

if 0:
    markeer( 'Aanvulling 13 H' )

    import sqlite3

    # connectie object heeft ook execute functies
    # dan zelf cursor maken niet meer nodig
    # intern nieuwe cursor gemaakt en returnt deze
    # iets minder efficiënt

    dbname = ':memory:'
    con = sqlite3.connect( dbname )
    con.execute( 'create table test (col)' )
    con.execute( 'insert into test values( 123 )' )
    for row in con.execute( 'select * from test' ):
        print( row )
    con.close()

if 0:
    markeer( 'Aanvulling 13 I' )

    import sqlite3

    # connection is een context manager
    #   auto commit wanneer geen exception
    # sluit niet automatisch de connectie
    dbname = ':memory:'

    if 0:
        con = sqlite3.connect( dbname )
        try:
            with con:
                con.execute( 'create table test(a unique)' )
                con.execute( 'insert into test values(?)', ('abc',) )
                con.execute( 'insert into test values(?)', ('def',) )
                if 0:
                    con.execute( 'insert into test values(?)', ('abc',) )
        except sqlite3.Error as ex:
            print( 'Sqlite error', ex )

        for row in con.execute( 'select * from test' ):
            print( row )
            
        if 0:
            # een cursor is een iterator
            #   is geen context manager
            # TypeError: 'sqlite3.Cursor' object does not support the context manager protocol
            with con.cursor() as cur:
                cur.execute( 'create table test (col)' )

        con.close() # nog steeds nodig

    if 1:
        # kan contextlib.closing gebruiken
        import contextlib

        print('met contextlib')
        with contextlib.closing( sqlite3.connect( dbname ) ) as con:
            with con:
                con.execute( 'create table test(a unique)' )
                con.execute( 'insert into test values(?)', ('abc',) )
                con.execute( 'insert into test values(?)', ('def',) )
            for row in con.execute( 'select * from test' ):
                print( row )
        if 0:
            # https://stackoverflow.com/questions/3687164/is-it-safe-to-close-sqlite-db-more-than-once
            con.close() # ok
            con.close() # ok
        
if 0:
    markeer( 'Aanvulling 13 J' )

    # executescript
    # returnt geen resultaat rijen
    # https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executescript

    import sqlite3

    # dbname = 'test.sqlite'
    dbname = ':memory:'

    con = sqlite3.connect( dbname )
    cur = con.cursor()

    sql = '''
        create table boek( jaar, auteur, titel );

        insert into boek values
            ( 2015, 'Allen Downey', 'Think Python' ),
            ( 2023, 'Pieter Spronck', 'De Programmeursleerling'  ),
            ( 2023, 'Pieter Spronck', 'Computational Thinking with Python'  );

        -- commentaar in SQL op 1 regel

        /* commentaar in SQL
           over meerdere regels */
           
        select * from boek;
    '''
    if 0:
        cur.executescript( sql )
    if 1:
        for row in cur.executescript( sql ):
            print( row )

    if 0:
        sql = 'select * from boek;'
        for row in cur.executescript( sql ):
            print( row )
    if 0:
        sql = 'select * from boek;'
        # sql = 'select * from boek'
        for row in cur.execute( sql ):
            print( row )

    con.close()

if 0:
    markeer( 'Aanvulling 13 K' )

    import sqlite3

    dbname = ':memory:'
    con = sqlite3.connect( dbname )
    if 1:
        con.executescript( 'create table boek( jaar, auteur, titel )' )
        sql = '''insert into boek values
            ( 2015, 'Allen Downey', 'Think Python' ),
            ( 2023, 'Pieter Spronck', 'De Programmeursleerling'  ),
            ( 2023, 'Pieter Spronck', 'Computational Thinking with Python'  )
            '''
        con.execute( sql )
    if 0:
        if 1:
            sql = '''create table boek( id primary key,
                           jaar, auteur, titel )'''
        if 0:
            sql = '''create table boek( id integer primary key,
                           jaar, auteur, titel )'''
        con.executescript( sql )
        sql = '''insert into boek values
            ( 111, 2015, 'Allen Downey', 'Think Python' ),
            ( 112, 2023, 'Pieter Spronck', 'De Programmeursleerling'  ),
            ( 113, 2023, 'Pieter Spronck', 'Computational Thinking with Python'  )
            '''
        con.execute( sql )

    for row in con.execute( 'select * from boek' ):
        print( row )
    for row in con.execute( 'select rowid from boek' ):
        print( row )
    for row in con.execute( 'select rowid, oid, _rowid_, * from boek' ):
        print( row )

    con.close()
        
if 0:
    markeer( 'Aanvulling 13 L' )

    import sqlite3

    # elke tabel heeft een primary key, een primaire sleutel
    # is meestal 1 kolom van een tabel
    #   maar kan ook meerdere kolommen bevatten

    # als de definitie van een primary key bij create table ontbreekt
    #   dan wordt hiervoor 'rowid' gemaakt en gebruikt

    # https://en.wikipedia.org/wiki/Primary_key
    # https://nl.wikipedia.org/wiki/Primaire_sleutel

    # de waarde van de primaire sleutel maakt het record uniek
    #   verschillende records met dezelfde primary key zijn niet toegestaan

    # de primaire sleutel mag niet leeg zijn
    #   null mag niet gebruikt worden
    #   niet helemaal in Sqlite

    # de primaire sleutel behoort zo min mogelijk te veranderen

    # externe waarden in een tabel kunnen wel eens veranderen
    #   bijvoorbeeld door hernoemen, rename

    # het is daarom gebruikelijk om als primaire key
    #   een geheel getal, een integer te gebruiken
    #   zoals rowid, maar beter zelf gedefiniëerd

    # vaak wordt deze kolom id genoemd of tabelnaam_id
    #   soms id_naam of pk_naam
    # zie ook DBeaver voorbeeld Chinook.db

    # als bij een insert de waarde voor de primaire key
    #   niet wordt meegegeven
    # dan wordt de primaire key waarde automatisch ingevuld
    #   meestal met de hoogste waarde in gebruik + 1

    con = sqlite3.connect( ':memory:' )
    sql = '''
        create table boek(
          -- id integer primary key,
          id integer primary key autoincrement,
          jaar, auteur, titel );

        -- insert into boek
        insert into boek ( jaar, auteur, titel )
          values
          ( 2015, 'Allen Downey', 'Think Python' ),
          ( 2023, 'Pieter Spronck', 'De Programmeursleerling'  ),
          ( 2023, 'Pieter Spronck', 'Computational Thinking with Python'  );
        '''
    con.executescript( sql )

    sql = 'select * from boek'
    # sql = 'select rowid, * from boek'
    for row in con.execute( sql ):
        print( row )

    if 0:
        # soms kan een eerder gebruikte waarde worden hergebruikt
        # dit is soms een probleem
        #  als die waarde altijd uniek moet blijven

        # autoincrement verhelpt dit
        #   dan wordt de laatst gebruikte waarde bijgehouden
        #   dit kost extra geheugen, cpu, diskruimte

        # https://www.sqlite.org/autoinc.html

        sql = '''
            delete from boek where id = 3;
            insert into boek( jaar, auteur, titel )
              values( null, null, null );
            '''
        con.executescript( sql )
        
        for row in con.execute( 'select * from boek' ):
            print( row )

        if 1:
            for row in con.execute( 'select * from sqlite_sequence' ):
                print( row )
        if 1:
            for row in con.execute( 'select * from sqlite_schema' ):
                print( row )

    con.close()

if 0:
    markeer( 'Aanvulling 13 M' )

    import sqlite3

    # dbname = 'test5.sqlite'
    dbname = ':memory:'
    con = sqlite3.connect( dbname )

    # de boek tabel bevat meerdere keren 'Pieter Spronck'
    # als deze naam hernoemd moet worden
    #   moet dit op meer plaatsen gebeuren

    sql = '''
        create table boek(
          id integer primary key,
          jaar, auteur, titel );

        insert into boek ( jaar, auteur, titel )
          values
          ( 2015, 'Allen Downey', 'Think Python' ),
          ( 2023, 'Pieter Spronck', 'De Programmeursleerling'  ),
          ( 2023, 'Pieter Spronck', 'Computational Thinking with Python'  );

        -- update boek set auteur='Spronck' where auteur='Pieter Spronck';
        '''
    con.executescript( sql )

    for row in con.execute( 'select * from boek' ):
        print( row )

    con.close()

if 0:
    markeer( 'Aanvulling 13 N' )

    import sqlite3

    # dbname = 'test5.sqlite'
    dbname = ':memory:'
    con = sqlite3.connect( dbname )

    if 0:
        # zie hieronder
        print( 'foreign keys on' )
        con.executescript( 'pragma foreign_keys=on' )

    # meestal is het beter om in zo'n situatie
    #   de tabel te splitsen

    # een waarde behoort zoveel mogelijk uniek te zijn
    # en maar op 1 plaats in de database te staan

    # als die waarde dan gewijzigd moet worden
    #   dan is dat doen op die ene plaats voldoende

    # beter om boek tabel te splitsen
    # dan komt de naam van de auteur nog maar 1 keer voor

    # zou de naam van de auteur als primary key
    #   in auteur tabel kunnen gebruiken
    # maar dan wordt die waarde toch op meer plaatsen
    #   in boek tabel gebruikt
    # en is hernoemen, rename weer op al die plaatsen nodig

    sql = '''
        create table auteur( id integer primary key, naam );

        -- insert into auteur( id, naam )
        --   values ( 1, 'Allen Downey' ), ( 2, 'Pieter Spronck' );

        insert into auteur( naam )
          values ( 'Allen Downey' ), ( 'Pieter Spronck' );

        create table boek( id integer primary key,
          jaar, titel, auteur_id );

        /* -- zie hieronder
        create table boek( id integer primary key,
          jaar, titel, auteur_id,
          foreign key ( auteur_id ) references auteur ( id )  );
        */

        insert into boek ( jaar, titel, auteur_id )
          values
          ( 2015, 'Think Python', 1 ),
          ( 2023, 'De Programmeursleerling', 2  ),
          ( 2023, 'Computational Thinking with Python', 2 );

        -- update auteur set naam='Spronck' where naam='Pieter Spronck';
        '''
    con.executescript( sql )

    if 1:
        # print( 'print 1' )
        for row in con.execute( 'select * from auteur' ):
            print( 'auteur', row )
        for row in con.execute( 'select * from boek' ):
            print( 'boek', row )
        print( )

    if 0:
        # kan id met select uit auteur tabel halen
        for row in con.execute( '''select id from auteur
                          where naam="Pieter Spronck" ''' ):
            print( row )

    if 0:
        if 0:
            # geen truncate in Sqlite
            # con.execute( 'truncate table boek' )
            con.execute( 'delete from boek' )
            if 0:
                # id met geneste select uit auteur tabel gebruiken
                sql = '''insert into boek ( jaar, titel, auteur_id )
                  values
                  ( 2015, 'Think Python',
                    ( select id from auteur where naam="Allen Downey" ) ),
                  ( 2023, 'De Programmeursleerling',
                    ( select id from auteur where naam="Pieter Spronck" ) ),
                  ( 2023, 'Computational Thinking with Python',
                    ( select id from auteur where naam="Pieter Spronck" ) );'''
                con.executescript( sql )

        if 0:
            con.execute( 'delete from auteur' )
            con.execute( 'delete from boek' )
            if 0:
                # lastrowid van cursor cur gebruiken
                #   bruikbaar bij insert en replace
                # dan ook auteur tabel opnieuw vullen
                cur = con.cursor()
                cur.execute( '''insert into auteur( naam )
                             values ( 'Allen Downey' )''' )
                downey_id = cur.lastrowid
                cur.execute( '''insert into auteur( naam )
                             values ( 'Pieter Spronck' )''' )
                spronck_id = cur.lastrowid
                cur.execute( '''insert into boek ( jaar, titel, auteur_id )
                    values
                    ( 2015, 'Think Python', ? ),
                    ( 2023, 'De Programmeursleerling', ?  ),
                    ( 2023, 'Computational Thinking with Python', ? );''',
                  ( downey_id, spronck_id, spronck_id ) )

        print( 'print 2' )
        for row in con.execute( 'select * from auteur' ):
            print( 'auteur', row )
        for row in con.execute( 'select * from boek' ):
            print( 'boek', row )

    if 0:
        # gecombineerde tabel

        # sql-89

        # alle combinaties
        sql = 'select * from auteur, boek'
        
        # combinaties met correcte ids
        # sql = '''select * from auteur, boek 
        #   where auteur.id = boek.auteur_id''' 

        # korte namen gebruiken
        # sql = '''select * from auteur as a, boek as b
        #   -- where auteur.id = boek.auteur_id
        #   where a.id = b.auteur_id'''
    
        # as weglaten
        # sql = '''select * from auteur a, boek b
        #   where a.id = b.auteur_id'''

        # zonder de ids
        # sql = '''select boek.jaar, boek.titel, auteur.naam
        #   from auteur, boek 
        #   where auteur.id = boek.auteur_id'''

        # sql-92
        # https://stackoverflow.com/questions/334201/why-isnt-sql-ansi-92-standard-better-adopted-over-ansi-89

        # zonder de ids
        # sql = '''select boek.jaar, boek.titel, auteur.naam
        #   from auteur inner join boek 
        #   on auteur.id = boek.auteur_id'''

        for row in con.execute( sql ):
            print( 'combi', row )

    if 0:
        # koppelingen tussen auteur en boek
        #   kunnen gemakkelijk stuk

        # verwijzende sleutel, vreemde sleutel, foreign key
        #   toevoegen door een id te laten verwijzen
        #   naar primaire key in andere tabel
        # dan zijn zulke fouten niet meer mogelijk

        # https://www.sqlite.org/foreignkeys.html

        # https://en.wikipedia.org/wiki/Foreign_key
        # https://nl.wikipedia.org/wiki/Vreemde_sleutel

        # hierboven bij create table boek
        # sql wijzigen, foreign key erbij

        for row in con.execute( 'pragma foreign_keys' ):
            print( 'foreign', row )
        # als 0 dan hierboven pragma foreign_keys on activeren

        # sql = 'delete from auteur where id=2'

        # sql = '''insert into boek ( jaar, titel, auteur_id )
        #          values ( 2000, 'Voorbeeld titel', 22 )'''

        # con.executescript( sql )
        # bij gebruik foreign key
        # sqlite3.IntegrityError: FOREIGN KEY constraint failed

        # anders na delete auteur 2
        #   records in boeken tabel zonder geldige auteur
        # of na insert boek 22
        #   record in boeken met auteur_id niet in auteur tabel
        print( 'print 3' )
        for row in con.execute( 'select * from auteur' ):
            print( 'auteur', row )
        for row in con.execute( 'select * from boek' ):
            print( 'boek', row )
        print()
        
        sql = '''select boek.jaar, boek.titel, auteur.naam
          from auteur inner join boek 
          on auteur.id = boek.auteur_id'''

        for row in con.execute( sql ):
            print( 'combi', row )

    con.close()

if 0:
    markeer( 'Aanvulling 13 O' )

    import sqlite3

    dbname = ':memory:'
    con = sqlite3.connect( dbname )

    # auteur boek
    #  nu nog 1 op n, 1:n relatie
    #    1 auteur schrijft 0, 1 of meer boeken, zoals Spronck

    #  een boek kan meer auteurs hebben
    #    wordt dan een n op m, n:m relatie
    #    hiervoor is een extra tabel nodig

    sql = '''
        pragma foreign_keys=on;

        create table auteur( id integer primary key, naam );

        create table boek( id integer primary key,
          jaar, titel );

        /**/
        create table boek_auteur(
          boek_id integer, auteur_id integer,
          foreign key ( boek_id ) references boek ( id ),
          foreign key ( auteur_id ) references auteur ( id ) );
        /**/

        /* -- andere manier om foreign key in te stellen
        create table boek_auteur(
          boek_id integer references boek ( id ),
          auteur_id integer references auteur ( id ) );
        */

        /* -- met primary key
        create table boek_auteur(
          boek_id integer -- not null,
          auteur_id integer -- not null,
          foreign key ( boek_id ) references boek ( id ),
          foreign key ( auteur_id ) references auteur ( id )
          -- , primary key ( boek_id, auteur_id )
          -- https://stackoverflow.com/questions/64753105/why-can-i-add-null-value-to-primary-key-in-sqlite
          );
        */
        
        insert into auteur( id, naam )
          values ( 11, 'Allen Downey' ), ( 12, 'Pieter Spronck' );

        insert into boek ( id, jaar, titel )
          values
          ( 101, 2015, 'Think Python' ),
          ( 102, 2023, 'De Programmeursleerling'  ),
          ( 103, 2023, 'Computational Thinking with Python' )
          -- , ( 104, 2028, 'Voorbeeld titel van beide samen' )
          ;

        insert into boek_auteur ( boek_id, auteur_id )
          values
          -- ( 100, 1 ), -- test foreign key
          -- ( 101, null ), -- test primary key
          ( 101, 11 ),
          ( 102, 12 ),
          ( 103, 12 )
          -- , ( 104, 11 ), ( 104, 12 ) -- meerdere
        '''
    con.executescript( sql )

    for row in con.execute( 'select * from auteur' ):
        print( 'auteur', row )
    for row in con.execute( 'select * from boek' ):
        print( 'boek', row )
    for row in con.execute( 'select * from boek_auteur' ):
        print( 'boek_auteur', row )

    sql = '''select boek.jaar, boek.titel, auteur.naam
      from auteur, boek, boek_auteur
      -- from boek, auteur, boek_auteur
      where boek_auteur.boek_id = boek.id and
            boek_auteur.auteur_id = auteur.id'''

    for row in con.execute( sql ):
        print( row )

    con.close()

    # voor meer over normaliseren zie voorbeelden in

    # https://nl.wikipedia.org/wiki/Databasenormalisatie

    # tot aan de 3e normaalvorm 3NF is meestal voldoende
    
    # https://en.wikipedia.org/wiki/Database_normalization
    # https://en.wikipedia.org/wiki/Third_normal_form

if 0:
    markeer( 'Aanvulling 13 P' )

    import sqlite3
    import datetime

    now = datetime.datetime.now


    tmbegin = now()

    dbname = ':memory:'

    # dbname = 'testindex.sqlite'
    # gebruikt ook tijdelijk testindex.sqlite-journal bestand

    con = sqlite3.connect( dbname )

    print( 'connect', dbname, now() - tmbegin )


    if 1:
        tmbegin = now()

        sql = '''
            create table
            if not exists
            getal( id integer primary key,
              dec, hex );
            '''
        con.execute( sql )

        print( 'create table', now() - tmbegin )

    if 0:
        tmbegin = now()

        sql = '''drop index
                 if exists
                 idx1'''
        con.execute( sql )

        print( 'drop index1', now() - tmbegin )
        
    if 0:
        tmbegin = now()

        sql = '''drop index
                 if exists
                 idx2'''
        con.execute( sql )

        print( 'drop index2', now() - tmbegin )

    if 0:
        tmbegin = now()

        sql = '''create index
                 if not exists
                 idx1 on getal( hex )'''
        con.execute( sql )

        print( 'create index1', now() - tmbegin )

    if 0:
        tmbegin = now()

        sql = '''create index
                 if not exists
                 idx2 on getal( id, dec, hex )'''
        con.execute( sql )

        print( 'create index2', now() - tmbegin )

    offset = 23

    limit = 20
    # limit = 100
    # limit = 1_000
    # limit = 1_000_000
    # limit = 10_000_000
    # limit = 100_000_000 # executemany 2 min 27 sec 2.78 GB zonder index

    if 0:
        tmbegin = now()

        sql = '''
            insert into getal( dec, hex )
              values ( ?, ? );
            '''

        for idx in range( limit ):
            val = idx + offset
            con.execute( sql, ( val, hex( val ) ) )

        con.commit()
        
        print( 'insert1', now() - tmbegin )

    if 0:
        tmbegin = now()

        sql = (
          'insert into getal( dec, hex ) values' +
              ', '.join('( ' + str(val) + ', ' + hex(val) + ' )'
                for val in range( offset, limit + offset) ) )

        # print( sql )
        print( len( sql ) )
        con.execute( sql )

        con.commit()

        print( 'insert2', now() - tmbegin )

    if 0:
        tmbegin = now()

        sql = '''
            insert into getal( dec, hex )
              values ( ?, ? );
            '''

        vals = ( (str(idx + offset), hex(idx + offset))
               for idx in range( limit ) )

        con.executemany( sql, vals ) # snel

        con.commit()

        print( 'insert3', now() - tmbegin )
        
    if 0:
        tmbegin = now()

        sql = 'select * from getal'

        # sql = 'select * from getal limit 10'
        # sql = 'select * from getal limit -1 offset 10'
        # sql = 'select * from getal limit 5, 3' # omgekeerde volgorde

        # count(*) doet altijd full table scan in sqlite
        #   maakt geen gebruik van index
        # https://stackoverflow.com/questions/8988915/sqlite-count-slow-on-big-tables/9338276#9338276
        # sql = 'select count(*) from getal'

        # soms wat sneller, niet instant
        # sql = 'select count(1) from getal'
        # sql = 'select count(id) from getal'
        # sql = 'select count(dec) from getal'
        # sql = 'select count(hex) from getal'

        for row in con.execute( sql ):
            print( row )
        
        if 0:
            # gebruikt index?
            sql = 'explain query plan ' + sql
            for row in con.execute( sql ):
                print( row )

        print( 'select1', now() - tmbegin )

    if 0:
        tmbegin = now()

        sql = 'select * from getal where hex=?'
        hexval = ( '0x18', )
        # hexval = ( hex( 9000000), )
        # hexval = ( 'bestaat niet', )

        for row in con.execute( sql, hexval ):
            print( row )

        print( 'select2', now() - tmbegin )

        if 0:
            # gebruikt index?
            print( 'explain' )
            sql = 'explain query plan ' + sql
            for row in con.execute( sql, hexval ):
                print( row )
            print()

    if 0:
        # index in metadata
        print( 'metadata' )
        for row in con.execute( 'select * from sqlite_schema' ):
            print( row )
        print()

    if 0:
        con2 = sqlite3.connect('backup.sqlite')

        def progress( status, remain, total):
            print( 'Backup progress', status,
                   'remain', remain, 'total', total, 'pages' )

        con.backup( con2, pages=-1,  # 1,
                    progress=progress )

        # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.backup

        con2.close()

    if 0:
        print( 'write dump.sqlite' )
        with open( 'dump.sqlite', 'w' ) as outf:
            for idx , line in enumerate( con.iterdump() ):
                print( idx, end=' ' )
                print( line, file=outf )

        # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.iterdump

    con.close()

    # meer over index
    # https://www.sqlite.org/queryplanner.html
    # https://www.sqlitetutorial.net/sqlite-index/

    # over B-tree
    # gebruikt door tabel en index
    # https://www.sqlite.org/fileformat2.html#btree
    # https://en.wikipedia.org/wiki/B-tree

if 0:
    markeer( 'Aanvulling 13 Q' )

    import json
    import sqlite3
    
    con = sqlite3.connect(':memory:')

    if 1:
        # test json support
        con.execute( 'select json(\'{"a": "b"}\')')

    if 1:
        sql = '''
            create table dier( id integer primary key, soort );
            insert into dier(soort) values
               ( "hond" ), ( "vos" ), ( "koe" ), ( "kip" );
            '''
        con.executescript( sql )

        sql = '''select * from dier'''
        for row in con.execute( sql ):
            print(row)

        zoek = 'kat', 'vos', 'hond'
        jsn = json.dumps( zoek )
        # print( 'jsn', jsn )

        if 0:
            sql = 'select value from json_each(?)'
            for row in con.execute( sql, ( jsn, ) ):
                print(row)

        if 0:
            sql = '''select id from dier where soort in
                ( select value from json_each(?) )'''
            for row in con.execute( sql, ( jsn, ) ):
                print(row)           

    con.close()

    # passing arrays
    # https://ricardoanderegg.com/posts/sqlite-list-array-parameter-query/
    
    # sqlite json extensie
    # https://www.sqlite.org/json1.html

    # meer extensies
    # https://antonz.org/sqlean/
    # https://github.com/nalgeon/sqlean/

if 0:
    markeer( 'Aanvulling 13 R' )

    import sqlite3

    if 0:
        sqlite3.enable_callback_tracebacks(True)
    
    con = sqlite3.connect(':memory:')

    # user-defined SQL function
    def func( val ):
        # print( 'func', val, type( val ) )
        # if 't' in val:
        #     raise 'Invalid pyfunc value'
        return '{{' + val + '}}'

    con.create_function( 'pyfunc', 1, func )
    # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function

    if 0:
        sql = 'select pyfunc(?)'
        for row in con.execute( sql, ( 'test', ) ):
            print(row)

    if 0:
        sql = '''
            create table tabel( id integer primary key, txt );
            insert into tabel(txt) values
               ( pyfunc("aa") ), ( pyfunc("bbb") ), ( pyfunc("c") );
            '''
        con.executescript( sql )

        for row in con.execute( 'select * from tabel' ):
            print(row)

    con.close()

    # Recipes from Python SQLite docs
    # https://rednafi.com/python/recipes_from_python_sqlite_docs/


# volgorde bepaald door gebruiker is lastig
# User-defined Order in SQL
# https://begriffs.com/posts/2018-03-20-user-defined-order.html
# https://news.ycombinator.com/item?id=16635440
# The surprisingly difficult problem of user-defined order in SQL | Hacker News
# https://news.ycombinator.com/item?id=25797674

# Return a new string that sorts between two given strings
# https://stackoverflow.com/questions/38923376/return-a-new-string-that-sorts-between-two-given-strings

# Models for hierarchical data | PPT
# https://www.slideshare.net/slideshow/models-for-hierarchical-data/4179181


# SQL As Understood By SQLite
# https://www.sqlite.org/lang.html
# wel DDL en DML, geen DCL

# SQL
# https://en.wikipedia.org/wiki/Structured_Query_Language
# DDL: create, alter, drop
# https://en.wikipedia.org/wiki/Data_definition_language
# DML: select, insert, update, delete
# https://en.wikipedia.org/wiki/Data_manipulation_language
# DCL
# https://en.wikipedia.org/wiki/Data_Control_Language

# command-line shell
# https://www.sqlite.org/cli.html


# ACID: atomicity, consistency, isolation, durability
# https://en.wikipedia.org/wiki/ACID

# Entity Relation (ER) diagrams
# https://en.wikipedia.org/wiki/Entity-relationship_model

# met DBeaver

# of met eralchemy2 (Python) en Graphviz
# https://github.com/maurerle/eralchemy2/
# via
# https://stackoverflow.com/questions/54393979/python-sqlite3-is-there-a-way-to-make-database-schema-figure
# pip install eralchemy2
# heeft GraphWiz nodig
# https://www.graphviz.org/download/

# ORM
# https://en.wikipedia.org/wiki/Object-relational_mapping
# SqlAlchemy
# https://docs.sqlalchemy.org/en/20/tutorial/index.html

# NoSQL
# https://en.wikipedia.org/wiki/NoSQL
# Redis, Valkey
# https://en.wikipedia.org/wiki/Redis
# https://en.wikipedia.org/wiki/Valkey

###############################################################
# Aanvulling 14 MicroPython

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_micropython directory
#     in PythonCursusXX.zip

# de MicroPython code werkt niet in IDLE
#   omdat er modules worden gebruikt
#     zoals hardware en esp32
#   die niet beschikbaar zijn voor CPython

###############################################################
# Aanvulling 15 Requests

# HTTP for Humans
# https://requests.readthedocs.io/en/latest/

# HTTP client library for Python
# https://en.wikipedia.org/wiki/Requests_(software)

# Real Python Python's Requests Library (Guide)
# https://realpython.com/python-requests/

# auteur Kenneth Reitz
# https://kennethreitz.org/

# overgenomen door Python Software Foundation (PSF)
# requests is toch geen Python standaard module

# geen support voor async

# gebruik virtuele environment
# onder Windows:
# > py -m venv testvenv
# > testvenv\scripts\activate
# (testvenv) > pip install requests

# requests gebruikt urrlib3

if 0:
    markeer( 'Aanvulling 15 A' )

    import requests

    print( requests.__version__ )

if 0:
    markeer( 'Aanvulling 15 B' )

    import requests

    # HTTP (Hypertext Transfer Protocol)
    # https://en.wikipedia.org/wiki/HTTP

    url = 'http://programmeren.hcc.nl'
    # url = 'https://programmeren.hcc.nl'

    resp = requests.get( url )
    print( resp )

    # https://requests.readthedocs.io/en/latest/api/#requests.Response

    if 0:
        # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes

        print( resp.status_code )
        print( resp.reason )

        if resp.status_code == 200:
        # if resp.status_code == requests.codes.ok:
            print( 'status ok' )
        else:
            print( 'status', resp.status_code )

        if resp: # < 400
        # if resp.ok: # < 400
            print( 'response ok' )
        else:
            print( 'response fail' )

    if 0:
        # https://requests.readthedocs.io/en/latest/api/#status-code-lookup
        print( requests.codes.ok )      # 200
        print( requests.codes.OK )      # 200
        print( requests.codes[ 'OK' ] ) # 200
        print( requests.codes[ 'Ok' ] ) # None
        print( requests.codes.bad )     # 400
        print( requests.codes.teapot )  # 418
        print( requests.codes[ 'teapot' ] ) # 418
        
    if 0:
        # https://stackoverflow.com/questions/24718557/get-the-description-of-a-status-code-in-python-requests        print( requests.codes )

        # werken niet
        print( list( requests.codes ) ) 
        print( dict( requests.codes ) )
        for code in requests.codes:
            print( code )

        # implementatie, niet gedocumenteerd
        # print( requests.status_codes )  # module
        # print( requests.status_codes._codes ) # dict

    if 0:
        print( len( resp.content ), type( resp.content ) )
        # print( resp.content )
        print( resp.content[ :256 ] )

    if 0:
        print( resp.encoding )
        print( len( resp.text ), type( resp.text ) )
        print( resp.text ) 
        print( resp.text[ :256 ] ) 

    if 0:
        # print( resp.headers )

        for key, val in resp.headers.items():
            print( key, '=>', val)

        # case-insensitive
        # https://requests.readthedocs.io/en/latest/user/quickstart/#response-headers
        # print( resp.headers[ 'content-type' ] )
        # print( resp.headers[ 'ConteNT-Type' ] )

    if 0:
        print( url )
        print( resp.url ) # http -> https

        # https://requests.readthedocs.io/en/latest/user/quickstart/#redirection-and-history
        print( resp.history )
        if len( resp.history ):
            print( resp.history[0].url )

if 0:
    markeer( 'Aanvulling 15 C' )

    import requests

    # https://httpbin.org/
    # site voor debuggen en testen
    # gebruikt in Requests documentatie

    # beknopte documentatie
    # https://httpbin.org/legacy

    # test httpbin.org ook in browser

    # beschikbaar als module
    # voor eigen httpbin server
    # https://pypi.org/project/httpbin/

    url = 'https://httpbin.org/status/418'
    resp = requests.get( url )

    print( resp )

    for key, val in resp.headers.items():
        print( key, '=>', val )
    
    print( resp.text )

if 0:
    markeer( 'Aanvulling 15 D' )

    import requests

    url = 'https://httpbin.org/ip'
    resp = requests.get( url )

    print( resp )

    for key, val in resp.headers.items():
        print( key, '=>', val )
    
    print( resp.text )
            
if 0:
    markeer( 'Aanvulling 15 E' )

    import requests

    print( 'request header fields' )
    url = 'https://httpbin.org/headers'

    if 1:
        resp = requests.get( url )
    else:
        # https://en.wikipedia.org/wiki/User-Agent_header
        headers = { 'User-Agent' : 'Voorbeeld/1.0' }
        resp = requests.get( url, headers=headers )

    print( resp )

   
    print( 'response header fields' )
    for key, val in resp.headers.items():
        print( key, '=>', val )
    
    print( resp.text )

if 0:
    markeer( 'Aanvulling 15 F' )

    import requests

    url = 'https://httpbin.org/get'

    parms = { 'key1' : 'valA',
              'key2' : 'valB',
              'key3' : None }
    resp = requests.get( url, params=parms )

    print( resp )
    print( resp.url )
    print( resp.text )

if 0:
    markeer( 'Aanvulling 15 G' )

    import requests

    url = 'https://httpbin.org/put'

    parms = { 'key11' : 'valAA',
              'key22' : 'valBB',
              'key33' : None }
    resp = requests.put( url, params=parms )

    print( resp )
    print( resp.url )
    print( resp.text )

if 0:
    markeer( 'Aanvulling 15 H' )

    import requests

    url = 'https://httpbin.org/post'

    parms = { 'key111' : 'valAAA',
              'key222' : 'valBBB',
              'key333' : None }
    resp = requests.post( url, params=parms )

    print( resp )
    print( resp.url )
    print( resp.text )

if 0:
    markeer( 'Aanvulling 15 I' )

    import requests

    # url = 'https://httpbin.org/get'
    url = 'http://programmeren.hcc.nl'

    # https://requests.readthedocs.io/en/latest/user/quickstart/#timeouts
    timeout = 0.01
    # timeout = 1
    # timeout = 20
    resp = requests.get( url, timeout=timeout )

    print( resp )

    print( resp.text )

if 0:
    markeer( 'Aanvulling 15 J' )

    import requests

    url = 'https://httpbin.org/links/12'
    # url = 'https://programmeren.hcc.nl'
    resp = requests.get( url )

    print( resp.text  )

    if 0:
        # https://docs.python.org/3/library/html.parser.html#example-html-parser-application
        import html.parser

        class TestParser( html.parser.HTMLParser ):
            def __init__( self ):
                super().__init__()
                self.links = [ ]
            def handle_starttag(self, tag, attrs):
                # print("Encountered a start tag:", tag)
                if tag == 'a':
                    print( 'attrs', attrs )
                    for attr, val in attrs:
                        if attr == 'href':
                            # print( 'link', val )
                            self.links.append( val )

            def handle_endtag(self, tag):
                pass
                # print("Encountered an end tag :", tag)

            def handle_data(self, data):
                pass
                # print("Encountered some data  :", data)

        parser = TestParser()
        parser.feed( resp.text )
        for link in parser.links:
            print( link )

if 0:
    markeer( 'Aanvulling 15 K' )

    # Requests-HTML: HTML Parsing for Humans
    # html.python-requests.org
    #   redirects naar
    # https://requests-html.kennethreitz.org/

    # https://github.com/kennethreitz/requests-html
    #   forked from
    # https://github.com/psf/requests-html

    # voor HTML parsing
    # (testvenv) > pip install requests-html

    # requests-html gebruikt Beautiful Soup

    import requests_html

    # na foutmelding
    # (testvenv) > pip install lxml_html_clean

    # print( requests_html.__version__ ) # error

    print( dir( requests_html ) )

if 0:
    markeer( 'Aanvulling 15 L' )

    import requests_html

    # url = 'https://httpbin.org/links/12'
    # url = 'https://programmeren.hcc.nl'

    sess = requests_html.HTMLSession()
    resp = sess.get( url )

    print( resp.html )

    # print( resp.html.links )
    # for link in resp.html.links:
    #     print( link )

    # print( resp.html.absolute_links )
    # for link in resp.html.absolute_links:
    #     print( link )

# andere HTML parsers

# Beautiful Soup
# Parse and process real-web HTML without the pain
# https://www.crummy.com/software/BeautifulSoup/bs4/doc/
# https://en.wikipedia.org/wiki/Beautiful_Soup_(HTML_parser)

# lxml
# XML and HTML with Python
# https://lxml.de/

if 0:
    markeer( 'Aanvulling 15 M' )

    import requests

    # https://www.daggegevens.knmi.nl/klimatologie/daggegevens
    # https://www.knmi.nl/kennis-en-datacentrum/achtergrond/data-ophalen-vanuit-een-script

    # url = 'https://httpbin.org/post'
    url = 'https://www.daggegevens.knmi.nl/klimatologie/daggegevens'

    data = { 'start' : '20240710', 'end' : '20240711',
             'stns' : '260', # De Bilt
             # 'fmt' : 'xml', # default csv met uitleg
             'fmt' : 'json',
             # 'vars' : 'temp' # error
             'vars' : 'TEMP'
             }
    resp = requests.post( url, data )
    print( resp )
    print( resp.text )
    # print( resp.json() )

if 0:
    markeer( 'Aanvulling 15 N' )

    import requests

    # online API
    # KNMI Developer Portal
    # https://developer.dataplatform.knmi.nl/apis

    # ook info over .nc bestanden
    # https://developer.dataplatform.knmi.nl/faq

    # datasets
    # https://dataplatform.knmi.nl/

    # CC BY 4.0 licentie voor open data
    # https://english.knmidata.nl/open-data
    # https://creativecommons.org/licenses/by/4.0/legalcode

    # beschikbaar onder open data policy
    # https://data.overheid.nl/

    # open data api
    # https://developer.dataplatform.knmi.nl/open-data-api
    #   Obtaining an API token
    #     gebruik waarde bij Anonymous key
    #     key geldig tot ..
    #     following key is available til ..
    #   Met voorbeeld code waarin gebruik requests module zoals
    #     Python example: Listing the first 10 files of today and retrieving the first one
    #     onderstaande code werkt op dezelfde manier
    # key variable vullen met Anonymous key
    #   voor gebruik handmatig overnemen van open data api webpagina
    key = ''
    if key == '':
        print( 'Vul key met geldige waarde, overnemen van webpagina' )
    else:
        import requests

        headers = { 'authorization' : key }

        url = ( 'https://api.dataplatform.knmi.nl/open-data/v1'
          '/datasets/Actuele10mindataKNMIstations/versions/2/files' )

        resp = requests.get( url, headers=headers )
        json = resp.json()
        files = json.get( 'files' )
        if not files:
            print( json )
        else:
            # print( json )
            file = files[ 0 ]
            filename = file[ 'filename' ]
            size = file[ 'size' ]
            
            url += '/' + filename + '/url'
            resp = requests.get( url, headers=headers )
            json = resp.json()

            tempurl = json.get( 'temporaryDownloadUrl' )
            if not tempurl:
                print( json )
            else:
                # print( json )
                resp = requests.get( tempurl )        
                print( 'size', size, 'download', len( resp.content ) )
                with open( filename, 'wb' ) as outf:
                    outf.write( resp.content )
                print( 'file', filename )

# cookies
# https://requests.readthedocs.io/en/latest/user/quickstart/#cookies

# authentication, passwords
# https://requests.readthedocs.io/en/latest/user/authentication/


# Recommended Packages and Extensions
# https://requests.readthedocs.io/en/latest/community/recommended/

# certifi module
# verzameling root certificaten
# voor valideren TLS/SSL hosts
# https://github.com/certifi/python-certifi

# requests.toolbelt module
# https://toolbelt.readthedocs.io


# Webbrowsers developer tools (F12)
# netwerk tab: headers en meer bekijken

###############################################################
# Aanvulling 16 NumPy

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_numpy directory
#     in PythonCursusXX.zip

# NumPy website
# https://numpy.org/

# Download NumPy documentatie
# https://numpy.org/doc/

# nog geen PDF bij nieuwste versies
# laatste PDF NumPy User Guide 1.23
# https://numpy.org/doc/1.23/numpy-user.pdf

###############################################################
# Aanvulling 17 INI, TOML, JSON, XML en YAML

# bestandsformaten met structuur

# string integer float bool
# list dict
# tree

# voor bewaren informatie
#   zolang een programma niet actief is
# instellingen, settings

# voor doorgeven informatie
#   van gebruiker naar programma of terug
# bij te veel voor command line of op scherm

# voor uitwisselen informatie
#   binnen en tussen programma's
#   lokaal of via netwerk

# tekst bestanden, dus niet binair
#   zijn meestal redelijk goed leesbaar
#   en te wijzigen met een editor

# enkele eerder kort besproken in boek
# Hoofdstuk 26 Bestandsformaten
# 26.3 JavaScript Object Notation (JSON)
# 26.4 HTML en XML

# INI

# https://en.wikipedia.org/wiki/INI_file
# https://nl.wikipedia.org/wiki/INI

# wordt vaak gebruikt in Windows
#   voor instellingen settings
# vanwege standaard C functies
#  in Microsoft Windows API Win32
#   GetProfileString, GetProfileInt
#     op win.ini
#   GetPrivateProfileString, GetPrivateProfileInt
#     met eigen filename

# er is geen ini standaard
#   er bestaan diverse varianten

# standaard Python module configparser
# https://docs.python.org/3/library/configparser.html

if 0:
    markeer( 'Aanvulling 17 A' )

    import configparser

    cfg = configparser.ConfigParser()

    cfg[ 'kleuren' ] = { 'edit' : 'rood', 'menu' : 'grijs' }

    if 1:
        cfg[ 'getallen' ] = { 'val' : 5, 'waarde' : 700.5 }
        # cfg[ 'getallen' ] = { 'val' : 5, 'waarde' : '700.5' }
        # cfg[ 'getallen' ] = { 'val' : 5, 'waarde' : '700' }

    if 0:
        cfg[ 'vlaggen' ] = { 'switch' : False, 'aanuit' : True }
        # cfg[ 'vlaggen' ] = { 'switch' : 'ja', 'aanuit' : True } # Error
        # cfg[ 'vlaggen' ] = { 'switch' : 'YeS', 'aanuit' : True }

    if 0:
        cfg[ 'DEFAULT' ] = { 'menu' : 'paars' }

    if 0:
        import sys

        cfg.write( sys.stdout )

    if 0:
        print( 'write test1.ini' )
        # cfg.write( 'test1.ini' ) # geen bestandsnaam
        with open( 'test1.ini', 'w' ) as inif:
            cfg.write( inif )

if 0:
    markeer( 'Aanvulling 17 B' )

    import configparser

    cfg = configparser.ConfigParser()
    print( cfg )
    print( cfg.sections() )

    if 0:
        cfg.read( 'test1.ini' )
        print( cfg.sections() )

    if 0:
        print( cfg[ 'kleuren' ] )
        print( cfg[ 'kleuren' ][ 'edit' ] )

        # section is case sensitive
        # print( cfg[ 'Kleuren' ] ) # KeyError

        # kan gewijzigd worden
        # https://stackoverflow.com/questions/49755480/case-insensitive-sections-in-configparser

        # key is case insensitive
        # print( cfg[ 'kleuren' ][ 'Edit' ] )

    if 0:
        getallen = cfg[ 'getallen' ]
        print( getallen )
        for getal in getallen:
            print( getal, getallen[ getal ], type( getal ) )

        # for getal, waarde in getallen.items():
        #     print( getal, waarde )

        # met controle
        print( cfg.getfloat( 'getallen', 'waarde' ) )
        # print( cfg.getint( 'getallen', 'waarde' ) ) # ValueError

    if 0:
        flags = cfg[ 'vlaggen' ]
        print( flags )

        print( 'vlaggen switch 11', flags[ 'switch' ] )
        # print( 'vlaggen switch 22', flags[ 'nietaanwezig' ] ) # KeyError
        # print( 'vlaggen switch 22', flags.get( 'nietaanwezig' ) )

    if 0:
        # extra controle

        # alleen toegestaan, case insensitive
        # '1', 'yes', 'true', 'on' => True
        # '0', 'no', 'false', 'off'

        print( 'vlaggen switch a', cfg.getboolean( 'vlaggen', 'switch' ) )

        # print( 'vlaggen switch b', cfg.getboolean( 'getallen', 'val' ) ) # Not a boolean

    if 0:
        # alle waarden worden opgeslagen als string

        # flags[ 'switch' ] = 1 # Option values must be strings
        flags[ 'switch' ] = '0'

    if 0:
        for sleutel, waarde in cfg[ 'DEFAULT' ].items():
            print( 'DEFAULT', sleutel, waarde )

        print( 'default', cfg.defaults() )

        print( cfg[ 'kleuren' ][ 'menu' ] )
        
        # bestaat niet, gebruikt default
        print( cfg[ 'vlaggen' ].get( 'menu' ) )
        # print( cfg[ 'vlaggen' ][ 'menu' ] )
        
    if 0:
        print( 'write test1.ini' )
        with open( 'test1.ini', 'w' ) as inif:
            cfg.write( inif )


# TOML - Tom's Obvious, Minimal Language
# https://toml.io
# https://en.wikipedia.org/wiki/TOML

# kan beschouwd worden als opvolger van .ini
# is wel gestandaardiseerd

# niet goed uitwisselbaar met INI / ConfigParser
#   omdat quotes nodig voor string waarden

# standaard Python module tomllib
# https://docs.python.org/3/library/tomllib.html
# alleen TOML lezen

if 0:
    markeer( 'Aanvulling 17 C' )

    import tomllib

    toml = '''
[kleuren]
menu = 'blauw'
'''
    tml = tomllib.loads( toml )
    print( tml )
    
if 0:
    markeer( 'Aanvulling 17 D' )

    import tomllib

    if 1:
        # TOMLDecodeError
        with open( 'test1.ini', 'rb' ) as inif:
            tml = tomllib.load( inif )

    if 0:
        ini1 = '''[kleuren]
edit = rood
menu = grijs
'''

        ini2 = '''[kleuren]
edit = "rood"
menu = "grijs"
        '''

        ini3 = '''[kleuren]
        edit = "rood"
        menu = "grijs"
        '''

        ini = ini1 # TOMLDecodeError
        # ini = ini2 # ok
        # ini = ini3 # ok

        tml = tomllib.loads( ini )

    print( tml )
    

# JSON
# https://www.json.org
# https://en.wikipedia.org/wiki/JSON
# https://nl.wikipedia.org/wiki/JSON

# standaard Python module json
# https://docs.python.org/3/library/json.html
# ook json.tool module command line interface
# https://docs.python.org/3/library/json.html#module-json.tool

if 0:
    markeer( 'Aanvulling 17 E' )

    import json

    jsn = '{ "kleuren" : [ { "menu" : "blauw" } ] }'
    # jsn = '{ "kleuren" : [ { "menu" : "blauw" } ]'
    print( json.dumps( jsn ) )
    
# XML
# https://www.w3.org/XML/
# https://en.wikipedia.org/wiki/XML
# https://nl.wikipedia.org/wiki/Extensible_Markup_Language

# standaard Python modules
# xml
#   xml.dom, xml.dom.minidom, xml.dom.pulldom
#   xml.etree.ElementInclude, xml.etree.ElementTree
#   xml.parsers.expat, xml.parsers.expat.errors,
#     xms.parsers.expat.model
#   xml.sax, xml.sax.handler, xml.sax.saxutils
# xmlrpc
#   xmlrpc.client, xmlrpc.server
# https://docs.python.org/3/library/xml.html

# XML lijkt op HTML maar is stricter

# was een tijd lang de belangrijkste manier
#   voor data uitwisselen
# werd gebruikt bij Ajax: Asynchronous JavaScript and XML
# https://en.wikipedia.org/wiki/Ajax_(programming)

# tegenwoordig wordt meer JSON gebruikt in plaats van XML
# XML is onveilig, lastiger te debuggen
# JSON kan veel minder

# discussie JSON vs. XML
# https://corecursive.com/json-vs-xml-douglas-crockford/
# https://news.ycombinator.com/item?id=35467711

if 0:
    markeer( 'Aanvulling 17 F' )

    import xml.etree.ElementTree

    xm = '''<?xml version="1.0"?>
<kleuren>
  <menu>blauw</menu>
</kleuren>'''

    root = xml.etree.ElementTree.fromstring( xm )
    print( root )
    print( root.tag, root.attrib, root.text )
    for child in root:
        print( child, child.tag, child.attrib, child.text )

    if 0:
        root.attrib[ 'attr' ] = 'test'
        root.text = 'tekst'
        
        print( root.tag, root.attrib, root.text )

    if 0:
        # menu = root[ 'menu' ] # TypeError element indices must be integers
        menu = root[ 0 ]
        print( menu )
        print( menu.tag, menu.attrib, menu.text )

        if 0:
            menu.tag = 'kaart'
            menu.attrib[ 'mattr' ] = 'mattrval'
            menu.text = 'geel'

            print( menu )
            print( menu.tag, menu.attrib, menu.text )

    if 0:
        child2 = xml.etree.ElementTree.Element( 'elm2' )
        root.append( child2 )
        
    if 0:
        tree = xml.etree.ElementTree.ElementTree( root )
        tree.write( 'test2.xml' )

    if 0:
        print()
        print( 'test2.xml' )
        with open( 'test2.xml' ) as xmlf:
            print( xmlf.read() )

# XML DOM
# https://docs.python.org/3/library/xml.dom.html
# volgens standaard, zonder reader/parser/builder
#   om XML in te lezen naar DOM

# Minimal DOM implementation
#   met parser
# https://docs.python.org/3/library/xml.dom.minidom.html

if 0:
    markeer( 'Aanvulling 17 G' )

    # types
    #            Node              base
    #      /------^---------\
    #      |      |         |      derived
    #      |     Document  Element  
    #      |
    #    /-^--\-----\-------------\-----\
    #    |    |     |             |     |   
    #    |   Attr  DocumentType  Text  CDATASection
    #    |
    #  /-^------\
    #  |        |
    # Comment  ProcessingInstruction

    # NodeList
    # NamedNodeMap   attribute list

    # DOMImplementation
    # DocumentImplementation
    # exception classes

    import xml.dom.minidom as dom

    xm1 = '''<?xml version="1.0"?>
<kleuren>
  <menu>blauw</menu>
</kleuren>'''

    xm2 = '''<?xml version="1.0"?>
<elm1>
  text1
  <elm2>text2</elm2>
  <!-- cmt1 -->
  text3
  <elm3 attr1="val1" />
</elm1>'''

    # https://en.wikipedia.org/wiki/Document_Type_Definition
    xm3 = '''<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>Inhoud</body>
</html>
'''

    xm = xm1
    # xm = xm2
    # xm = xm3

    print( xm )
    print()

    doc = dom.parseString( xm )

    def printnode( node, level ):
        def pr( *args ):
            print( ' ' * level, *args )
        pr( node )
        # pr( type( node ) )
        # pr( node.nodeType )

        if node.attributes: # NamedNodeMap
            for idx in range( node.attributes.length ):
                printnode( node.attributes.item( idx ), level + 1 )

        if isinstance( node, dom.Attr ):
            pr( node.name, node.value )

        if isinstance( node, dom.Document ):
            if node.doctype:
                printnode( node.doctype, level + 1 )
            printnode( node.documentElement, level + 1 )

        if isinstance( node, dom.DocumentType ):
            pr( node.name )
            pr( node.publicId )
            pr( node.systemId )

        if isinstance( node, dom.Text ):
            pr( 'text:', node.data )

        if isinstance( node, dom.Element ):
            pr( node.tagName )

            if 0:
                for child in node.childNodes: # NodeList
                    printnode( child, level + 1 )

            if 1:
                child = node.firstChild
                while child:
                    printnode( child, level + 1 )
                    child = child.nextSibling
                
    printnode( doc, 0 )

    # print( doc.getElementsByTagName( 'menu' ) )
    # print()


# XPath support
# https://docs.python.org/3/library/xml.etree.elementtree.html#xpath-support

# XIncludes support
# https://docs.python.org/3/library/xml.etree.elementtree.html#xinclude-support

# SAX: Simple API for XML
# https://docs.python.org/3/library/xml.sax.html

# Python XML modules zijn niet veilig, not secure
# https://docs.python.org/3/library/xml.html#xml-vulnerabilities
# beter defusedxml gebruiken
# https://docs.python.org/3/library/xml.html#the-defusedxml-package
# https://pypi.org/project/defusedxml/
# https://github.com/tiran/defusedxml

# lxml
# XML and HTML with Python
# https://lxml.de/


# YAML
# https://yaml.org
# https://en.wikipedia.org/wiki/YAML

# geen standaard Python module
# https://pyyaml.org
#   meest recente versie 0.2.5 van 2020-06-01
# https://pypi.org/project/ruamel.yaml/
# https://yaml.readthedocs.io/en/latest/
#   laatste versie 0.18.6 van 2024-02-07

# pip install ruamel.yaml

# tutorial met voorbeelden
# https://www.cloudbees.com/blog/yaml-tutorial-everything-you-need-get-started

# YAML wordt gebruikt bij diverse Cloud tools
# zoals Ansible
#   https://www.ansible.com/
#   https://en.wikipedia.org/wiki/Ansible_(software)
# of GitHub Actions
#   https://docs.github.com/en/actions/quickstart

if 0:
    markeer( 'Aanvulling 17 H' )

    import ruamel.yaml

    yaml = ruamel.yaml.YAML()
    print( yaml )

    # YAML is compatibel met JSON
    jsn = '{ "kleuren" : [ { "menu" : "blauw" } ] }'
    jsn2 = '''{
"kleuren" :
  [
    {
      "menu" :
        "blauw"
    }
  ]
}
'''
    yml = yaml.load( jsn )
    print( 'jsn', yml )

    lst = yml[ 'kleuren' ]
    print( lst )
    dct = lst[0]
    print( dct[ 'menu' ] )

if 0:
    markeer( 'Aanvulling 17 I' )

    import ruamel.yaml

    yaml = ruamel.yaml.YAML()

    ym = '''
- kleuren:
  - menu:
      blauw
  - blauw:
    # - blauw
  - 100:
  # honderd
'''
    yml = yaml.load( ym )
    print( 'yml', yml )

# discussie XML is better than YAML
# https://changelog.com/posts/xml-better-than-yaml
# https://news.ycombinator.com/item?id=37586580

# vergelijking
#    complexiteit    std Python  foutgevoelig
# INI      -         +            -
# TOML     +-        +-           ?
# JSON     -         +            ?
# XML      ++        +            +
# YAML     +         -            ?

if 0:
    markeer( 'Aanvulling 17 J' )

    # smartmontools
    # https://www.smartmontools.org/
    # https://en.wikipedia.org/wiki/Smartmontools

    # smartctl JSON/YAML/GREP output
    # https://www.smartmontools.org/wiki/TocDoc#Manpages
    # https://manpages.debian.org/unstable/smartmontools/smartctl.8.en.html
    # https://www.smartmontools.org/ticket/766
    
    # zie ook Aanvulling 18 I

    # run handmatig:
    # smartctl --scan
    # smartctl --all --json /dev/sdb >smartctl.json

    # eventueel ter vergelijking
    # smartctl --all --json=y /dev/sdb >smartctl.yaml
    # smartctl --all --json=g /dev/sdb >smartctl.grep

    # handmatig nakijken
    # open .json met editor, notepad of webbrowser

    import json

    smartjson = 'smartctl.json'
    with open( smartjson ) as jsonf:
        jsn = json.load( jsonf )

    print( type( jsn ) ) # dict

    if 0:
        for key in jsn:
            print( key )

    if 0:
        # smart tabel zoeken
        attrs = jsn[ 'ata_smart_attributes' ]

        if 0:
            for key in attrs:
                print( key )

        if 0:
            table = attrs[ 'table' ]

            print( type( table ) ) # list

            if 0:
                for entry in table:
                # for entry in table[ : 3]:
                # for entry in table[ : 1]:
                    if 1:
                        print( len( entry ) )
                        print( entry )
                        print()

                    if 0:
                        id          = entry[ 'id' ]
                        name        = entry[ 'name' ]
                        value       = entry[ 'value' ]
                        worst       = entry[ 'worst' ]
                        thresh      = entry[ 'thresh' ]
                        when_failed = entry[ 'when_failed' ]
                        flags       = entry[ 'flags' ]
                        raw         = entry[ 'raw' ]

                        name = name.replace( '_', ' ' )

                        rawvalue  = raw[ 'value' ]
                        rawstring = raw[ 'string' ]

                        # niet gebruikt: when_failed, flags, rawstring

                        print( f'{id:02x} {name:23} {value:3} {worst:3} '
                               f'{thresh:3} {rawvalue:012x}' )


###############################################################
# Aanvulling 18 Systeembeheer

if 0:
    markeer( 'Aanvulling 18 A' )

    # zie ook Opgave 24.1 X

    import subprocess
    import sys

    # run programma

    def Run( cmd ):
        print( 'Run', cmd )

        proc = subprocess.run( cmd )

        retcode = proc.returncode
        print( 'returncode', retcode )
        print()
        
    # Run( '' ) # OSError

    # Run( 'notepad.exe' )
    # Run( 'notepad.exe test.txt' )

    # geforceerd afsluiten X rechtsboven geeft
    # returncode 3221225786 = 0xc000013a = STATUS_CONTROL_C_EXIT
    # bij python.exe en cmd.exe
    
    # Run( r'c:\Program Files\Python311\python.exe' ) # ok?!
    # Run( r'c:\Program Files\Python311\pythonw.exe' ) # ok?!

    # Run( sys.executable ) # OSError, bevat spatie
    # Run( '"' + sys.executable + '"' ) # ok
    # Run( [ sys.executable ] ) # ok, gebruik bij voorkeur list

    # Run( [ 'cmd.exe' ] )
    # Run( [ 'cmd.exe', '/?' ] )
    # Run( [ 'cmd.exe', '/c', 'dir' ] )
    # Run( [ 'cmd.exe', '/c', 'pause' ] ) # wacht

    if 0:
        bat = 'test.bat'
        # battxt = 'echo Hello from test.bat'
        # battxt = 'echo Hello from test.bat\npause'
        battxt = '@echo Hello from test.bat\n@pause'

        with open( bat, 'w' ) as batf:
            batf.write( battxt )
        Run( bat )

if 0:
    markeer( 'Aanvulling 18 B' )

    import subprocess

    def RunShell( cmd ):
        print( 'RunShell', cmd )

        if 0:
            proc = subprocess.run( cmd, shell=True )
        else:
            # zichtbaar maken lukt niet
            info = subprocess.STARTUPINFO()
            info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            info.wShowWindow = 1 # subprocess.SW_NORMAL
            proc = subprocess.run( cmd, shell=True, startupinfo=info)

        retcode = proc.returncode
        print( 'returncode', retcode )
        print()

    # RunShell( '' )

    # RunShell( 'dir' )

    # RunShell( 'pause' ) # met taakbeheer process stoppen

    # window niet zichtbaar

    # beter niet shell=True gebruiken
    # er zijn security considerations
    # https://docs.python.org/3/library/subprocess.html#subprocess.Popen

if 0:
    markeer( 'Aanvulling 18 C' )

    import subprocess
    
    def RunStd( cmd ):
        print( 'RunStd', cmd )

        if 1:
            proc = subprocess.run( cmd, capture_output=True )
        else:
            # maak window onzichtbaar
            # https://stackoverflow.com/questions/7006238/how-do-i-hide-the-console-when-i-use-os-system-or-subprocess-call
            info = subprocess.STARTUPINFO()
            info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            info.wShowWindow = subprocess.SW_HIDE
            proc = subprocess.run( cmd, capture_output=True,
                                   startupinfo=info)

        retcode = proc.returncode
        print( 'returncode', retcode )

        stdout = proc.stdout
        if stdout != None:
            stdout = stdout.decode( errors='backslashreplace' )
        print( 'stdout', stdout )

        stderr = proc.stderr
        if stderr != None:
            stderr = stderr.decode( errors='backslashreplace' )
        print( 'stderr', stderr )
        print()
        
    # RunStd( [ 'cmd.exe', '/c', 'dir', 'testvenv' ] )

    # wacht niet, geen stdin
    # RunStd( [ 'cmd.exe', '/c', 'pause' ] )
    # RunStd( [ 'cmd.exe', '/k', 'pause' ] )
    # RunStd( [ 'cmd.exe' ] )
    
    # RunStd( [ 'ping', 'programmeren.hcc.nl' ] )

if 0:
    markeer( 'Aanvulling 18 D' )

    import datetime
    import email.utils
    import smtplib

    # zelf login bestand maken
    #   na gebruik verwijderen

    with open( 'loginsmtp.txt' ) as loginf:
        host = loginf.readline().strip()
        port = int(loginf.readline().strip())
        user = loginf.readline().strip()
        passw = loginf.readline().strip()

    print( 'Host', host )
    print( 'Port', port )
    print( 'User', user )

    pos = user.find( '@' )
    if pos < 0:
        print( 'Geen @ in user', user )
    else:
        domain = user[ pos + 1 : ]
        msgid = email.utils.make_msgid( None, domain )
        print( 'Msgid', msgid )

        inp = input( 'Type j voor email versturen>' )
        if inp.lower() == 'j':

            with smtplib.SMTP_SSL( host, port ) as smtp:
                print( smtp.noop() )

                if 0:
                    smtp.login( user, passw )

                    print( 'login ok' )

                    if 0:
                        frm = user
                        to = user
                        dat = datetime.datetime.now().ctime()
                        title = 'Test bericht'
                        
                        id = msgid
                        txt = 'Test email'

                        lines = [ 'From: ' + frm,
                                  'To: ' + to,
                                  'Date: ' + dat,
                                  'Subject: ' + title,
                                  # 'Message-ID: ' + id,
                                  '',
                                  txt ]
                        msg = '\r\n'.join( lines )
                        smtp.sendmail( frm, to, msg )

                        print( 'email verstuurd' )

    # email and MIME handling package
    # https://docs.python.org/3/library/email.html

    # Sending Emails With Python
    # https://realpython.com/python-send-email/

if 0:
    markeer( 'Aanvulling 18 E' )

    import poplib

    # zelf login bestand maken
    #   na gebruik verwijderen

    with open( 'loginpop.txt' ) as loginf:
        host = loginf.readline().strip()
        port = int(loginf.readline().strip())
        user = loginf.readline().strip()
        passw = loginf.readline().strip()

    print( 'Host', host )
    print( 'Port', port )
    print( 'User', user )

    # TypeError: does not support context manager protocol
    # with poplib.POP3_SSL( host, port ) as pop:
    #     print( pop.getwelcome() )

    pop = poplib.POP3_SSL( host, port )
    print( pop.getwelcome() )
    pop.user( user )
    pop.pass_( passw )

    rsp = pop.list()
    print( rsp )

    resp, lst, octets = rsp
    # print( resp )
    # print( lst )

    for item in lst:
        if 1:
            print( item )
        else:
            item, length = item.split()
            msg = pop.retr( int(item) )
            # print( msg )
            msgresp, msglst, octets = msg
            print( msgresp )
            # print( msglst )
            for msgln in msglst:
                # print( msgln )
                print( msgln.decode( errors='backslashreplace' ) )

    pop.quit()

if 0:
    markeer( 'Aanvulling 18 F' )

    import datetime
    import email.utils
    import os
    import shutil
    import smtplib

    def sendmail( subject, msg ):
        # zelf login bestand maken
        #   na gebruik verwijderen

        with open( 'loginsmtp.txt' ) as loginf:
            host = loginf.readline().strip()
            port = int(loginf.readline().strip())
            user = loginf.readline().strip()
            passw = loginf.readline().strip()

        # print( 'Host', host )
        # print( 'Port', port )
        # print( 'User', user )

        pos = user.find( '@' )
        if pos < 0:
            print( 'Geen @ in user', user )
            return
        domain = user[ pos + 1 : ]
        msgid = email.utils.make_msgid( None, domain )
        # print( 'Msgid', msgid )

        # inp = input( 'Type j voor email versturen>' )
        # if inp.lower() != 'j':
        #     return

        with smtplib.SMTP_SSL( host, port ) as smtp:
            # print( smtp.noop() )

            smtp.login( user, passw )
            # print( 'login ok' )

            frm = user
            to = user
            dat = datetime.datetime.now().ctime()
            
            lines = [ 'From: ' + frm,
                      'To: ' + to,
                      'Date: ' + dat,
                      'Subject: ' + subject,
                      'Message-ID: ' + msgid,
                      '',
                      msg ]
            msg = '\r\n'.join( lines )
            smtp.sendmail( frm, to, msg )

            print( 'email verstuurd' )

    if 0:
        # pth = '.'

        # pth = os.getcwd()

        # pth = r'd:\'  # unterminated string literal
        # pth = r'd:\\' # \\ 2x
        # pth = 'd:\\'  # \ 1x
        pth = 'd:/'  # / werkt ook

        total, used, free = shutil.disk_usage( pth )
        print( pth, total, used, free )

        if 0:
            if free < 10_000_000_000:
                titel = 'Waarschuwing'
                txt = 'weinig disk ruimte op ' + pth
                print( txt, titel )
                sendmail( titel, txt )

if 0:
    markeer( 'Aanvulling 18 G' )

    import ctypes
    import shutil

    # Is there a way to list all the available Windows' drives?
    # https://stackoverflow.com/questions/827371/is-there-a-way-to-list-all-the-available-windows-drives

    print( ctypes.windll )
    print( ctypes.windll.kernel32 )

    if 0:
        mask = ctypes.windll.kernel32.GetLogicalDrives()
        print( mask )

        if 0:
            letterord = ord( 'A' )
            driveletters = [ ]
            while mask:
                if mask & 1:
                    driveletters.append( chr( letterord ) )
                mask >>= 1
                letterord += 1
            print( driveletters )

            if 0:
                for letter in driveletters:
                    drive = letter + ':'

                    total, used, free = shutil.disk_usage( drive )
                    print( drive, total, used, free )

if 0:
    markeer( 'Aanvulling 18 H' )

    # SSH via Paramiko
    # https://www.paramiko.org/

    # pip install paramiko

    # als admin OpenSSH server activeren
    # Windows instellingen, settings
    #  Systeem, System
    #    Optionele onderdelen, Optional Features
    #      OpenSSH server en client toevoegen
    # https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse

    # OpenSSH poort wijzigen
    # %programdata%\ssh\sshd_config
    # https://superuser.com/questions/1501402/how-to-change-ssh-port-on-windows-10

    if 0:
        # warning uitzetten
        # https://github.com/paramiko/paramiko/issues/2419
        import warnings
        warnings.filterwarnings(action='ignore', module='.*paramiko.*')

    import paramiko # geeft CryptographyDeprecationWarning

    print( paramiko.__version__ )

    if 0:
        # zelf login bestand maken
        #   na gebruik verwijderen

        with open( 'loginssh.txt' ) as loginf:
            host = loginf.readline().strip()
            port = int(loginf.readline().strip())
            user = loginf.readline().strip()
            passw = loginf.readline().strip()

        print( 'Host', host )
        print( 'Port', port )
        print( 'User', user )

        with paramiko.SSHClient() as client:
            if 1:
                client.set_missing_host_key_policy( paramiko.AutoAddPolicy() )
            client.connect( host, port, user, passw )

            print( 'SSH connect ok' )

            if 0:
                stdin, stdout, stderr = client.exec_command( 'dir' )
                print( stdin, stdout, stderr )

                if 0:
                    print( stdout.read().decode() )

# monitor hdd/ssd met SMART info
# https://en.wikipedia.org/wiki/Self-Monitoring,_Analysis,_and_Reporting_Technology

# CrystalDiskInfo
# GUI, admin rechten nodig
# https://crystalmark.info/en/software/crystaldiskinfo/

# smartmontools
# cmd line, admin rechten nodig
# https://www.smartmontools.org/
# zie ook Aanvulling 17 J
# smartctl JSON/YAML/GREP output

if 0:
    markeer( 'Aanvulling 18 I' )

    # pySMART
    # https://pypi.org/project/pySMART/
    # https://github.com/truenas/py-SMART

    # wrapper voor smartctl
    # heeft smartmontools nodig

    # pip install pySMART

    if 0:
        import os
        
        path = os.environ[ 'PATH' ]
        # print( path )
        path += ';c:\\Program Files\\smartmontools\\bin\\;'
        # print( path )
        os.environ[ 'PATH' ] = path

    import pySMART

    print( pySMART.__version__ )

    # als FileNotFoundError: Command smartctl doesn't exist!
    # toevoegen in environment aan path, nog voor import

    devs = pySMART.DeviceList()
    # print( devs )
    # print( len( devs ) ) # error

    # for idx, dev in enumerate( devs ):
    #     print( idx, dev )

    # lege lijst in IDLE

    # dit werkt niet op Windows
    # Setting sudo is ignored on non-posix systems
    # pySMART.SMARTCTL.sudo = True

    # run als Administrator in Opdrachtprompt

    device = devs[0]
    print( 'device', device )

    # print( 'assessment', device.assessment )

    # print( device.all_attributes() )

    # print( 'attrib 5:', device.attributes[ 5 ] )

if 0:
    markeer( 'Aanvulling 18 J' )

    # Windows: run chkdsk als admin
    #   werkt niet, is niet mogelijk op onderstaande manier
    # Linux: run fsck ## todo

    # https://stackoverflow.com/questions/60214168/python-3-how-to-use-subprocess-run-as-admin-windows-10
    # gebruik psexec van Sysinternals
    
    # Sysinternals
    # https://learn.microsoft.com/en-us/sysinternals/

    import subprocess

    with open( 'loginadmin.txt' ) as loginf:
        user = loginf.readline().strip()
        passw = loginf.readline().strip()

    print( user )

    # werkt niet onder Windows
    #   niet toegestaan om automatisch admin te worden
    #   handmatig akkoord geven nodig
    #   via User Account Control (UAC)

    cmd = [ 'c:\\Program Files\\Sysinternals\\psexec',
            '-u', user, '-p', passw,
            # 'c:\\Windows\\System32\\chkdsk', '/?' ]
            'c:\\Windows\\System32\\cmd', '/k' ]
    ret = subprocess.run( cmd,capture_output=True  )
    print( 'returncode', ret.returncode )
    print( ret.stdout )
    print( ret.stderr )

if 0:
    markeer( 'Aanvulling 18 K' )

    # traverse tree
    import pathlib

    pth = pathlib.Path( '.' )

    if 0:
        for idx, p in enumerate( pth.iterdir() ):
            print( idx, p.is_dir(), p )
            if idx == 5:
                break

    # walk vanaf Python 3.12
    for idx, (dirpath, dirnames, filenames) in enumerate( pth.walk() ):
        pass
        if 0:
            for dirname in dirnames[ : 2]:
                print( dirpath / dirname )
        if 0:
            for filename in filenames[ : 5]:
                print( dirpath / filename )
        if idx == 3:
             break

if 0:
    markeer( 'Aanvulling 18 L' )

    # alleen Windows
    # WMI interface
    # https://en.wikipedia.org/wiki/Windows_Management_Instrumentation
    
    # Windows Management Instrumentation
    # https://learn.microsoft.com/en-us/windows/win32/wmisdk

    # WMI · PyPI
    # https://pypi.org/project/WMI/
    # https://timgolden.me.uk/python/wmi/index.html

    # pip install wmi
    # gebruikt pywin32

    import wmi

    if 1:
        cobj = wmi.WMI()
    else:
        # traag
        server = 'server'
        user = 'admin'
        password = 'secret'
        cobj = wmi.WMI( server, user=user, password=password )
        # meestal foutmelding
        #   De RPC-server is niet beschikbaar
        #   Toegang geweigerd
    print( cobj )

    if 0:
        # https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-service
        svcs = cobj.Win32_Service()
        print( 'Services', len(svcs) )
        if 1:
            for svc in svcs[:20]:
                print( svc.Caption )
            print()

        svcs = cobj.Win32_Service( StartMode='Auto', State='Stopped')
        print( 'Services auto stopped', len(svcs) )
        if 1:
            for svc in svcs:
                print( svc.Caption )
            print()

    if 0:
        # https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-operatingsystem
        oss = cobj.Win32_OperatingSystem()
        for os in oss:
            print( os.Caption )
            print( os.Name )
            print( os.Version )
            print( os.Description )
            print( os.Locale )
            print( os.CurrentTimeZone )

    if 0:
        # https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskdrive
        drvs = cobj.Win32_DiskDrive()
        for drv in drvs:
            print( drv.Caption )
            print( drv.Description )

if 0:
    markeer( 'Aanvulling 18 M' )

    # watchdog module
    # https://pypi.org/project/watchdog/
    # https://github.com/gorakhargosh/watchdog

    # pip install watchdog
    
    import time

    import watchdog.observers
    import watchdog.events

    class EventHandler( watchdog.events.FileSystemEventHandler ):
        def on_any_event( self, event ):
            print( event )

    eventhandler = EventHandler()
    observer = watchdog.observers.Observer()
    pth = '.'
    observer.schedule( eventhandler, pth, recursive=True )
    observer.start()
    try:
        while True:
            time.sleep( 1 )
            print( '*', end='', flush=True )
    finally:
        observer.stop()
        observer.join()
        print( 'stopped' )

if 0:
    markeer( 'Aanvulling 18 N' )

    # Everything
    # Locate files and folders by name instantly
    # https://www.voidtools.com/

    # SDK
    # https://www.voidtools.com/support/everything/sdk/

    # adapted from Python example
    # https://www.voidtools.com/support/everything/sdk/python/

    import ctypes
    import datetime
    import struct

    # evdllf = "C:\\EverythingSDK\\DLL\\Everything32.dll"
    evdllf = 'c:\\Program Files\\Everything\\Everything64.dll'
    dll = ctypes.WinDLL( evdllf )
    intullptr = [ ctypes.c_int, ctypes.POINTER( ctypes.c_ulonglong ) ]
    dll.Everything_GetResultDateModified.argtypes = intullptr
    dll.Everything_GetResultSize.argtypes = intullptr
    dll.Everything_GetResultFileNameW.argtypes = [ ctypes.c_int ]
    dll.Everything_GetResultFileNameW.restype = ctypes.c_wchar_p

    # srch ="test.py"
    srch = 'ProgrammeursleerlingExtra'
    dll.Everything_SetSearchW( srch )

    REQ_FILE_NAME = 1
    REQ_PATH = 2
    REQ_SIZE = 16
    REQ_DATE_MODIFIED = 64
    reqflags = REQ_FILE_NAME + REQ_PATH + REQ_SIZE + REQ_DATE_MODIFIED
    dll.Everything_SetRequestFlags( reqflags )
        
    dll.Everything_QueryW( 1 )

    count = dll.Everything_GetNumResults()
    print( f'Aantal: { count }' )

    # adapted from
    # https://stackoverflow.com/questions/39481221/convert-datetime-back-to-windows-64-bit-filetime

    wintickcount = 10_000_000 # 100 ns
    winepoch = datetime.datetime( 1601, 1, 1 )
    posixepoch = datetime.datetime( 1970, 1, 1 )
    epochdiffsecs = (posixepoch - winepoch).total_seconds()
    epochdiffwinticks = epochdiffsecs * wintickcount

    def todatetime( wintime ):
        winticks = struct.unpack( '<Q', wintime )[0]
        posixsecs = ( winticks - epochdiffwinticks ) / wintickcount
        return datetime.datetime.fromtimestamp( posixsecs )

    filename = ctypes.create_unicode_buffer( 260 )
    modified = ctypes.c_ulonglong( 1 )
    size = ctypes.c_ulonglong( 1 )

    for idx in range( count ):
        dll.Everything_GetResultFullPathNameW( idx, filename, 260 )
        print( f'File: { ctypes.wstring_at( filename ) }' )

        dll.Everything_GetResultDateModified( idx, modified )
        print( f'Gewijzigd: { todatetime( modified ) }' )

        dll.Everything_GetResultSize( idx, size )
        print( f'Size: { size.value } bytes\n' )

###############################################################
# Aanvulling 19 MySQL / MariaDB

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_sqlite en extra_mariadb directories
#     in PythonCursusXX.zip

# voor de workshop over MySql - Android - PC integratie
# zie extra_mariadb/android_python.txt

###############################################################
# Aanvulling 20 Jupyter

# Project Jupyter

# https://jupyter.org

# interactive data science, scientific computing
# non-profit, open-source door Jupyter community
# multiple programming languages

# https://en.wikipedia.org/wiki/Project_Jupyter

# afgeleid van IPython (Interactive Python)
# later Julia en R erbij
#   naam Jupyter -> Julia, Python en R
# later nog meer programmeertalen erbij

# is groot met veel projecten, is heel uitgebreid
# heeft veel gebruikers

if 0:
    markeer( 'Aanvulling 20 A' )

    # > py -m venv testvenv
    # > testvenv\Scripts\activate
    # (testvenv)> pip install jupyter

    import jupyter

    # print( dir( jupyter ) )

    print( jupyter.__version__ )
    print( jupyter.__name__ )
    print( jupyter.__doc__ )

    # bevat geen functies of classes

    # Jupyter modules zijn niet bedoeld
    # om te gebruiken binnen eigen Python code

if 0:
    markeer( 'Aanvulling 20 B' )

    # kan inhoud jupyter.exe bekijken als .zip
    #   bijvoorbeeld met Total Commander
    #   zie __main__.py in jupyter.exe 

    import jupyter_core.command as cmd

    if 0:
        cmd.main() # print jupyter.exe help output
        # gebruikt argv[0] programmanaam bij usage
        print( 'na cmd' ) # komt hier niet vanwege SystemExit

    if 0:
        import sys
        sys.argv = [ '', '--version' ]
        cmd.main() # print jupyter.exe output
        print( 'na cmd --version' ) # komt hier wel
    
# meest gebruikte programma's:
#  Jupyter Notebook
#  JupyterLab
#  Jupyter Lite

# draaien via webbrowser
#   lijken op een IDE
#   met meer en andere mogelijkheden

# Jupyter Notebook

# met actieve venv run:
#   jupyter-notebook.exe
# start automatisch een webserver
#   http://localhost:8888/tree
# start ook een webbrowser client

# selecteer bestand, maak nieuw
#   of upload, bedoeld om ook
#   met server op afstand te kunnen werken

# selecteer rechts boven New, Python3
# leeg Jupyter notebook opent in nieuwe tab

# voeg toe aan notebook:
# print( 'Hello Jupyter notebook' )
# klik op run > icon bovenaan
# output zichtbaar onder cell met print

# icons in het midden
# Save (Ctrl+S), Insert cell below (B),
#  Cut (X), Copy (C), Paste (V),
#  Run (Shift+Enter), Interrupt (Stop),
#  Restart, Restart run all,
#  Select cell type

# menu's
# File Edit View Run Kernel Settings Help

# Help, About Jupyter Notebook
#   Version 7.2.1

# hiermee begonnen met maken van eigen notebook
# notebook bestaat uit genummerde cellen
#   soms met (ongenummerde) output ertussen
# elke cell heeft input en soms output
# elke cell heeft een celltype
# klik op: Select cell type
#   mogelijkheden -, Code, Markdown, Raw
# Python code met print is type Code


# notebook werkt met 2 modes:
#   Edit mode en Command mode
# bij klikken op een cell wordt Edit mode actief
#   knipperende cursor verschijnt, kan die cell wijzigen
# bij klikken buiten een cell wordt Command mode actief
#   ook na Esc, cursor verdwijnt
#   kan cell als geheel bewerken
#   ga op cell staan, type C voor copy, dan V voor paste
# zie ook Edit menu
#   gebruik Z voor undo, Shift+Z voor redo

# huidige actieve cell heeft blauwe rand
#   en links een blauwe dikke lijn

# rechts in actieve cell icons
#  create duplicate,
#  move cell up (Ctrl+Shift+Up,
#  move cell down (Ctrk+Shift+Down),
#  insert cell above (A),
#  insert cell below (B),
#  delete this cell (D, D)
# zie ook Edit menu

# voeg een cell toe, inhoud:
#   # Titel
#   *Dit* is **markdown** tekst
# zet cell type op Markdown
# kies run, cell moet actief zijn
# tekst wordt omgezet
#   naar tekst met titel
#   en regel met italic en bold
# dubbel klik op cell om te wijzigen

# menu Help, Markdown reference
# https://commonmark.org/help/

# Markdown cell, inhoud:
# <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII" />
# https://gist.github.com/ondrek/7413434

# Python cells, inhoud:
# import base64
# # add = for padding
# raw = b'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII='
# png = base64.b64decode(raw)

# import IPython.display as disp
# disp.Image(png)

# menu File, Save notebook
#   bewaar bestand Untitled.ipynb
# bekijk met Editor, is JSON

# stop met menu File, Shutdown
#   of sluit webbrowser af
#   en stop jupyter-notebook server met Ctrl+C

# andere programmeertalen, kernels
# dependencies nodig, zelf installeren

# https://ipython.readthedocs.io/en/latest/install/kernel_install.html
# https://github.com/jupyter/jupyter/wiki/Jupyter-kernels

# Jupyter Notebook: An Introduction
# https://realpython.com/jupyter-notebook-introduction/

# 28 Jupyter Notebook Tips, Tricks, and Shortcuts
# https://www.dataquest.io/blog/jupyter-notebook-tips-tricks-shortcuts/


# JupyterLab

# met actieve venv run:
#   jupyter-lab.exe
# start automatisch een webserver
#   http://localhost:8888/lab
# en een webbrowser client

# midden Launcher
#   met keuzes
#   zoals Notebook Python
# aan linkerkant
#   directory en files

# dubbelklik links op eerder gemaakte
#   Untitled.ipynb
# opent rechts

# stop met File, Shutdown

# JupyterLab Extensions
# via de Extension Manager links in JupyterLab
# https://jupyterlab.readthedocs.io/en/stable/user/extensions.html

# Unofficial Jupyter Notebook Extensions
# https://jupyter-contrib-nbextensions.readthedocs.io

# A Simple Guide to Jupyter Notebook Extensions
# https://saturncloud.io/blog/a-simple-guide-to-jupyter-notebook-extension/

# JupyterLab lijkt op Jupyter Notebook
#   nieuwer, meer mogelijkheden
#   andere GUI layout

# https://saturncloud.io/blog/what-is-the-difference-between-jupyter-notebook-and-jupyterlab/


# Jupyter Lite
#   loopt in de browser
#   is nog experimenteel
#   heeft nog geen officiële release
# kan werken via statische website
#   geen Python op server nodig
#   draait Python in webbrowser
# https://jupyterlite.readthedocs.io

# gebruikt Pyodide via Wasm webassembly
# https://pyodide.org

# pip install jupyterlite

# dit werkt niet
# run jupyter-lite.exe
#  geeft geen foutmelding

# run:
# jupyter lite init
# jupyter lite serve

# dan met webbrowser naar
# http://127.0.0.1:8000/index.html
# redirect naar
# http://127.0.0.1:8000/lab/index.html

# https://jupyterlite.readthedocs.io/en/stable/quickstart/standalone.html

# nog geen Python of andere kernel aanwezig
# stop server met Ctrl+C

# pip install jupyterlite-pyodide-kernel
# start server, dan webbrowser
# jupyter lite serve

# https://jupyterlite.readthedocs.io/en/stable/howto/configure/kernels.html

# kan ook online uitproberen
#   dan geen installatie nodig
#   meer voorbeelden aanwezig
#   benodigde modules al geïnstalleerd

# https://jupyter.org/try

# https://jupyterlite.github.io/demo/lab/index.html
# menu Help, About: Version 0.4.0

# open readme.md
# copy inhoud
# plak in nieuw notebook markdown cell
# run

# open python.ipynb
# run met meerdere keren Shift-Enter

# probeer ook andere notebooks zoals
# p5.ipynb en javascript.ipynb

# in Jupyter Notebook
# gebruik python.ipynb van Jupyter Lite
# extra modules nodig
#   pip install pandas sympy

# in plaats van installatie met pip
# kan ook met conda, mamba, pipenv, Homebrew
# https://jupyter.org/install

# architecture
#   hoe werkt het intern
# https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html


# JupyterBook
# https://jupyterbook.org
# maak HTML en PDF boeken
# van notebooks of markdown

# staat enigszins los van Jupyter project
#   gebruikt ander domein namen
# https://blog.jupyter.org/announcing-the-new-jupyter-book-cbf7aa8bc72e

# werkt niet via webserver
#   is minder interactief
#   gebruikt geen webbrowser voor invoer

# pip install jupyter-book

# genereer voorbeeld boek bestanden
# jupyter-book create testboek

# bekijk testboek directory

# jupyter-book build testboek

# output met daarbij
# Running Sphinx

# Sphinx wordt ook gebruikt voor de Python documentatie
# https://www.sphinx-doc.org

# JupyterBook is grotendeels een wrapper om Sphinx
#   met een plugin om Myst Markdown te kunnen gebruiken
# in plaats van reStructuredText
#   wat bij de Python documentatie wordt gebruikt

# Maak PDF boek van HTML
# met Pyppeteer - simpel maar gammel
#   of met LaTeX - ingewikkeld, niet geprobeerd

# https://jupyterbook.org/en/stable/advanced/pdf.html

# pip install pyppeteer

# Pyppeteer is een
# headless chrome/chromium automation library
# https://pypi.org/project/pyppeteer/
# https://github.com/pyppeteer/pyppeteer

# jupyter-book build testboek --builder pdfhtml

# foutmelding:
# Chromium downloadable not found
# No such object: chromium-browser-snapshots/Win_x64/1181205/chrome-win.zip

# open bug
# https://github.com/pyppeteer/pyppeteer/issues/463
# download chrome-win.zip van
# https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Win_x64/1181217/
# unzip in
# ~/AppData/Local/pyppeteer/pyppeteer/local-chromium/1181205

# jupyter-book build testboek --builder pdfhtml

# Finished generating PDF via HTML for book. Your PDF is here:
#    testboek\_build\pdf\book.pdf

# Aantal boeken gemaakt met JupyterBook
# https://executablebooks.org/en/latest/gallery/

# bijvoorbeeld
# Explaining neural networks in raw Python
# https://bronwojtek.github.io/neuralnets-in-raw-python/

# Meer projecten
# https://docs.jupyter.org/en/latest/projects/content-projects.html
# https://docs.jupyter.org/en/latest/#sub-project-documentation

# Voila
#   maak standalone dashboard van notebook
# https://voila.readthedocs.io

# JupyterHub
#   multi-user notebooks
# https://jupyter.org/hub

# Binder
#   cloud tools
# https://jupyter.org/binder

# Jupyter Extension for Visual Studio Code
# https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter

# gratis online hosted Jupyter notebooks

# Google Colab
# https://colab.research.google.com/

# SaturnCloud
# https://saturncloud.io

# DeepNote
# https://deepnote.com

# Anaconda
# https://www.anaconda.com
# soms licentie wijzigingen
# https://www.theregister.com/2024/08/08/anaconda_puts_the_squeeze_on/

# Anaconda is ook een Python distributie
#   soms gemakkelijker dan standaard Python met pip

# https://www.anaconda.com/download
#   skip registration
# voor Windows 912 MB

# download en run Anaconda3-2024.06-1-Windows-x86_64.exe
# useful resources link bij einde installatie
# https://www.anaconda.com/installation-success?source=installer

# Run Anaconda Navigator via Start menu
# kan dan programma's starten
# Jupyter Lab, Jupyter Notebook en meer

###############################################################
# Aanvulling 21 SciPy

# SciPy
# https://scipy.org

# fundamental algorithms for scientific computing

# https://en.wikipedia.org/wiki/SciPy

# uitbreiding van NumPy
# ook wrapper van Fortran, C en C++ libraries
#   daardoor snel

# Numpy and Scipy Documentation download
# https://docs.scipy.org/doc/
# recente HTML+zip download SciPy 1.14.0
# https://docs.scipy.org/doc/scipy-1.14.0/scipy-html-1.14.0.zip
# laatste PDF download SciPy 1.8.1
# https://docs.scipy.org/doc/scipy-1.8.1/scipy-ref-1.8.1.pdf

# > py -m venv testvenv
# > testvenv\Scripts\activate
# (testvenv)> pip install scipy      # ook numpy erbij
# (testvenv)> pip install matplotlib # soms ook gebruikt

if 0:
    markeer( 'Aanvulling 21 A' )

    import scipy

    print( scipy.__version__ )

    # print( dir( scipy ) )

if 0:
    markeer( 'Aanvulling 21 B' )

    # SciPy package bestaat uit
    # diverse subpackages

    import scipy

    if 0:
        # https://stackoverflow.com/questions/52877803/how-to-display-the-subpackages-of-a-package-in-python
        for package in scipy.__all__:
            print( package )

    if 0:
        # https://docs.scipy.org/doc/scipy/tutorial/index.html#subpackages
        userGuide = ( 'cluster', 'constants', 'fft', 'fftpack', 'integrate',
                      'interpolate', 'io', 'linalg', 'ndimage', 'odr',
                      'optimize', 'signal', 'sparse', 'spatial', 'special',
                      'stats', )

        for pckg in userGuide:
            if pckg in scipy.__all__:
                inguide = ''
            else:
                inguide = 'not '
            print( pckg, inguide + 'in User Guide' )

        if 0:
            for package in scipy.__all__:
                if not package in userGuide:
                    print( 'not in User Guide', package )

    if 0:
        scipy.show_config()

if 0:
    markeer( 'Aanvulling 21 C' )

    import matplotlib.pyplot as plt
    import numpy as np
    import scipy as sp

    if 1:
        deg = 95
        # deg = 190
        # deg = 380
        # deg = 360 * 5
        # deg = 360 * 500

        ardeg = np.arange( deg )
        # ardeg = np.arange( deg, step=10 )

    if 0:
        print( ardeg )

    arrad = np.deg2rad( ardeg )
    if 0:
        print( arrad )
    
    arsin = np.sin( arrad )
    if 0:
        print( arsin )

    arcos = np.cos( arrad )
    if 0:
        print( arcos )

    if 0:
        arsin += arcos

    if 0:
        fig, ax = plt.subplots( )
        ax.plot( arsin )
        if 0:
            ax.plot( arcos )
        plt.show()

    # https://docs.scipy.org/doc/scipy/tutorial/io.html#wav-sound-files-scipy-io-wavfile
    # https://en.wikipedia.org/wiki/WAV
    wavf = 'test.wav'
    if 0:
        if 0:
            arsin = np.array( arsin, np.float32 )
        if 0:
            arsin = np.array( arsin * 32000, np.int16 )
        
        orate = 22050
        sp.io.wavfile.write( wavf, orate, arsin )

    if 0:
        irate, arwav = sp.io.wavfile.read( wavf )
        print( 'wav rate', irate, 'type', arwav.dtype, 'len', len(arwav) )

        if 0:
            fig, ax = plt.subplots( )
            ax.plot( arwav )
            plt.show()

if 0:
    markeer( 'Aanvulling 21 D' )

    import matplotlib.pyplot as plt
    import numpy as np
    import scipy as sp

    rate = 22050  # samples per second
    freq = 2      # Hz
    freq2 = 8
    if 0:
        freq = 1000   # Hz
        freq2 = 4000
    duration = 3  # seconds
    print( 'rate', rate, 'freq', freq, 'duration', duration )

    totalcount = rate * duration
    # periodcount = rate / freq

    # https://stackoverflow.com/questions/62106028/what-is-the-difference-between-np-linspace-and-np-arange
    # range met stap, linspace met aantal
    arx = np.linspace( 0, duration, totalcount, endpoint=False )
    ary = np.sin( 2 * np.pi * freq * arx )

    if 1:
        ary2 = np.sin( 2 * np.pi * freq2 * arx )
        ary += ary2 * 0.5

    if 0:
        plt.plot( arx, ary )
        plt.show()

    if 0:
        arffty = sp.fft.fft( ary )
        arfftx = sp.fft.fftfreq( totalcount, 1 / rate )
        
        if 0:
            if 1:
                # ComplexWarning
                #   casting discards imaginary part
                plt.plot( arfftx, arffty )
            else:
                arffty2 = np.abs( arffty )
                plt.plot( arfftx, arffty2 )
            plt.show()

        if 0:
            aryi = sp.fft.ifft( arffty )

            if 0:
                if 0:
                    # anders ComplexWarning
                    #   casting discards imaginary part
                    aryi = np.abs( aryi )

                plt.plot( arx, aryi )
                plt.show()

# Scientific Python Lectures
# https://lectures.scientific-python.org

# 1.5. SciPy : high-level scientific computing
# https://lectures.scientific-python.org/intro/scipy/index.html

# license
# Creative Commons Attribution 4.0 International License (CC-by)
# https://creativecommons.org/licenses/by/4.0/

# Python code copy via icon links boven
#   in lichtblauwe vakken met code
# plakken in cmd Python werkt ok
#   gaat niet goed in IDLE Shell REPL
# https://stackoverflow.com/questions/1615379/pasting-multiple-lines-into-idle

# wel met de PastePyShell.py extensie van IdleX 
# https://idlex.sourceforge.net
# https://github.com/serwy/idlex


# Fourier Transforms With scipy.fft: Python Signal Processing
# https://realpython.com/python-scipy-fft/

# SciPy Tutorial
# https://www.w3schools.com/python/scipy/

# The 'eu' in eucatastrophe:
#   Why SciPy builds for Python 3.12 on Windows are a minor miracle
# https://labs.quansight.org/blog/building-scipy-with-flang


###############################################################
# Aanvulling 22 pandas

# pandas
# https://pandas.pydata.org

# data analysis and manipulation

# https://en.wikipedia.org/wiki/Pandas_(software)

# afgeleid van de programmeertaal R
# https://en.wikipedia.org/wiki/R_(programming_language)

# maakt veel gebruik van NumPy

# veel mogelijkheden, veel gebruikers

if 0:
    markeer( 'Aanvulling 22 A' )

    import pandas

    print( pandas.__version__ )

if 0:
    markeer( 'Aanvulling 22 B' )

    import pandas

    # Series
    # lijkt op array
    # één dimensie 1D
    # bevat ook Index

    if 0:
        ser1 = pandas.Series()

        if 0:
            print( ser1 )

        if 0:
            ser1.info()

    if 0:
        ser2 = pandas.Series( [ 10, 7, -3, 12 ] )

        if 1:
            print( ser2 )

        if 0:
            ser2.info()

        if 0:
            print( type( ser2.index ) )
            print( ser2.index )

        if 0:
            print( type( ser2.values ) )
            print( ser2.values )

        if 0:
            print( ser2[ 3 ] )

        if 0:
            print( ser2[ -1 ] ) # KeyError

        if 0:
            print( type( ser2.index ) )
            del ser2[ 2 ]
            if 0:
                print( ser2[ 2 ] ) # KeyError
            if 0:
                print( ser2 )
            if 0:
                print( ser2[ 3 ] )
            if 0:
                print( type( ser2.index ) )

        if 0:
            ser2.name = 'sernaam'
            print( ser2 )

        if 0:
            ser2.index.name = 'serindex'
            print( ser2 )

    if 0:
        ser3 = pandas.Series( [ 2.7, 1.4, 0.03, -12 ],
                              index=[ 'x', 'y', 'z', 't' ] )
        if 1:
            print( ser3 )

        if 0:
            ser3.info()

        if 0:
            print( type( ser3.index ) )
            print( ser3.index )

        if 0:
            print( type( ser3.values ) )
            print( ser3.values )

        if 0:
            print( ser3[ 2 ] ) # FutureWarning, deprecated

        if 0:
            print( ser3[ 'z' ] )

        if 0:
            print( ser3.z )

        if 0:
            print( ser3 * 3 )

        if 0:
            print( ser3[ ser3 > 2 ] )
            # print( ser3[ ser3 < 1 ] )
        
        if 0:
            print( 1.4 in ser3 )        # False
            print( 1.4 in ser3.values ) # True
            print( 'y' in ser3 )        # True

    if 0:
        dct = { 'x' : 22.3, 'k' : -3.7, 'nn' : 0.0 }
        ser4 = pandas.Series( dct )
        if 0:
            print( 'ser4' )
            print( ser4 )
            if 0:
                ser4.info()

        if 0:
            ser5 = pandas.Series( dct, index=[ 'nn', 'x', 'k' ] )
            print( ser5 )
            if 0:
                ser5.info()

        # aa niet in dict -> NaN
        # k niet in index -> niet in series
        ser6 = pandas.Series( dct, index=[ 'nn', 'x', 'aa' ] )
        if 0:
            print( 'ser6' )
            print( ser6 )
            if 0:
                ser6.info()

        if 0:
            print( 'ser4+6' )
            print( ser4 + ser6 )

    if 0:
        items = [ 1, 3.5, 'a', -1j ]
        ser = pandas.Series( items )

        if 0:
            print( ser )

        if 0:
            ser.info()

        if 0:
            print( ser.values )
            print( type( ser.values ) )
            print( ser.values.dtype )

if 0:
    markeer( 'Aanvulling 22 C' )

    import numpy
    import pandas
    import sys

    if 0:
        frm1 = pandas.DataFrame()
        print( frm1 )

        if 0:
            frm1.info()

    if 0:
        dat = { 'temp'  : [ 20.0, 27.2, 23.5, 25.1,  19.7  ],
                'regen' : [    0,    3,    0,    0,     2  ],
                'windr' : [  'z',  'zw', 'z',  'zo', 'zzw' ] }
        if 0:
            frm2 = pandas.DataFrame( dat )
            print( frm2 )

            if 0:
                frm2.info()

        idx = [ 'De Bilt', 'Den Helder', 'Eelde',
                'Maastricht', 'Vlissingen' ]
        frm3 = pandas.DataFrame( dat, index=idx )

        if 1:
            print( frm3 )

            if 0:
                frm3.info()

        if 0:
            temp = frm3.temp
            # temp = frm3[ 'temp' ]
            print( temp )

            if 0:
                print( type( temp ) )

        if 0:
            regen = frm3.regen
            # regen = frm3[ 'regen' ]
            print( regen )

            if 0:
                print( type( regen ) )

        if 0:
            regenwindr = frm3[ 'regen' : 'windr' ]
            print( 'regenwindr', regenwindr )

        if 0:
            # frm3.temp = range( 10, 14 ) # ValueError
            # frm3.temp = range( 10, 15 )
            # frm3.temp = numpy.arange( 10.0, 15.0 )
            # frm3.temp = numpy.arange( 10.0, 20.0, 2.0 )
            # frm3.temp = numpy.linspace( 10.0, 20.0, 5 )
            # frm3.temp = numpy.linspace( 10.0, 20.0, 5, endpoint=False )
            print( frm3 )
            print( frm3.temp.dtype )

        if 0:
            frm3.regen = 4
            print( frm3 )

        if 0:
            print( frm3.index )

        if 0:
            print( frm3.columns )

        if 0:
            print( frm3[ 'Eelde' ] ) # KeyError

        if 0:
            print( frm3.loc )

        if 0:
            eelde = frm3.loc[ 'Eelde' ]
            print( eelde )

            if 0:
                print( type( eelde ) )

        if 0:
            eevl = frm3.loc[ 'Eelde' : 'Vlissingen' ] # inclusief
            print( eevl )

            if 0:
                print( type( eevl ) )

        if 0:
            print( frm3.loc[ : ] )

        if 0:
            print( frm3.loc[ : , 'regen' ] )

        if 0:
            print( frm3.loc[ : , 'regen' : 'windr' ] )

        if 0:
            print( frm3.loc[ 'Den Helder' : 'Maastricht',
                             'regen' : 'windr' ] )

        if 0:
            eelde = frm3.iloc[ 2 ]
            print( eelde )

        if 0:
            eeplus = frm3.iloc[ 2 : ]
            print( eeplus )

        if 0:
            eeplusreg = frm3.iloc[ 2 : , 1 ]
            print( eeplusreg )

        if 0:
            eeplusregplus = frm3.iloc[ 2 : , 1 : ]
            print( eeplusregplus )

        if 0:
            rechtsonder = frm3.iloc[ -1, -1]
            print( rechtsonder )
            if 0:
                print( type( rechtsonder ) )

        if 0:
            frm4 = frm3
            # frm4 = frm3.copy()
            frm4.windr = ''
            frm4.regen = numpy.arange(5)
            
            print( frm3 )
            print( frm4 )

        if 0:
            if 0:
                ser1 = pandas.Series( [ 3, 2, 2, 3, 5 ] ) # NaN
            if 0:
                ser1 = pandas.Series( [ 3, 2, 2, 3, 5 ], index=idx )
            
            if 0:
                frm3.winds = ser1 # geen nieuwe kolom
                if 0:
                    del frm3.winds
                if 0:
                    del frm3[ 'winds' ] # KeyError

            if 0:
                frm3[ 'winds' ] = ser1
                if 0:
                    del frm3.winds # AttributeError
                if 0:
                    del frm3[ 'winds' ]

            print( frm3 )

            if 0:
                print( frm3.winds )
            if 0:
                print( frm3[ 'winds' ] )

        if 0:
            if 0:
                frm6 = frm3.transpose()
            if 0:
                frm6 = frm3.T

            if 0:
                print( type( frm6 ) )
                print( frm6 )

            if 0:
                frm6.info()

        if 0:
            if 0:
                print( frm3[ 'De Bilt' ][ 'temp' ] ) # KeyError

            if 0:
                print( frm3[ 'temp' ][ 'De Bilt' ] )

            if 0:
                frm3[ 'temp' ][ 'De Bilt' ] = 23.2 # FutureWarning

            if 0:
                frm3.loc[ 'temp' ][ 'De Bilt' ] = 23.2 # KeyError

            if 0:
                frm3.loc[ 'De Bilt' ][ 'temp' ] = 23.2 # SettingWithCopyWarning
                
            if 0:
                frm3.loc[ 'De Bilt', 'temp' ] = 23.2

            if 0:
                frm3.loc[ 'De Bilt', 'temp' ] = numpy.NaN # AttributeError
                # frm3.loc[ 'De Bilt', 'temp' ] = numpy.nan
                # print( frm3.loc[ 'De Bilt', 'temp' ] )
                # print( numpy.isnan( frm3.loc[ 'De Bilt', 'temp' ] ) )

            if 0:
                frm3.info()
                frm3.loc[ 'De Bilt', 'regen' ] = numpy.nan
                frm3.info()

            print( frm3 )

        if 0:
            idx2 = idx + [ 'Eindhoven' ]
            frm7 = pandas.DataFrame( dat, index=idx2 ) # ValueError
            print( frm7 )

        if 0:
            frm8 = pandas.DataFrame( { 'temp'  : [ 10.4 ],
                                       'regen' : [ 5 ],
                                       'windr' : [ 'o' ] },
                                    [ 'Eindhoven' ] )
            if 0:
                print( frm8 )

            if 0:
                frm9 = pandas.concat( ( frm3, frm8 ) )
                print( frm9 )

        if 0:
            # frm3.index.name = 'plaats'
            # frm3.columns.name = 'meting'
            # frm3.name = 'dagwaarde'
            print()
            if 0:
                print( frm3 )
                print()
            if 0:
                frm3.info()
                print()
            if 0:
                frm3.to_csv( sys.stdout )
                print()

        if 0:
            frm3.to_csv( sys.stdout, sep='$' )

        if 0:
            frm3.to_csv( sys.stdout, header=None )

        if 0:
            if 0:
                frm3.loc[ 'Eelde', 'temp' ] = numpy.nan
                print( frm3 )

            frm3.to_csv( sys.stdout )

            if 0:
                frm3.to_csv( sys.stdout, na_rep='leeg' )

        csvfile = 'test.csv'
        if 0:
            if 0:
                frm3.to_csv( csvfile )

            if 0:
                frm3.to_csv( csvfile, header=None )

        if 0:
            if 0:
                frm7 = pandas.read_csv( csvfile )

            if 0:
                frm7 = pandas.read_csv( csvfile, index_col=0 )

            if 0:
                frm7 = pandas.read_csv( csvfile, header=0 )

            if 0:
                frm7 = pandas.read_csv( csvfile, header=None )

            if 0:
                frm7 = pandas.read_csv( csvfile, header=None,
                            names=[ '', 'Temp', 'Regen', 'Wind' ])
            if 0:
                frm7 = pandas.read_csv( csvfile, index_col=0, header=None,
                            names=[ '', 'Temp', 'Regen', 'Wind' ])
                            # names=[ None, 'Temp', 'Regen', 'Wind' ])

            print( 'frm7' )
            print( frm7 )
            if 0:
                frm7.info()

        if 0:
            frm3.to_json( sys.stdout )

        if 0:
            frm3.loc[ 'Eelde', 'temp' ] = numpy.nan
            print( frm3 )

            frm3.to_json( sys.stdout )

        jsonfile = 'test.json'
        if 0:
            frm3.to_json( jsonfile )

        if 0:
            frm3.loc[ 'Eelde', 'temp' ] = numpy.nan
            frm3.to_json( jsonfile )

        if 0:
            frm8 = pandas.read_json( jsonfile )

            print( 'frm8' )
            print( frm8 )
            if 0:
                frm8.info()

if 0:
    markeer( 'Aanvulling 22 D' )

    import io
    import json
    import pandas
    import requests

    if 0:
        # zie ook Aanvulling 15 M
        
        if 1:
            url = 'https://www.daggegevens.knmi.nl/klimatologie/daggegevens'
        else:
            url = 'https://www.daggegevens.knmi.nl/klimatologie/uurgegevens'

        data = { # 'fmt' : 'xml', # default csv met uitleg
                 'fmt' : 'json' }
        data[ 'start' ] = '20240710'
        data[ 'end' ] = '20240711'
        if 0:
            data[ 'end' ] = '20240720'

        if 1:
            data[ 'stns' ] = '260' # De Bilt
        if 1:
            data[ 'vars' ] = 'TEMP'

        resp = requests.post( url, data )
        print( resp )
        if resp.status_code != 200:
            print( 'download error' )

        resptxt = resp.text
        if len( resptxt ) < 200:
            print( resptxt )
        print( len( resptxt ) )

        respjsn = resp.json()
        print( len( respjsn ) )

        if 0:
            if 0:
                frm = pandas.read_json( respjsn ) # ValueError

            if 0:
                # https://github.com/pandas-dev/pandas/issues/52271
                frm = pandas.read_json( io.StringIO( respjsn ) ) # TypeError

            if 0:
                # https://stackoverflow.com/questions/42518864/convert-json-data-from-request-into-pandas-dataframe
                frm = pandas.DataFrame.from_dict( respjsn )

            print( frm )

        if 0:
            if 0:
                jsonfile = 'weer1.json'
                with open( jsonfile, 'w' ) as jsnf:
                    json.dump( respjsn, jsnf )

            if 0:
                jsonfile = 'weer2.json'
                with open( jsonfile, 'w' ) as jsnf:
                    jsnf.write( resptxt )

    if 0:
        if 0:
            jsonfile1 = 'weer1.json'
            frm1 = pandas.read_json( jsonfile1 )
            print( frm1 )

        if 0:
            jsonfile2 = 'weer2.json'
            frm2 = pandas.read_json( jsonfile2 )
            print( frm2 )

        if 0:
            print( frm1 == frm2 )

        if 0:
            frm3 = frm1[ not frm1.isna() ] # ValueError
            print( frm3 )

        if 0:
            frm4 = frm1.dropna()
            # frm4 = frm1.dropna( axis=0 )
            # frm4 = frm1.dropna( axis='index' )
            print( frm4 )

        if 0:
            frm5 = frm1.dropna( axis=1 )
            # frm5 = frm1.dropna( axis='columns' )
            print( frm5 )

        if 0:
            # Invalid comparison between dtype=datetime64[ns, UTC] and int
            frm6 = frm1[ frm1 > 10 ]
            # frm6 = frm1.loc[ frm1 > 10 ]
            print( frm6 )

        if 0:
            frm7 = frm1.drop( 'date', axis=1 )
            print( frm7 )

            if 0:
                frm8 = frm7[ frm7 > 10 ]
                print( frm8 )

# The pandas DataFrame: Make Working With Data Delightful
# https://realpython.com/pandas-dataframe/

# Pandas Tutorial
# https://www.w3schools.com/python/pandas/

# pandas lijkt enigszins op een SQL database
# met meer mogelijkheden
#   meer functies, types
#   experimenteren eenvoudiger
# maar ook met beperkingen
#   moet in geheugen passen
#   geen commit / rollback

# Pandas vs. SQL
# Part 1: The Food Court and the Michelin-Style Restaurant
# https://ponder.io/pandas-vs-sql-food-court-michelin-style-restaurant/
# Part 2: Pandas Is More Concise
# https://ponder.io/pandas-vs-sql-part-2-pandas-is-more-concise/
# Part 3: Pandas Is More Flexible
# https://ponder.io/pandas-vs-sql-part-3-pandas-is-more-flexible/
# Part 4: Pandas Is More Convenient
# https://ponder.io/pandas-vs-sql-part-4-pandas-is-more-convenient/

# pandas is single threaded
# er zijn nieuwe multithreading
#   modules met dezelfde API in opkomst
# zoals Modin van Ponder

# We parallelized 600 pandas functions with Modin
# https://ponder.io/how-do-we-parallelized-600-pandas-functions-with-modin/


# Python Data Science Handbook
# https://jakevdp.github.io/PythonDataScienceHandbook/

# 3. Data Manipulation with Pandas
# https://jakevdp.github.io/PythonDataScienceHandbook/#3.-Data-Manipulation-with-Pandas

# met Jupytext in branch jupytext_no_ipynb
# https://github.com/mwouts/PythonDataScienceHandbook/tree/jupytext_no_ipynb

# Jupytext extensie voor Jupyter
# .ipynb <-> .md of .py
# https://jupytext.readthedocs.io
# Notebooks as code
# https://jupytext.readthedocs.io/en/latest/formats-scripts.html


###############################################################
# Aanvulling 23 MQTT

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_mqtt directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 24 3D

# 3D graphics
# https://en.wikipedia.org/wiki/3D_computer_graphics

# input 3D object model, invoeren of scannen
# met computer verwerken genereren renderen
# output 3D afbeelding, film, printer

# meer data gegevens model voor hogere resolutie kwaliteit snelheid
# daarom meestal speciale GPU grafische processor in gebruik
#   naast algemene CPU

# meest gebruikte APIs
#   ontworpen voor veel mogelijkheden,
#     veel data en hoge snelheid
#   daardoor heel uitgebreid en ingewikkeld
# meestal geschreven in C/C++

# OpenGL cross-platform
# https://en.wikipedia.org/wiki/OpenGL

# OpenGL ES vereenvoudigde versie
#   voor embedded zoals mobiele telefoons
# https://en.wikipedia.org/wiki/OpenGL_ES

# WebGL voor JavaScript in webbrowsers
#   gebaseerd op OpenGL ES
# https://en.wikipedia.org/wiki/WebGL

# Vulkan cross-platform
#  opvolger van OpenGL
# https://en.wikipedia.org/wiki/Vulkan

# DirectX voor Windows en XBox
# https://en.wikipedia.org/wiki/DirectX

# Metal voor MacOS en iOS
# https://en.wikipedia.org/wiki/Metal_(API)

# WebGPU voor JavaScript in webbrowsers
#   opvolger van WebGL
# https://en.wikipedia.org/wiki/WebGPU

# Python modules
# PyOpenGL
# https://pyopengl.sourceforge.net/
# https://pypi.org/project/PyOpenGL/
# relatief laag niveau, lijkt op OpenGL

# pip install PyOpenGL
# https://pyopengl.sourceforge.net/documentation/index.html

# pip install PyGame
# https://www.pygame.org/docs/

if 0:
    markeer( 'Aanvulling 24 A' )

    import pygame   # print versie
    import OpenGL.GL as ogl
    import OpenGL.GLU as oglu

    if 1:
        import OpenGL
        print( OpenGL.__version__ )

    #     kubus     coördinaten
    #    achter       y
    #     4---5       |  -1
    #    /|  /|      +1 /
    #   / 7-/-6       |/
    #  0---1 /   -1 --0-- +1 --x
    #  |/  |/        /|
    #  3---2        / -1
    #  voor      z +1

    cubeVerts = (
        #  x   y   z
        ( -1,  1,  1 ),  # 0
        (  1,  1,  1 ),  # 1
        (  1, -1,  1 ),  # 2
        ( -1, -1,  1 ),  # 3
        ( -1,  1, -1 ),  # 4
        (  1,  1, -1 ),  # 5
        (  1, -1, -1 ),  # 6
        ( -1, -1, -1 ) ) # 7

    cubeEdges = [ ( 0, 1 ), ( 0, 3 ),
                  ( 0, 4 ), ( 1, 2 ),
                  ( 1, 5 ), ( 2, 3 ),
                  ( 2, 6 ), ( 3, 7 ),
                  ( 4, 5 ), ( 4, 7 ), # weghalen proberen
                  ( 5, 6 ), ( 6, 7 ) ]

    if 0:
        cubeEdges.extend( [ ( 0, 2) ])
    if 0:
        cubeEdges.extend( [ ( 1, 3) ])

    cubeSurfaces = [
        ( 0, 1, 2, 3 ),
        ( 4, 5, 6, 7 ),
        # ( 4, 5, 7, 6 ),  # verkeerde volgorde
        ]

    colorRed    = 1, 0, 0 # r, g, b
    colorGreen  = 0, 1, 0
    colorBlue   = 0, 0, 1
    colorBlack  = 0, 0, 0
    colorWhite  = 1, 1, 1
    colorYellow = 1, 1, 0
    colorCyan   = 0, 1, 1
    colorMagenta = 1, 0, 1
    colorOrange = 1, 0.7, 0
    
    cubeColors = [
        ( colorOrange, colorGreen, colorBlue, colorCyan ),
        ( colorMagenta, colorBlack, colorCyan, colorYellow ) ]

    if 0:
        cubeSurfaces.append( ( 0, 1, 6, 7 ) )
        cubeColors.append(
            ( colorBlack, colorGreen, colorWhite, colorRed ) )

    if 0:
        cubeSurfaces.insert( 0, ( 0, 1, 6, 7 ) )
        cubeColors.insert( 0,
            ( colorBlack, colorGreen, colorWhite, colorRed ) )

    pygame.init()
    try:
    # if 1:
        width, height = 1024, 768
        flags = pygame.DOUBLEBUF | pygame.OPENGL
        pygame.display.set_mode( ( width, height ), flags )

        fieldofview = 45 # 90 proberen  camera gezichtspunt afstand
        aspectratio = width / height
        clipnear = 0.1
        clipfar = 50.0
        oglu.gluPerspective( fieldofview, aspectratio,
                             clipnear, clipfar )

        if 1:
            trz = -5.0
            # trz = 0.0  # vanuit midden kubus
            # trz = -10.0 # lijkt kleiner, je ziet minder
            # trz = 5.0 # niets te zien
            ogl.glTranslatef( 0.0, 0.0, trz )

        run = True
        count = 0
        while run:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
            
            if 1:
                ogl.glRotatef( 1, 3, 1, 1 )

            if 1:
                ogl.glClear( ogl.GL_COLOR_BUFFER_BIT | ogl.GL_DEPTH_BUFFER_BIT )
            
            if 0:
                ogl.glBegin( ogl.GL_QUADS )

                for surface, colors in zip( cubeSurfaces, cubeColors ):
                    for vert, color in zip( surface, colors ):
                        ogl.glColor3fv( color )
                        ogl.glVertex3fv( cubeVerts[ vert ] )
                        
                ogl.glEnd()
            
            if 1:
                if 1:
                    ogl.glColor3fv( colorWhite )

                ogl.glBegin( ogl.GL_LINES )

                for edge in cubeEdges:
                    for vert in edge:
                        ogl.glVertex3fv( cubeVerts[ vert ] )

                ogl.glEnd()

            pygame.display.flip()

            3/0 # crash met opzet voor finally pygame.quit()
            # pigame.iets

            if 1:
                ms = 10
                # ms = 1
                pygame.time.wait( ms )

    finally:
        pygame.quit() # zodat PyGame Window verdwijnt bij fout

# OpenGL with PyOpenGL introduction and creation of Rotating Cube
# https://pythonprogramming.net/opengl-rotating-cube-example-pyopengl-tutorial/

# pip install pyopengltk
# https://github.com/jonwright/pyopengltk

if 0:
    markeer( 'Aanvulling 24 B' )

    import tkinter
    import pyopengltk
    import sys
    import OpenGL.GL as ogl
    import OpenGL.GLU as oglu

    cubeVerts = (
        #  x   y   z
        ( -1,  1,  1 ),  # 0
        (  1,  1,  1 ),  # 1
        (  1, -1,  1 ),  # 2
        ( -1, -1,  1 ),  # 3
        ( -1,  1, -1 ),  # 4
        (  1,  1, -1 ),  # 5
        (  1, -1, -1 ),  # 6
        ( -1, -1, -1 ) ) # 7

    cubeEdges = [ ( 0, 1 ), ( 0, 3 ),
                  ( 0, 4 ), ( 1, 2 ),
                  ( 1, 5 ), ( 2, 3 ),
                  ( 2, 6 ), ( 3, 7 ),
                  ( 4, 5 ), ( 4, 7 ),
                  ( 5, 6 ), ( 6, 7 ) ]

    cubeSurfaces = [
        ( 0, 1, 2, 3 ),
        ( 4, 5, 6, 7 ),
        # ( 4, 5, 7, 6 ),  # verkeerde volgorde
        ]

    colorRed    = 1, 0, 0 # r, g, b
    colorGreen  = 0, 1, 0
    colorBlue   = 0, 0, 1
    colorBlack  = 0, 0, 0
    colorWhite  = 1, 1, 1
    colorYellow = 1, 1, 0
    colorCyan   = 0, 1, 1
    colorMagenta = 1, 0, 1
    colorOrange = 1, 0.7, 0
    
    cubeColors = [
        ( colorOrange, colorGreen, colorBlue, colorCyan ),
        ( colorMagenta, colorBlack, colorCyan, colorYellow ) ]

    class CubeFrame( pyopengltk.OpenGLFrame ):
        def initgl( self ):
            ogl.glLoadIdentity()
            oglu.gluPerspective( 45,
                ( self.width / self.height), 0.1, 50.0)
            ogl.glTranslatef( 0.0, 0.0, -5.0)

        def redraw(self):
            ogl.glRotatef( 1, 3, 1, 1 )
            ogl.glClear( ogl.GL_COLOR_BUFFER_BIT | ogl.GL_DEPTH_BUFFER_BIT )

            if 0:
                ogl.glBegin( ogl.GL_QUADS )

                for surface, colors in zip( cubeSurfaces, cubeColors ):
                    for vert, color in zip( surface, colors ):
                        ogl.glColor3fv( color )
                        ogl.glVertex3fv( cubeVerts[ vert ] )
                        
                ogl.glEnd()

            if 0:
                ogl.glColor3fv( colorWhite )

            if 1:
                ogl.glBegin( ogl.GL_LINES )
                for edge in cubeEdges:
                    for vertex in edge:
                        ogl.glVertex3fv( cubeVerts[ vertex ] )
                ogl.glEnd()

    main = tkinter.Tk()

    def quitcmd():
        print( 'Quit pressed' )

        # calling quit() does not close the main window
        #   when run inside IDLE
        # because IDLE is als a tkinter application

        # https://stackoverflow.com/questions/3431498/what-code-can-i-use-to-check-if-script-is-running-in-idle
        # https://stackoverflow.com/questions/2307464/what-is-the-difference-between-root-destroy-and-root-quit

        if 'idlelib.run' in sys.modules:
            print( 'In IDLE' )
            main.destroy()
        else:
            print( 'No IDLE' )
            main.quit()

    button = tkinter.Button( main, text='Quit', command=quitcmd )
    button.pack()

    frm = CubeFrame( main, height=600, width=800 )
    frm.animate = 10
    # frm.animate = 1
    frm.pack( fill=tkinter.BOTH, expand=tkinter.YES )

    main.mainloop()

# ook in PySide OpenGL support
#   QtOpenGL widget

# Microsoft Paint3D
# in Microsoft Store
# https://apps.microsoft.com/detail/9nblggh5fv99
# al aanwezig op Windows 10
# niet op Windows 11, kan van MS Store downloaden
#   support stopt 4 november 2024

# voorbeeld Duck download
# https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/Duck
# met Paint3D bekijken

# VPython
# 3D Programming for Ordinary Mortals
# https://vpython.org
# https://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples/
# gebruikt RapydScript-NG
#   een Python naar JavaScript compiler
#   https://github.com/kovidgoyal/rapydscript-ng
# voorbeeld AtomicSolid uitproberen
# online of lokaal in webbrowser
# aantal N aanpassen


###############################################################
# Aanvulling 25 Fractals

# fractals zijn zelfgelijkende meetkundige figuren
# een fractal is opgebouwd uit delen
#   die gelijkvormig zijn met het figuur zelf

# https://nl.wikipedia.org/wiki/Fractal
# https://en.wikipedia.org/wiki/Fractal

# bekende fractals zijn Mandelbrot en Julia

# Nederlandstalige boeken over fractals
# geschreven door Hans Lauwerier in 1987, 1990 en 1994

# https://nl.wikipedia.org/wiki/Hans_Lauwerier
# https://www.vanunnik.net/fraktals_nl/index.htm

# ook in CWI Syllabus 14
# Vakantiecursus 1987
# De personal computer en de wiskunde op school
# H. A. Lauwerier
# Fractals, meervoudige gelijkvormigheid
# met kleine Basic programma's
# https://ir.cwi.nl/pub/12913/12913D.pdf

if 0:
    markeer( 'Aanvulling 25 A' )

    import tkinter
    import math

    # kromme van Levy
    # 12913D.pdf blz 20
    # van Basic naar Python

    width, height = 1024, 768
    main = tkinter.Tk()
    canv = tkinter.Canvas( main, width=width, height=height )
    canv.pack()

    offsetx, offsety = 0.53, 1.15
    scalex, scaley = 500, 500
    posx, posy = 0, 0

    def line( x, y ):
        global posx, posy
        x0 = (offsetx + posx) * scalex
        y0 = (offsety + posy) * scaley
        x1 = (offsetx + x) * scalex
        y1 = (offsety + y) * scaley
        canv.create_line( x0, y0, x1, y1 )
        # print( x0, y0, x1, y1 )
        posx, posy = x, y

    p = 3
    # p = 4
    # p = 12
    # p = 13
    h = 2**( -p/2 )
    a = h * math.cos( p * math.pi / 4 )
    b = h * math.sin( p * math.pi / 4 )
    line( a, -b )
    line( a + b, a - b )
    x = 1
    y = 1
    for n in range( 2, 2 ** p ):
        m = n
        s = 1
        while True:
            if m % 2 == 1:
                s += 1
            m = m // 2
            if m <= 1:
                break
        if s % 4 == 0:
            x += 1
        if s % 4 == 1:
            y += 1
        if s % 4 == 2:
            x -= 1
        if s % 4 == 3:
            y -= 1
        line( a * x + b * y, a * y - b * x )

    main.mainloop()

if 0:
    markeer( 'Aanvulling 25 B' )

    import tkinter
    import math

    # stoft
    # 12913D.pdf blz 20
    # van Basic naar Python

    width, height = 1024, 768
    main = tkinter.Tk()
    canv = tkinter.Canvas( main, width=width, height=height )
    canv.pack()

    offsetx, offsety = 0.53, 0.45
    scalex, scaley = 500, 500

    def pset( x, y ):
        xp = (offsetx + x) * scalex
        yp = (offsety + y) * scaley
        canv.create_oval( xp, yp, xp, yp, width=2 )
        # print( xp, yp )

    p = 3
    # p = 12
    # p = 14
    aa = [ 0 ] * ( p + 1 )
    a, b, c, d = 0.5, 0.5, 0.5, -0.5
    pset( 0, 0 )
    pset( 1, 0 )
    for m in range( p ):
        for n in range( 2 ** m, 2 ** ( m + 1 ) ):
            l = n
            for k in range( 1, m + 1 ):
                aa[ k ] = l % 2
                l //= 2
            x = a
            y = b
            for j in range( 1, m + 1 ):
                if aa[ j ] == 0:
                    z = x
                    x = a * x - b * y
                    y = b * z + a * y
                else:
                    z = x
                    x = c * x - d * y + 1 - c
                    y = d * z + c * y - d
            pset( x, y )

    main.mainloop()

if 0:
    markeer( 'Aanvulling 25 C' )

    import tkinter
    import math

    # julia
    # 12913D.pdf blz 23
    # van Basic naar Python

    width, height = 1024, 768
    main = tkinter.Tk()
    canv = tkinter.Canvas( main, width=width, height=height )
    canv.pack()

    offsetx, offsety = 1.8, 1.3
    scalex, scaley = 260, 300

    def pset( x, y ):
        xp = (offsetx + x) * scalex
        yp = (offsety + y) * scaley
        canv.create_oval( xp, yp, xp, yp, width=2 )
        # print( xp, yp )

    def sgn( val ):
        if val == 0:
            return 0
        if val > 0:
            return 1
        return -1

    # p = 3
    p = 12
    # p = 14
    x1 = [ 0 ] * ( p + 1 )
    y1 = [ 0 ] * ( p + 1 )
    x2 = [ 0 ] * ( p + 1 )
    y2 = [ 0 ] * ( p + 1 )

    a, b = 0, 1
    x1[ 0 ] = -1.3002
    y1[ 0 ] = 0.6248
    pset( x1[ 0 ], y1[ 0 ] )
    s = 1

    for m in range( 0, 2 ** ( p - 1 ) ):
        if m != 0:
            s = p
            n = m
            while n % 2 == 0:
                n = n // 2
                s -= 1
            x1[ s - 1 ] = x2[ s - 1 ]
            y1[ s - 1 ] = y2[ s - 1 ]
    
        for j in range( s, p + 1 ):
            x = x1[ j - 1 ]
            y = y1[ j - 1 ]
            r = math.sqrt( ( x - a ) ** 2 + ( y - b ) ** 2 ) / 2
            t = ( x - a ) / 2
            x1[ j ] = math.sqrt( r + t )
            x2[ j ] = - x1[ j ]
            y1[ j ] = math.sqrt( r - t ) * sgn( y - b )
            y2[ j ] = - y1[ j ]
            pset( x1[ j ], y1[ j ] )
            pset( x2[ j ], y2[ j ] )

    main.mainloop()

# Draw the Mandelbrot Set in Python – Real Python
# https://realpython.com/mandelbrot-set-python/

# Mandelbrot Set
# https://tomroelandts.com/articles/mandelbrot-set
# How to Compute the Mandelbrot Set using NumPy Array Operations
# https://tomroelandts.com/articles/how-to-compute-the-mandelbrot-set-using-numpy-array-operations
# How to Compute Colorful Fractals using NumPy and Matplotlib
# https://tomroelandts.com/articles/how-to-compute-colorful-fractals-using-numpy-and-matplotlib

if 0:
    markeer( 'Aanvulling 25 D' )

    import numpy as np
    import matplotlib.pyplot as plt
     
    base = 400
    width = 3 * base
    height = 2 * base

    shape = height, width
    xshape = 1, width
    yshape = height, 1
     
    x = np.linspace( -2, 1, num=width)
    y = np.linspace( -1, 1, num=height)

    x = x.reshape( xshape )
    y = y.reshape( yshape )

    c = np.tile( x, yshape ) + 1j * np.tile( y, xshape )
     
    z = np.zeros( shape, dtype=complex)
    m = np.full( shape, True, dtype=bool)
    n = np.zeros( shape )
    for i in range( 256 ):
        z[ m ] = z[ m ] * z[ m ] + c[ m ]
        m[ np.abs(z) > 2] = False
        n[ m ] = i
     
    fig = plt.figure()
    ax = fig.add_axes( [0, 0, 1, 1], frameon=False, aspect=1)
    ax.set_xticks( [] )
    ax.set_yticks( [] )

    if 1:
        plt.imshow( np.flipud( 1 - m ), cmap='hot' )
        plt.show()

    if 0:
        plt.imshow( np.flipud( n ), cmap='hot' )
        plt.show()

if 0:
    markeer( 'Aanvulling 25 E' )

    import numpy as np
    import matplotlib.pyplot as plt
    import os.path
     
    width = 1024
    height = 768
    scale = 600

    xscale = width / scale
    yscale = height / scale

    shape = height, width
    xshape = 1, width
    yshape = height, 1

    x = np.linspace( -xscale, xscale, num=width )
    y = np.linspace( -yscale, yscale, num=height )

    x = x.reshape( xshape )
    y = y.reshape( yshape )

    z = np.tile( x, yshape ) + 1j * np.tile( y, xshape )
     
    if 1:
        const = -0.4 + 0.6j
    else:
        const = -0.42 + 0.6j
    c = np.full( shape, const )

    m = np.full( shape, True, dtype=bool)

    n = np.zeros( shape )
    for i in range( 256 ):
        z[ m ] = z[ m ] * z[ m ] + c[ m ]
        m[ np.abs( z ) > 2 ] = False
        n[ m ] = i
     
    fig = plt.figure()
    ax = fig.add_axes( [0, 0, 1, 1], frameon=False, aspect=1)
    ax.set_xticks( [] )
    ax.set_yticks( [] )

    plt.imshow( np.flipud( n ), cmap='hot' )
    idx = 0
    while True:
        fname = f'{idx}.png'
        if not os.path.isfile( fname ):
            break
        idx += 1
    plt.savefig(fname)
    plt.show()

# programma om zelf 3D fractals te maken
# Mandelbulber
# https://mandelbulber.com
# https://github.com/buddhi1980/mandelbulber2
# https://github.com/buddhi1980/mandelbulber_doc
# https://github.com/buddhi1980/mandelbulber_doc/releases/tag/2.24.0

# heel veel prachtige fractal afbeeldingen
# Sekino's Fractal Art Gallery
# https://www.sekinoworld.com/fractal/


###############################################################
# Aanvulling 26 SVG

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_svg directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 27 Git en GitHub

# Git is software, een gedistribueerd versiebeheersysteem
# https://git-scm.com
# https://nl.wikipedia.org/wiki/Git_(software)
# https://en.wikipedia.org/wiki/Git

# Git is in 2005 begonnen
#   geschreven door Linus Torvalds voor de Linux kernel
# Git is tegenwoordig het meest gebruikte versie beheer programma

# https://git-scm.com/about
# uitleg, voordelen
#   Branching and Merging
#   Small and Fast
#   Distributed          
#   Data Assurance
#   Staging Area
#   Free and Open Source
#   Trademark

# https://git-scm.com/doc
# documentatie
#   Reference
#   Book
#   Videos
#   External Links

# https://git-scm.com/docs
# Reference
#   Visual Git cheatsheet
#   https://ndpsoftware.com/git-cheatsheet.html
#   welk git command in welke situatie

# https://git-scm.com/book
# Pro Git boek, open source, gratis
# online, PDF, ePub en meer

# default Engels, met link naar PDF en ePub
# https://git-scm.com/book/en/v2
# GitHub repository
# https://github.com/progit/progit2
# rechterzijde Releases, PDF en meer
# https://github.com/progit/progit2/releases/download/2.1.434/progit.pdf

# linkerzijde Nederlands
# https://git-scm.com/book/nl/v2
# GitHub repository
# https://github.com/progit/progit2-nl/
# rechterzijde Releases, PDF en meer
# https://github.com/progit/progit2-nl/releases/download/2.1.117/progit.pdf
# PDF boek Pro Git vertaald in Nederlands
#   uitgebreid 500 blz, 2021-09-28

# https://git-scm.com/doc/ext
# externe links
#   Tutorials
#     GitHub Docs
#     https://docs.github.com/en
#   Books
#     Git Internals Peepcode PDF 2008
#     https://github.com/pluralsight/git-internals-pdf/releases/download/v2.0/peepcode-git.pdf

# https://git-scm.com/downloads
# downloads
#   macOS
#   Linux
#   Windows

# https://git-scm.com/downloads/win
# Download for Windows
#   2.46.2 64-bit 2024-09-24
# https://github.com/git-for-windows/git/releases/download/v2.46.2.windows.1/Git-2.46.2-64-bit.exe

# installeer Git-2.46.2-64-bit.exe
#   zie ook boek Pro Git NL blz 19
# in C:\Program Files\Git, Administrator rechten nodig
#   of
# in C:\Users\<gebruiker>\AppData\Local\Programs\Git
# als directory al bestaat
#   Uninstall bestaande (oudere) versie
#   via Instellingen (Win+I), Apps, zoeken in lijst Git, verwijderen
#   als niet alle files verwenen
#      dan delete directory of rename directory naar .old
# Select Components, Next
# Start Menu Folder Git, Next
# Default editor, Use Notepad, Next
# Initial branch, Override main, Next
# Adjusting path, Git from the command line, Next
# Use bundled OpenSSH, Next
# Use the OpenSSL library, Next
# Checkout Windows-style, commit Unix-style, Next
# Use MinTTY, Next
# Default git pull Fast-forward or merge, Next
# Git Credential manager, Next
# Enable file system cachine, enable symbolic links, Next
# off Enable experimental support pseude consoles, Install
# file:///C:/Users/<gebruiker>/AppData/Local/Programs/Git/ReleaseNotes.html
# Finish

# in Start menu items Git Bash, Git CMD, Git Gui
# maak shortcuts met zelfde items
#   met huidige directory waar bezig

# Git komt van Linux
#   handig om meestal Git Bash te gebruiken

# git-bash is wennen
# plakken lukt met Shift+Ins
#   Ctrl+V werkt niet

# tip: gebruik eigen .bash_history
#   maak bestand git-bash.bat:

# set HOME=%CD%
# set USERPROFILE=%HOME%
# start "" "c:\Program Files\git\git-bash.exe"

# probeer Git

# $ git

# Git klaarmaken voor eerste gebruik
#   zie boek Pro Git NL blz 21 en verder

# $ git config --list

# name en email wordt gebruikt bij elke commit

# $ git config --global user.name "Test Voorbeeld"
# $ git config --global user.email test.voorbeeld@localhost

# config kan op 3 niveaus:
#   /etc/gitconfig    voor iedere gebruiker
#   ~/.gitconfig      per gebruiker
#   .git/config       per repository

# $ git config --list --show-origin
# $ git config user.email

# Git Basics uitproberen
#   gebruik boek Pro Git NL blz 21 en verder

# $ git init <directory naam>
# Initialized empty Git repository in ..

# handmatig delete .git
# aanwezigheid .git directory wijzigt prompt git-bash

# nieuwe bestanden of gewijzigde bestanden
#   toevoegen aan staging area
# staging area wordt ook wel index genoemd

# $ git add <file(s)>

# vaak gebruiken
#   met name bij wijzigingen working directory of staging area

# $ git status

# van staging area naar repository
# legt vast, maakt nieuw commit object
#   complete lijst van bestanden voor deze commit
# opent editor uit config voor beschrijving commit
# regels met # commentaar komen niet in commit

# $ git commit

# opent geen editor
#   gebruikt message als beschrijving

# $ git commit -m "message"

# slaat staging area over

# $ git commit -a -m "message"

# $ git diff <hex1> <hex2>

# $ git log

# .gitignore file
#   lijst met bestanden / directories
#   die niet mogen worden meegenomen
#     door Git in repository
# handig .gitignore wel in repository
#   kan dan ook elders of door anderen gebruikt worden

# Git bewaart alleen bestanden
# kan geen lege directory toevoegen
# emptydir/ zonder iets geen reactie git status
# emptydir/empty.txt 0 bytes wel met git status

# over naar andere versie
# alle bestanden worden aangepast
# git checkout <hex>

# working directory  bestanden
#   untracked
#     nieuw gemaakt
#     bekend in .gitignore
#   tracked
#     unmodified
#     modified
# staging area       tijdelijk in .git database met git add
# repository         vastgelegd in .git database met git commit

# Git werkt veel met SHA-1 hash
#   https://en.wikipedia.org/wiki/SHA-1
# 160 bits, 20 bytes, 40 hex digits
#   vormt checksum, kans op dezelfde zeer klein
#   in gebruik als hex waarde
#   alleen eerste 5 hex digits zijn meestal voldoende
#    voor terugvinden door Git

# hoe zit .git in elkaar
#   zie boek Pro Git NL
#     Git blz 450 en verder Git Binnenwerk

# objecten in repository .git/objects
#  deze wijzigen (meestal?) niet meer 
#   blob      lengte en data, key is SHA-1
#               geen naam
#   tree      lijst met mode, type, SHA-1, naam
#                mode 04000 of 100644 rwx ?
#                type blob, of tree andere directory
#                naam is koppeling bestand en blob
#   commit    size, tree, parent, auteur, committer, message
#               kunnen 0, 1 of meer parents mogelijk
#   tag       geannoteerd, uitgebreider

# inhoud wordt gecomprimeerd opgeslagen met zlib deflate
# bestanden worden samengevoegd als packfiles
# automatisch na enige tijd of handmatig

# $ git gc

# commits kunnen onbereikbaar dangling zijn
# worden met git gc verwijderd

# $ git fsck --full

# referenties refs in repository .git/refs
#  deze kunnen regelmatig wijzigen
#   branch
#   HEAD
#   remotes     zoals origin
#   tags        lichtgewicht, eenvoudig

# gitk
#   voor bekijken repository
#     lijst commits, met branches en tags
#   is wrapper voor git log
#   sorteren op datum is duidelijker

# git gui
# voor info over working directory en staging area

# taggen
#   zie boek Pro Git NL blz 56 en verder

## voorbeelden

# branches
#   zie boek Pro Git NL blz 64 en verder

# maak branch
# git branch <branchnaam>

# git log

# maak branch current, HEAD
# git checkout <branchnaam>

# terug naar main branch
# git checkout main

# git diff main <branchnaam>

# git merge <branchnaam>

# remote branches
#   zie boek Pro Git NL blz 85 en verder
# hebben naam van remote en /
#   zoals origin/main
# kan deze niet zelf wijzigen
#   wijzigen wel bij update vanaf remote

# remotes

# GitHub is een online platform gebaseerd op Git
# https://github.com
# https://nl.wikipedia.org/wiki/GitHub
# https://en.wikipedia.org/wiki/GitHub

# het bedrijf GitHub is opgericht in 2008
#   is in 2018 overgenomen door Microsoft
# groot platform, miljoenen gebruikers en projecten

# als voorbeeld grote Python repository
# https://github.com/python/cpython

# $ git clone https://github.com/python/cpython.git

# maakt nieuwe directory 1 miljoen objecten
#   3 minuten om te downloaden

# $ cd cpython
# $ ls

# .git wordt verborgen bij 'ls'
#   wel te zien bij 'ls -a'
# kan ook bekijken via Gui
#   zoals Windows Explorer
#    of Total Commander

# cpython directory 777MB
#   daarin .git 671 MB

# alle bestanden hebben datum en tijd van clone
# Git bewaart de datum en tijd niet

# tags bekijken

# $ git tag

# bekijk huidige versie
# README bestand eerste regel
# This is Python version ..

# $ git checkout v3.11.8

# alle bestanden in working directory
#   zoals gebruikt bij versie 3.11.8

# checkout ook enkele andere versie tags

# kan zo heel eenvoudig naar source code gaan
#   van elke willekeurige oude versie met tag

# kan naar elke commit
#   probeer met korte hex waarde

# Visual Studio Community 2022
#   installeren via Visual Studio Installer
#   Workloads Python development en
#     Desktop development with C++

# Visual Studio is een ander programma
#    dan Visual Studio Code
# Visual Studio alleen voor Windows (en Mac)
# Visual Studio Code multi platform, ook voor Linux

# run Visual Studio
# open Project / Solution
#   pcbuild.sln
#   in cpython\PCbuild directory

# build Project, mislukt
# run get_externals.bat
#   download extra bestanden
# daarna lukt build

# in cpython\PCbuild\amd64 staat het resultaat
# bij Debug build run python_d.exe
# bij Release build run python.exe

# Python 3.12.0 (main, Sep 26 2024, 14:30:05) [MSC v.1941 64 bit (AMD64)] on win32
# Type "help", "copyright", "credits" or "license" for more information.
# >>>

# datum en tijd in eerste regel
#   laat zien eerder zelf build gedaan

# bekijk cpython repository in Visual Studio
#   via View en Git menus

# Nederlandstalige Git cursussen

# Meneer Depuydt
# https://meneer.depuydt.eu/cursus/
# Versiebeheer met Git PDF 37 blz, augustus 2021
# https://meneer.depuydt.eu/wp-content/uploads/2021/08/Cursus-Git-2021.pdf
# GitHub
# https://github.com/dieterdepuydt

# Git Workshop Nederlands 2015
# https://git-github-workshop-dutchnederlands.readthedocs.io
# GitHub
# https://github.com/diondresschers/git_workshop_dutch_nederlands/

# The Power of Git in a Windows Shell
# https://tortoisegit.org

# aanvulling van Meindert Meindertsma
# SCM patterns
# Samenvatting van Berczuk & Appleton 2003
# Software configuration management patterns
#   zie de PDF download op de website van HCC!programmeren
#   https://programmeren.hcc.nl/ontwikkel/python/python-cursus-voor-beginners


###############################################################
# Aanvulling 28 Oscilloscoop met Matplotlib

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_matplotlib_osc directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 29 DuckDB

# DuckDB
# fast in-process analytical portable
# SQL database management system
# https://duckdb.org
# https://en.wikipedia.org/wiki/DuckDB

# DuckDB in Python - The Next Pandas Killer?
# video van september 2024
# https://www.youtube.com/watch?v=nWxwqxb0FCk
# door NeuralNine
# https://www.neuralnine.com

# begonnen bij CWI in Amsterdam
# presentatie uit 2020
# door Mark Raasveldt en Hannes Mühleisen
# https://www.cwi.nl/documents/195355/duckdb-scientific-meeting.pdf

# bedoeld voor gebruik in Data Science
# nog weinig gebruik gemaakt van SQL en RDBMS
#   Relational Database Management Systems

# geschreven in C++
#   Open Source MIT licentie
# met client interface voor veel programmeertalen
#   C, C++, Go, Java, JavaScript, Julia,
#   Python, R, Rust, Swift, Wasm en meer
# https://duckdb.org/docs/api/overview

# lijkt ietwat op SQLite
#   is nieuwer meer mogelijkheden
#   sluit bijvoorbeeld beter aan op Pandas

# https://duckdb.org/docs/api/python/overview.html

# pip install duckdb

if 0:
    markeer( 'Aanvulling 29 A' )

    import duckdb

    print( duckdb.__version__ ) # 1.1.2

    duckdb.sql("SELECT 42").show()    
    
# DuckDB ondersteunt DB-API 2.0 specificatie van PEP 249
# zoals ook SQLite
# https://duckdb.org/docs/api/python/dbapi

if 0:
    markeer( 'Aanvulling 29 B' )

    import duckdb

    # zie ook Aanvulling 13 B
    # SQLite

    # dbname = 'voorbeeld.duckdb'
    dbname = ':memory:'

    con = duckdb.connect( dbname )
    cur = con.cursor()

    if 0:
        # dynamische types geven Parser Error
        # res = cur.execute( 'create table tabel(a, b, c)' )
        # res = cur.execute( 'Create Table tabel(a, b, c)' )
        # res = cur.execute( 'CREATE TABLE tabel(a, b, c)' )
        # res = cur.execute( 'create table if not exists tabel(a, b, c)' )

        # vaste types wel mogelijk
        res = cur.execute( 'create table tabel(a text, b text, c text)' )

        # res = cur.execute( 'create table tabel(a text, b text, c integer)' )

        # strict geeft Parser Error
        # res = cur.execute( 'create table tabel(a text, b text, c text) strict' )
        # res = cur.execute( 'create table tabel(a text, b text, c integer) strict' )

        print( 'create', res.fetchall() )

    if 0:
        if 0:
            # Binder Error: column "x" not found
            res = cur.execute( 'insert into tabel values ("x", "y", "z")' )

        if 0:
            # enkele aanhalingstekens nodig
            # https://stackoverflow.com/questions/75671499/duckdb-binder-error-referenced-column-not-found-in-from-clause
            res = cur.execute( "insert into tabel values ('x', 'y', 'z')" )

            print( 'insert', res.fetchall() )

        if 0:
            res = cur.execute( "insert into tabel values "
                               "('k', 'l', 'm'),"
                               "('n', 'o', 'p')" )
        if 0:
            res = cur.execute( "insert into tabel values "
                               "('k', 'l', 'm'),"
                               "(11, 12, 13),"
                               "(TRUE, FALSE, True),"
                               "(1.1, 1.2, 1.3),"
                               "(Null, NULL, null),"
                               # https://www.sqlite.org/lang_expr.html
                               # 3. Literal Values, Blob literal
                               "(x'01020304', 0, 0)" # blob, hexadecimal data
                               )

     
    if 0:
        res = cur.execute( 'select * from tabel' )
        print( 'select', res.fetchall() )

    if 0:
        con.commit()

    if 0:
        # Transaction Context Error
        # no transaction is active
        con.rollback()

    con.close()

# DuckDB is stricter dan SQLite
#   strongly typed
# elke kolom heeft 1 bepaald type
# ondersteund veel verschillende types
#   bool, int, float, timestamp, date, time, varchar, blob
# met ook samengestelde types, composite types
#   list, array, map, union, struct, enum, decimal
#   deze kunnen ook genest worden

# https://duckdb.org/docs/api/c/types
# https://duckdb.org/docs/api/python/types

# Awesome DuckDB
# https://github.com/davidgasquez/awesome-duckdb

# DuckDB Guides
# https://duckdb.org/docs/guides/overview

# SQL on Pandas
# https://duckdb.org/docs/guides/python/sql_on_pandas.html

# pip install pandas

if 0:
    markeer( 'Aanvulling 29 C' )

    import pandas
    import duckdb

    dat = { 'temp'  : [ 20.0, 27.2, 23.5, 25.1,  19.7  ],
            'regen' : [    0,    3,    0,    0,     2  ],
            'windr' : [  'z',  'zw', 'z',  'zo', 'zzw' ] }

    frm = pandas.DataFrame( dat )
    print( type( frm ) )
    print( frm )

    frmdb = duckdb.sql( "select * from frm" )
    print( type( frmdb ) )
    print( frmdb )

    frm2 = frmdb.df()
    print( type( frm2 ) )
    print( frm2 )
    

###############################################################
# Aanvulling 30 Web Browser Engineering

# online boek
# https://browser.engineering

# Open Source met MIT licentie
# https://github.com/browserengineering/book

# boek verschijnt eind 2024 op papier

# geschreven door Pavel Panchekha
#   professor University of Utah
#   https://github.com/pavpanchekha
#   https://pavpanchekha.com
# en Chris Harrelson
#   Chrome rendering software engineer Google
#   https://github.com/chrishtr
#   https://twitter.com/chrishtr

# gebruikt Markdown voor tekst
#  en grotendeels Python voor code

# GitHub groene knop <>Code links boven
# Download ZIP
# unzip in book-main directory

# open command prompt in book-main directory
# maak venv
#   python -m venv BookVenv

# activeer venv
#   BookVenv\Scripts\Activate

# installeer benodigde Python packages
#   pip install -r requirements.txt

# met Python 3.12

# ERROR: Could not find a version that satisfies
#   the requirement skia-python==87 (from versions:
#   87.6, 119.0b4, 120.0b5, 121.0b6, 124.0b7, 126.0b8, 128.0b9, 130.0b10)
# ERROR: No matching distribution found for skia-python==87

# wijzig requirements.txt, ==87 weghalen bij skia-python
# # skia-python==87
# skia-python

# lukt deze keer wel
#   pip install -r requirements.txt

# WARNING: The candidate selected for download or install
#   is a yanked version: 'requests' candidate (version 2.32.0
#   at https://files.pythonhosted.org/packages/24/e8/09e8d662a9675a4e4f5dd7a8e6127b463a091d2703ed931a64aa66d00065
#   /requests-2.32.0-py3-none-any.whl (from https://pypi.org/simple/requests/)
#   (requires-python:>=3.8))
# Reason for being yanked: Yanked due to conflicts with
#   CVE-2024-35195 mitigation

# controleren
#   pip list
#   ..
#   requests           2.32.0

# https://pypi.org/project/requests/


# update naar nieuwere versie 2.32.2
#   pip install -U requests

# naar src directory
#   cd src
#   dir *.py

# probeer lab1.py tot en met lab16.py
#  met enkele URLS
# beste (minst slecht) werkt boek website zelf

#   py lab1.py https://browser.engineering
#   ..
#   py lab16.py https://browser.engineering

# of met specifiek hoofdstuk
#   py lab15.py https://browser.engineering/embeds.html

# of met andere website
#   py lab16.py https://spronck.net/pythonbook/dutchindex.xhtml
#   py lab16.py https://en.wikipedia.org/wiki/Hedy

# soms hangt of crasht programma
# stop zetten met Ctrl+C
#   of beter Ctrl+Break
#   of anders via Taakbeheer, Task Manager

# lees boek
#   bekijk online hoofdstukken

# laad Python source in editor
#   bekijk code

# probeer oefeningen exercises

# probeer met eigen web server
#   py server8.py
#   server8, server9, server10, server12
# daarna in ander geactiveerde command prompt
#   py lab8.py http://localhost:8000

# gebruik online test servers

# https://httpbin.org # zie ook Aanvulling 15 Requests

# https://reqbin.com

# andere manier om web browsers te bestuderen
# gebruik F12
#  Webontwikkelaarshulpmiddelen
#  Developer Tools
#   in Chrome, Firefox, Edge, etc

# gebruikte modules

# DukPy
# simple JavaScript interpreter
# https://github.com/amol-/dukpy
# https://pypi.org/project/dukpy

# Skia-python
# Python binding to Skia Graphics Library
# https://github.com/kyamagu/skia-python
# https://pypi.org/project/skia-python

# Skia 2D graphics library
# Open Source van Google
# https://skia.org

# PySDL2
# Python wrapper around SDL2
# https://github.com/py-sdl/py-sdl2
# https://pypi.org/project/PySDL2

# SDL
# Simple DirectMedia Layer
# https://www.libsdl.org
# https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer

# PyOpenGL
# Python binding to OpenGL
# https://pyopengl.sourceforge.net
# https://pypi.org/project/PyOpenGL

# OpenGL
# Open Graphics Library
# http://www.opengl.org
# https://en.wikipedia.org/wiki/OpenGL


###############################################################
# Aanvulling 31 Signalen in MicroPython met CPython simulatie

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_project_mpy directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 32 Quarto en RStudio

# markdown tekst document of Jupyter notebook
#   gebruiken voor
# artikelen, presentaties, websites, boeken, meer

# Quarto
# scientific and technical publishing system
# open source MIT licentie
# https://quarto.org
# https://github.com/quarto-dev/

# command line programma quarto-cli
# https://github.com/quarto-dev/quarto-cli

# Visual Studio Code extensie
# https://github.com/quarto-dev/quarto

# wrapper rondom Pandoc
# a universal document converter
# https://pandoc.org

# gemaakt door Posit PBC
# een open source data science software bedrijf
# naam tot juli 2022 RStudio PBC, tot januari 2020 RStudio Inc.
# https://rstudio.com
#   gaat naar
# https://posit.co
# https://en.wikipedia.org/wiki/Posit_PBC

# RStudio
# data science IDE for R and Python
# open source AGPLv3 licentie
# https://posit.co/products/open-source/rstudio

# Quarto gebruikt .qmd extensie
#   is opvolger van R Markdown
# https://rmarkdown.rstudio.com
# voor gebruikers van R

# The R Project for Statistical Computing
# programmeertaal en omgeving
# gemaakt door de R Foundation
# open source GPL licentie
# https://www.r-project.org
# https://en.wikipedia.org/wiki/R_(programming_language)

# download Quarto CLI
# https://quarto.org/docs/download/
# versie 1.6.39 van 27 november 2024 voor Windows
# https://github.com/quarto-dev/quarto-cli/releases/download/v1.6.39/quarto-1.6.39-win.msi

# run quarto-1.6.39-win.msi Quarto Setup
# voor alle gebruikers in C:\Program Files\Quarto\
# Quarto in path, open command prompt cmd.exe en run
# > quarto -V
# 1.6.39

# download RStudio Desktop
# Open Source Edition, Free
# https://posit.co/downloads/
# https://posit.co/download/rstudio-desktop/
# versie 2024.09.1+394 van 2024-11-04 for Windows
# https://download1.rstudio.org/electron/windows/RStudio-2024.09.1-394.exe
# 64-bits OS nodig
# eerst R installatie nodig

# R repository
# https://cran.rstudio.com/
# https://cran.rstudio.com/bin/windows/base/
# download R-4.4.2 for Windows 64 bit

# run R-4.4.2-win.exe setup, run als Admin
#   klik rechtermuisknop, Als Administrator uitvoeren
# komt dan zelf met default C:\Program Files\R\R-4.4.2

# R niet in path, in command prompt run
# > "c:\Program Files\R\R-4.4.2\bin\R.exe" --version
# R version 4.4.2 (2024-10-31 ucrt) -- "Pile of Leaves"
# ...

# run RStudio-2024.09.1-394.exe setup
# vraagt om Admin rechten C:\Program Files\RStudio
# menu Help, About RStudio
# 2024.09.1 Build 394

# RStudio IDE niet in path, run
# > "c:\Program Files\RStudio\rstudio.exe"
# requires an existing installation of R
# + Use your machine's default 64-bit version of R
# daarna GUI met menu

# snelkoppeling shortcut aan map toevoegen
# taakbalk zoeken of Win+S, intypen rstudio,
#   kies bestandslocatie openen
# sleep drag RStudio regel icon naar map dan drop

# RStudio heeft eigen Quarto
# C:/Program Files/RStudio/resources/app/bin/quarto/bin/quarto.exe
# > quarto -V
# 1.5.57


# voorbeeld met editor zoals Idle of Notepad
# https://quarto.org/docs/get-started/hello/text-editor.html

# cmd.exe prompt

# py -m venv venv
# venv\scripts\activate
# pip install jupyter matplotlib plotly

# copy paste hello.qmd via knop rechtsboven in notepad
# bewaren in directory

# quarto render hello.qmd --to html
# quarto render hello.qmd --to docx

# voor pdf is TinyTeX nodig
# quarto install tinytex

# quarto render hello.qmd --to pdf

# qmd met Python code -> Jupyter -> md -> Pandoc -> html/docx/pdf

# quarto preview hello.qmd

# runt webserver, opent webbrowser
# editor links, browser rechts

# wijzig code in editor, dan bewaren Ctrl+S
#   browser update automatisch


# voorbeeld met RStudio
# https://quarto.org/docs/get-started/hello/rstudio.html

# install.packages in Console binnen RStudio

# qmd met R code -> knitr -> md -> Pandoc -> html/docx/pdf


# voorbeeld met Jupyter
# Jupyter md -> Pandoc -> html/docx/pdf

# voorbeeld met Visual Studio Code en Quarto extensie

# voorbeeld met NeoVim en Quarto plugin


# interactief met JavaScript
# https://quarto.org/docs/interactive/

# interactief met extensie voor Python met Pyodide
# http://quarto.thecoatlessprofessor.com/pyodide/


###############################################################
# Aanvulling 33 TekstKlok

# GUI klok met tijd weergave in spreektaal tekst
#   zie het voorbeeld programma van Theo van Haandel
#   bestand tekstklok.py in PythonCursusXX.zip


###############################################################
# Aanvulling 34 I2C met MicroPython en met Raspberry Pi

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_i2c directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 35 SPI met Raspberry Pi

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_spi directory
#     in PythonCursusXX.zip

# voor de datasheet van de MB85RS64 64K SPI FRAM zie:
# https://www.mouser.com/datasheet/2/1113/MB85RS64_DS501_00012_6v0_E-2329095.pdf

###############################################################
# Aanvulling 36 Simulatie met guizero

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de simulate_guizero directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 37 SPI met Raspberry Pi Pico

# zie de voorbeeld programma's van Anthony Vogelaar
#   in de extra_spi_fram_pico directory
#     in PythonCursusXX.zip

# zie ook extra_spi_fram_pico\0_intro\bytes.py
#   study bytes, bytearray, memoryview, and struct

# voor de datasheet van de MB85RS64 64K SPI FRAM zie:
# https://www.mouser.com/datasheet/2/1113/MB85RS64_DS501_00012_6v0_E-2329095.pdf


###############################################################
# Aanvulling 38 Python op Android

# zie de bestanden van Anthony Vogelaar
#   in de extra_python_on_android directory
#     in PythonCursusXX.zip


###############################################################
# Aanvulling 39 Webapp GUI met Microdot

# zie de bestanden van Anthony Vogelaar
#   in de extra_gui_webapp directory
#     in PythonCursusXX.zip

###############################################################
# Aanvulling 40 The Fuzzing Book

# The Fuzzing Book
# Tools and Techniques for Generating Software Tests
# https://www.fuzzingbook.org

# online boek
# tekst en Python code beschikbaar
# Open Source met Creative Commons licentie
# https://github.com/uds-se/fuzzingbook/

# gebruik menu bovenin om online te lezen

# Sitemap
# https://www.fuzzingbook.org/html/00_Table_of_Contents.html

# open hoofdstuk
# bijvoorbeeld Introduction to Software Testing
# https://www.fuzzingbook.org/html/Intro_Testing.html

# online Jupyter notebooks op binder
# in hoofdstuk, menu Resources, Edit as Notebook
# https://mybinder.org/v2/gh/uds-se/fuzzingbook/HEAD?labpath=docs%2Fnotebooks/Intro_Testing.ipynb
# soms Server ConnectionError
# kan ook op eigen computer

# slides
# in hoofdstuk, menu Resources, View slides
# met pijl-toetsen doorheen
# naar rechts en links voor volgende en vorige
#   omlaag en omhoog als beschikbaar voor vervolg inhoud
# zie <^v> rechtsonder op slide
# https://www.fuzzingbook.org/slides/Intro_Testing.slides.html#/

# code als package met pip
# op Windows lastig te installeren
# pip install fuzzingbook
# ...
# Failed to build pygraphviz

# PyGraphviz nodig
# Python interface to the Graphviz graph layout and visualization package
# https://github.com/pygraphviz/pygraphviz
# Install info
# PyGraphviz requires Graphviz. Please see INSTALL.txt for details.
# https://github.com/pygraphviz/pygraphviz/blob/main/INSTALL.txt

# Graphviz nodig
# INSTALL.txt regel 122
# Windows - may also need to install Visual C/C++

# Visual Studio IDE of Visual Studio Build Tools nodig
# Community Edition for individual use
# https://visualstudio.microsoft.com/
# https://visualstudio.microsoft.com/visual-cpp-build-tools/
# Visual Studio Installer
#   kies Desktop Development with C++
#   enkele GB download en diskruimte nodig
#   Admin rechten en restart nodig

# Graphviz
# INSTALL.txt regel 131
# manual download and install Graphviz
# https://graphviz.org
# https://graphviz.org/download/
# graphviz-12.2.1 (64-bit) EXE installer
# windows_10_cmake_Release_graphviz-install-12.2.1-win64.exe
# download en installeer Graphviz, admin rechten nodig

# daarna PyGraphviz install
# > py -m venv Venv
# > Venv\Scripts\activate
# > pip install pygraphviz
# Successfully installed pygraphviz-1.14

# dan Fuzzing Book install
# > pip install fuzzingbook
# ...
# ... download en install veel packages
# ... enkele minuten bezig
# ...
# Successfully installed Markdown-3.7 ...
#  ...
#  ... fuzzingbook-1.2.2 ...
#  ...
#  ... z3-solver-4.11.2.0

# probeer code
# run IDLE in venv
# > py -m idlelib
# open dan dit extra .py bestand

if 0:
    markeer( 'Aanvulling 40 A' )

    import fuzzingbook

    print( fuzzingbook.__doc__ )

if 0:
    markeer( 'Aanvulling 40 B' )

    # zoals in Introduction to Software Testing
    # https://www.fuzzingbook.org/html/Intro_Testing.html

    from time import sleep
    from fuzzingbook.ExpectError import ExpectTimeout

    with ExpectTimeout( 3 ):
        try:
            for i in range( 10 ):
                print( '*', end = '' )
                sleep( 1 )
            print( 'Done' )
        except TimeoutError:
            print( 'Timeout' )


###############################################################
# Aanvulling 41 PySDR

# PySDR: A Guide to SDR and DSP using Python
# https://pysdr.org

# online boek
# tekst en Python code beschikbaar
# Open Source met Creative Commons licentie
# https://github.com/777arc/PySDR

# auteur Dr. Marc Lichtman
# Adjunct Professor at the University of Maryland
# https://pysdr.org/content/about_author.html

# gebruik inhoudsopgave links om boek te lezen
# 23 hoofdstukken
# wordt nog steeds aan gewerkt

# enkele vertalingen beschikbaar waaronder Nederlands
# rechtsboven Dutch
# https://pysdr.org/nl/index-nl.html
# 18 hoofdstukken, niet helemaal actueel

# 2. Frequency Domain
# https://pysdr.org/content/frequency_domain.html
# 2. Het frequentiedomein
# https://pysdr.org/nl/content-nl/frequency_domain.html

# boek is niet interactief
# kan voorbeeld Python code niet online uitproberen

# > pip install pysdr
# werkt wel maar is niet zinvol
# https://pypi.org/project/pysdr/#history
# laatste update in 2018

# code download van GitHub
# groene knop rechtsboven Code
#   onderaan Download ZIP
# of rechts Releases
#   Assets, Source code (zip)

# in zip
# figure-generating-scripts\fft_in_python.py
# voorbeeld code voor figuren in hoofdstuk 2
# maak kopie in werkdirectory

# > py -m venv Venv
# > Venv\Scripts\activate
# > pip install numpy matplotlib
# > py -m idlelib

# open fft_in_python.py in IDLE

# run
# FileNotFoundError: [Errno 2] No such file or directory: '../_images/fft-python1.svg'

# maak directory ..\images
# of wijzig regels met ../_images/, verwijder path

# run ok
# maakt 3 .svg bestanden
# en opent 3 plots zoals in online boek

# van dezelfde auteur
# IQEngine
# online SDR toolkit
# met voorbeeld RF recordings
# https://iqengine.org


###############################################################
# Aanvulling 42 Thonny en MicroPython op een microcontroller zetten

# zie het bestand windows_cp210x.txt van Anthony Vogelaar
#   in de extra_starter_kit\1_docs directory
#     in PythonCursusXX.zip

# zie ook Aanvulling 44 http.server en print naar lcd


###############################################################
# Aanvulling 43 turtle

# standaard Turtle graphics module
#  https://docs.python.org/3/library/turtle.html

# maakt gebruik van de standaard tkinter module
# soms nodig deze te installeren
#   zie hiervoor Aanvulling 6 Tkinter

# lijkt op tekenen met Hedy
#   bijvoorbeeld https://hedy.org/hedy#turtle
#   zie hiervoor Aanvulling 0 Hedy

# uitleg voor beginners
# The Beginner's Guide to Python Turtle – Real Python
#  https://realpython.com/beginners-guide-python-turtle/
#   doornemen in IDLE
#   code niet overnemen vanwege auteursrecht

# ook als video in 10 delen
#  vanaf deel 4 alleen met betaald account
# Python Turtle for Beginners (Overview) (Video) – Real Python
#  https://realpython.com/videos/python-turtle-overview/

# Turtle demo programma's
# https://docs.python.org/3/library/turtle.html#module-turtledemo
# > python -m turtledemo
# of in IDLE, menu Help, onderaan Turtle Demo

if 0:
    markeer( 'Aanvulling 43 A' )

    # voorbeeld tekening
    import turtle

    def vierkant(trt):
        trt.pendown()
        trt.forward( 100 )
        trt.right( 90 )
        trt.forward( 100 )
        trt.right( 90 )
        trt.forward( 100 )
        trt.right( 90 )
        trt.forward( 100 )
        trt.right( 90 )
        trt.penup()

    t1 = turtle.Turtle()
    t1.penup()
    t1.pencolor( 'green' )
    t1.left( 45 )
    vierkant( t1 )

    t2 = turtle.Turtle()
    t2.penup()
    t2.forward( 50 )
    t2.pencolor( 'red' )
    vierkant( t2 )

    # t1.hideturtle()
    # t1.showturtle()

    if 0:
        # bewaar plaatje als postscript bestand
        # python - Turtle module - Saving an image - Stack Overflow
        # https://stackoverflow.com/questions/4071633/turtle-module-saving-an-image
        canvas = turtle.getscreen().getcanvas()
        canvas.postscript( file='turtle.eps' )

        # voor conversie van postscript naar JPEG
        # Introduction to Programming in Python - Turtle Graphics - summer15-turtle-graphics.pdf
        #  https://www.cs.utexas.edu/~byoung/summer-python-class/summer15-turtle-graphics.pdf
        # gebruikt PIL module
        #  zie Aanvulling 4 Pillow / PIL

    if 1:
        turtle.done()


###############################################################
# Aanvulling 44 Keyestudio ESP32 Inventor Starter Kit FKS0001

# zie de bestanden van Anthony Vogelaar
#   in de extra_starter_kit directory
#     in PythonCursusXX.zip

# zie ook Aanvulling 42 Thonny en MicroPython op een microcontroller zetten


###############################################################
# Aanvulling 45 Pyodide

# Pyodide
# Python distributie voor webbrowsers en Node.js
# Pyodide is een port van standaard Python (CPython) naar Webassembly
# de C code van CPython is gecompileerd met Emscripten
# https://pyodide.org

# online Python REPL
# https://pyodide.org/en/stable/console.html
# nadelen: geen editor, kan wel plakken

# Emscripten
# C/C++ compiler met WebAssembly output
# is gebaseerd op LLVM
# https://emscripten.org
# https://llvm.org

# WebAssembly wordt ondersteund door de meeste webbrowsers
# (nog) niet toegestaan om rechtstreeks de DOM te manipuleren
#   moet via JavaScript
# https://webassembly.org
# https://en.wikipedia.org/wiki/WebAssembly

# kan Pyodide bestanden downloaden en installeren
# maakt het lastiger te gebruiken
# is niet nodig bij gebruik in webbrowser

if 0:
    markeer( 'Aanvulling 45' )

    def WebServe( html ):
        fname = 'index.html'

        with open( fname, 'w' ) as outf:
            outf.write( html )

        addr, port = 'localhost', 8001
        addrport = addr, port
        
        # url = fname
        url = 'http://' + addr + ':' + str(port) + '/' + fname
        print( 'Link', url )
       
        if 1:
            # https://docs.python.org/3/library/webbrowser.html#webbrowser.open
            import webbrowser

            webbrowser.open( url )

        if 1:
            import os
            print( 'Huidige directory', os.getcwd() )
        
            # https://docs.python.org/3/library/http.server.html#http.server.SimpleHTTPRequestHandler
            import http.server
            import socketserver
            import time

            handler = http.server.SimpleHTTPRequestHandler
            with socketserver.TCPServer( addrport, handler ) as server:
                print( 'Server gestart op', addr, port )
                print( 'Stop met Ctrl+C' )
                server.serve_forever()

    htmlFmt = """\
<html>
 <head>
  <script src="https://cdn.jsdelivr.net/pyodide/v0.28.1/full/pyodide.js"></script>
 </head>
 <body>
  <p id="idtest">Pyodide test</p>
  <script>
{}
  </script>
 </body>
</html>
"""
    # dubbele accolades {{ }} nodig voor .format
    # https://docs.python.org/3/library/string.html#format-string-syntax

    # `` nodig voor Python code
    # pyodide.runPython("{}");  werkt alleen met enkele regel Python code
    # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

    if 1:
        jsFmt1 = """\
        async function main(){{
          let pyodide = await loadPyodide();
          pyodide.runPython(`{}`);
        }};
        main();"""
        py1 = "print('Hello World in Python voorbeeld 1')"
        py2 =  """
            print('Voorbeeld 2 import js')

            import js
            js.document.title = 'Titel aangepast voorbeeld 2'
            if True:
            """
        py3 =  """
                print('Voorbeeld 3 tekst aanpassen')

                import js
                js.document.title = 'Titel aangepast voorbeeld 3'

                js.document.getElementById('idtest').innerText = "Gewijzigd3"
            """
        py4 = py2 + py3
        js = jsFmt1.format( py1 )  # py2, py3, ...
        html = htmlFmt.format( js )
        WebServe( html )

    if 0:
        # BigInt
        # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt

        jsFmt2 = """\
        var strvar = 'VoorbeeldTekst';
        var numvar = 12321;
        var floatvar = 123.456;
        var bigvar1 = 1234567890987654321;
        var bigvar2 = 1234567890987654321n;
        var boolvar = true;
        var undefinedvar = undefined;
        var nullvar = null;

        async function main(){{
          let pyodide = await loadPyodide();
          pyodide.runPython(`{}`);
        }};
        main();"""
        py11 =  """
            import js

            js.document.title = 'Titel 11'
            print('Voorbeeld 11')

            strvar = js.strvar
            print( 'strvar', strvar, type( strvar ) )

            numvar = js.numvar
            print( 'numvar', numvar, type( numvar ) )

            floatvar = js.floatvar
            print( 'floatvar', floatvar, type( floatvar ) )

            bigvar1 = js.bigvar1
            print( 'bigvar1', bigvar1, type( bigvar1 ) )

            bigvar2 = js.bigvar2
            print( 'bigvar2', bigvar2, type( bigvar2 ) )

            boolvar = js.boolvar
            print( 'boolvar', boolvar, type( boolvar ) )

            undefinedvar = js.undefinedvar
            print( 'undefinedvar', undefinedvar, type( undefinedvar ) )

            nullvar = js.nullvar
            print( 'nullvar', nullvar, type( nullvar ) )
            """

        js = jsFmt2.format( py11 )
        html = htmlFmt.format( js )
        WebServe( html )

    if 0:
        jsFmt3 = """\
        var lstvar = [ 'Tekst1', 'tekst2' ];
        var dctvar = {{ 7 : 'tkst10', 12 : 'tkst32' }};
        var lstvar = [ 'tekstA', {{ 'test' : 5, 'test2' : 6 }}, 'TekstB' ];

        async function main(){{
          let pyodide = await loadPyodide();
          pyodide.runPython(`{}`);
        }};
        main();"""
        py21 =  """
            import js

            js.document.title = 'Titel 21'
            print('Voorbeeld 21')

            if 1:
                lstvar = js.lstvar
                print( 'lstvar', lstvar, type( lstvar ) )

                if 1:
                    lst = lstvar.to_py()
                    print( 'lst', lst, type( lst ) )

            if 1:
                dctvar = js.dctvar
                print( 'dctvar', dctvar, type( dctvar ) )

                if 1:
                    dct = dctvar.to_py()
                    print( 'dct', dct, type( dct ) )
            """

        js = jsFmt3.format( py21 )
        html = htmlFmt.format( js )
        WebServe( html )

    if 0:
        jsFmt4 = """\
        async function main(){{
          let pyodide = await loadPyodide();
          pyodide.runPython(`{}`);
          let testvrs = pyodide.globals.get('testvars');
          console.log(testvrs.toJs());
          console.log(testvrs.toJs( {{dict_converter: Object.fromEntries}}));
        }};
        main();"""
        py31 =  """
            import js

            js.document.title = 'Titel 31'
            print( 'Voorbeeld 31' )
            testvars = [ 'pyA', { 'testP' : 15, 'testP2' : 6 }, 'tekstP' ]
            """
        js = jsFmt4.format( py31 )
        html = htmlFmt.format( js )
        WebServe( html )

    if 0:
        jsFmt5 = """\
        async function main(){{
          let pyodide = await loadPyodide();
          await pyodide.loadPackage("numpy");
          // await pyodide.loadPackage(["numpy", "requests"]);
          pyodide.runPython(`{}`);
        }};
        main();"""
        py41 =  """
            import js

            js.document.title = 'Titel 31'
            print( 'Voorbeeld 31' )

            import numpy
            print( numpy )
            np = numpy
            # numpy code overgenomen uit Aanvulling 21 C
            if 1:
                deg = 95
                # deg = 190
                # deg = 380
                # deg = 360 * 5
                # deg = 360 * 500

                ardeg = np.arange( deg )
                # ardeg = np.arange( deg, step=10 )

            if 1:
                print( ardeg )

            arrad = np.deg2rad( ardeg )
            if 1:
                print( arrad )
            
            arsin = np.sin( arrad )
            if 1:
                print( arsin )

            arcos = np.cos( arrad )
            if 1:
                print( arcos )

            """
        js = jsFmt5.format( py41 )
        html = htmlFmt.format( js )
        WebServe( html )

# Pyodide Complete example
#  en daaronder Alternative example
# https://pyodide.org/en/stable/usage/quickstart.html#complete-example

# Building a Single Page Application with Python and Pyodide - Part 1
# https://testdriven.io/blog/build-spa-with-python-part-1/

# GitHub releases
# https://pyodide.org/en/stable/usage/downloading-and-deploying.html#github-releases

# Related Projects
# https://pyodide.org/en/stable/project/related-projects.html
# zoals Iodide, Starboard notebook


###############################################################
# Aanvulling 46 uv

# uv
# An extremely fast Python package and project manager
#   written in Rust

# uitgebreide documentatie
# https://docs.astral.sh/uv/

# meer uitleg

# Managing Python Projects With uv:
#   An All-in-One Solution
# https://realpython.com/python-uv/

# Ultimate guide to uv library in Python
# https://deepnote.com/blog/ultimate-guide-to-uv-library-in-python


# installatie op Windows

# volgens documentatie
# PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# of eerst download met webbrowser:
#   https://astral.sh/uv/install.ps1
# resulteert in download
#   uv-installer.ps1
# bekijken
# run powershell installatie
# c:\Windows\System32\WindowsPowerShell\v1.0\
#  powershell.exe -ExecutionPolicy ByPass .\uv-installer.ps1

# Downloading uv 0.9.3 (x86_64-pc-windows-msvc)
# Installing to C:\Users\Windows User\.local\bin
#   uv.exe
#   uvx.exe
#   uvw.exe
# everything's installed!
# 
# To add C:\Users\Windows User\.local\bin to your PATH, either restart your shell or run:
#
#     set Path=C:\Users\Windows User\.local\bin;%Path%   (cmd)
#     $env:Path = "C:\Users\Windows User\.local\bin;$env:Path"   (powershell)

# installatie op Linux

#  of Windows Subsystem for Linux (wsl)
#   installatie via wsl.exe (Administrator), reboot
#    wsl.exe --list --online
#    wsl.exe --install Ubuntu
#    default Unix user account: user, password: ..
# in Windows start menu erbij:
#   Ubuntu
#   WSL
#   Welcome to WSL
#   WSL Settings

# volgens documentatie
# $ curl -LsSf https://astral.sh/uv/install.sh | sh

# of eerst download:
# $ curl -LsSf https://astral.sh/uv/install.sh > out
# bekijken
# $ less out
# dan pas installeren
# $ sh < out

# downloading uv 0.9.3 x86_64-unknown-linux-gnu
# no checksums to verify
# installing to /home/user/.local/bin
#   uv
#   uvx
# everything's installed!
#
# To add $HOME/.local/bin to your PATH, either restart your shell or run:
#
#     source $HOME/.local/bin/env (sh, bash, zsh)
#     source $HOME/.local/bin/env.fish (fish)

# restart shell

# uitproberen
#  in cmd, powershell, wsl, bash

# $> uv
# An extremely fast Python package manager.
# 
# Usage: uv [OPTIONS] <COMMAND>
# 
# Commands:
#   auth     Manage authentication
#   run      Run a command or script
# ...

# $> uv --version
# uv 0.9.3 (83635a6c4 2025-10-15)

# $> uv self update
# info: Checking for updates...
# success: You're on the latest version of uv (v0.9.3)

# installatie methodes hierboven
#   hebben geen werkende Python installatie nodig

# andere manieren voor installeren
#  vanaf Pypi met pip of pipx, Homebrew, WinGet,
#   Scoop, Docker, GitHub, Cargo
#  zie https://docs.astral.sh/uv/getting-started/installation/

# op Windows
# c:\Users\<user>\.local\bin\ directory
#  toegevoegd aan path, daarin 3 bestanden:
#    uv.exe, uvw.exe, uvx.exe

# op Linux
# /home/<user>/.local/bin directory
#  toegevoegd aan PATH, daarin 4 bestanden:
#    uv, uvx, env, env.fish


# uv vervangt veel andere Python package en project mangement programma's
# pip, pip-tools, pipx, poetry, pyenv, twine, venv, virtualenv, ..

# uv werkt met projecten
#   hieronder als voorbeeld
#   testpy

# hieronder als voorbeeld Python 3.12.1
#  al eerder geïnstalleerd
#  mag ook andere versie zijn
# installatie vooraf hoeft niet
#   uv kan ook Python versies installeren
#     zie verder hieronder

# $> uv init testpy
# maakt subdirectory testpy
# met daarin bestanden
#     .python-version
#     main.py
#     pyproject.toml
#     README.md
#  als git aanwezig is dan
#    ook .git directory en .gitignore file
# ga naar project directory
# $> cd testpy

# kan ook bestaande directory gebruiken
# $> uv init

# bekijk bestanden
# wijzig main.py en/of meer

# run programma

# $> uv run main.py
# Using CPython 3.12.1 interpreter at: C:\Program Files\Python312\python.exe
# Creating virtual environment at: .venv
# Hello from testpy!

# toont eerste keer Python versie
#  en maakt automatisch een virtual environment aan
#   en bestand uv.lock

# volgende keren niet meer nodig

# $> uv run main.py
# Hello from testpy!

# pyproject.toml is voor eigen gebruik, toml formaat
#   standaard Python formaat
#   https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
    # uv.lock is voor uv intern gebruik, toml formaat
#   dit bestand niet wijzigen
#   https://docs.astral.sh/uv/concepts/projects/layout/

# edit main.py
# voeg import numpy en print(numpy.__version__) toe

# import numpy
#
# def main():
#     print("Hello from testpy!")
# 
#     print(numpy.__version__)

# $> uv run main.py
# Traceback (most recent call last):
#   File "...\main.py", line 1, in <module>
#     import numpy
# ModuleNotFoundError: No module named 'numpy'

# numpy nog niet beschikbaar

# $> uv add numpy
# Resolved 2 packages in 253ms
# Prepared 1 package in 2.27s
# ░░░░░░░░░░░░░░░░░░░░ [0/1] Installing wheels...
# warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
#          If the cache and target directories are on different filesystems, hardlinking may not be supported.
#          If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
# Installed 1 package in 469ms
#  + numpy==2.3.4

# soms hardlink warnings bij gebruik andere dan default install drive
#   kan deze warnings negeren
#   todo: hoe uv cache directory verplaatsen

# bekijk project.toml
#   numpy in dependencies
# en uv.lock
#  wheels erbij

# $> uv run main.py
# Hello from testpy!
# 2.3.4

# kan later aanpassen
# $> uv add --update numpy
# error: unexpected argument '--update' found
# 
#   tip: a similar argument exists: '--upgrade'
#
# Usage: uv add --upgrade <PACKAGES|--requirements <REQUIREMENTS>>
#
# For more information, try '--help'.

# $> uv add --upgrade numpy
# Resolved 2 packages in 100ms
# Audited 1 package in 0.26ms

# of verwijderen
# $> uv remove numpy
# Resolved 1 package in 12ms
# Uninstalled 1 package in 97ms
#  - numpy==2.3.4

# dan wel weer foutmelding bij uv run
# update met specifieke versie
# $> uv add numpy==2.1
# ..

# $> uv run main.py
# Hello from testpy!
# 2.1.0

# $> uv add --upgrade numpy

# blijft op numpy 2.1.0

# $> uv run main.py
# Hello from testpy!
# 2.1.0

# wijzig pyproject.toml
#  van "numpy==2.1"
# naar "numpy>=2.1"

# daarna
# $> uv add --upgrade numpy
# nu wel numpy 2.3.4

# uv run main.py
# Hello from testpy!
# 2.3.4

# rename of verwijder
#  .venv directory en uv.lock
# bijvoorbeeld naar
#  .venv.old en uv.lock.old

# $> uv run main.py
# Using CPython 3.12.1 interpreter at: C:\Program Files\Python312\python.exe
# Creating virtual environment at: .venv
# ░░░░░░░░░░░░░░░░░░░░ [0/1] Installing wheels...
# warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
#          If the cache and target directories are on different filesystems, hardlinking may not be supported.
#          If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
# Installed 1 package in 477ms
# Hello from testpy!
# 2.3.4

# uv maakt Virtual Environment .venv
#   en package info in uv.lock vanzelf weer opnieuw aan
# packages op Pypi hebben deze niet nodig

# uv vervangt ook pip
#   kan pip commands in uv gebruiken

# $> uv pip list
# Package Version
# ------- -------
# numpy   2.3.4

# $> uv pip freeze
# numpy==2.3.4

# uv kan ook Python installeren
# $> uv python list
# uv python list
# cpython-3.15.0a1-windows-x86_64-none                 <download available>
# cpython-3.15.0a1+freethreaded-windows-x86_64-none    <download available>
# cpython-3.14.0-windows-x86_64-none                   <download available>
# cpython-3.14.0+freethreaded-windows-x86_64-none      <download available>
# ..
# cpython-3.12.1-windows-x86_64-none                   C:\Program Files\Python312\python.exe
# cpython-3.11.14-windows-x86_64-none                  <download available>
# ..
# pypy-3.11.13-windows-x86_64-none                     <download available>
# ..
# graalpy-3.12.0-windows-x86_64-none                   <download available>

# $> uv python install
# uv python install
# Installed Python 3.12.12 in 6.58s
#  + cpython-3.12.12-windows-x86_64-none (python3.12.exe)

# $>uv python list
# ..
# cpython-3.12.12-windows-x86_64-none                  C:\Users\<user>\AppData\Roaming\uv\python\cpython-3.12.12-windows-x86_64-none\python.exe
# ..

# voeg regels toe aan main.py
# in begin
# import sys
# en in main
#     print(sys.version)

# $> uv run main.py
# Hello from testpy!
# 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)]
# 2.3.4

# gebruikt nog 3.12.1
#   kiest de eerste die
# https://docs.astral.sh/uv/concepts/python-versions/

# $> uv run main.py --python 3.12.12
# Hello from testpy!
# 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)]
# 2.3.4

# gebruikt nog steeds 3.12.1

# wijzig pyproject.toml
# van
#   requires-python = ">=3.12"
# naar
#   requires-python = ">=3.12.12"

# $> uv run main.py
# Using CPython 3.12.12
# Removed virtual environment at: .venv
# Creating virtual environment at: .venv
# ░░░░░░░░░░░░░░░░░░░░ [0/1] Installing wheels...
# warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
#          If the cache and target directories are on different filesystems, hardlinking may not be supported.
#          If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
# Installed 1 package in 409ms
# Hello from testpy!
# 3.12.12 (main, Oct 14 2025, 21:24:50) [MSC v.1944 64 bit (AMD64)]
# 2.3.4

# nu wel, maakt nieuwe virtual environment
# nog een keer

# $>uv run main.py
# Hello from testpy!
# 3.12.12 (main, Oct 14 2025, 21:24:50) [MSC v.1944 64 bit (AMD64)]
# 2.3.4

# rename .venv directory
#   bijvoorbeeld naar .venv.old2

# $> uv venv
# Using CPython 3.12.12
# Creating virtual environment at: .venv
# Activate with: .venv\Scripts\activate

# maakt weer nieuwe .venv

# $> uv run main.py
# ░░░░░░░░░░░░░░░░░░░░ [0/1] Installing wheels...
# warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
#          If the cache and target directories are on different filesystems, hardlinking may not be supported.
#          If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
# Installed 1 package in 456ms
# Hello from testpy!
# 3.12.12 (main, Oct 14 2025, 21:24:50) [MSC v.1944 64 bit (AMD64)]
# 2.3.4


###############################################################
# Aanvulling 47 Tabs en spaties

# implementaties tabsToSpaces en spacesToTabs

if 0:
    markeer( 'Aanvulling 47' )

    # voorbeeld tekst met tabs
    #   uit extra_starter_kit\15_morse_rmt\morse_writer_rmt.py
    # IDLE gebruikt tab size 8
    #   kolommen niet goed uitgelijnd aligned in IDLE
    # Thonny gebruikt tabsize 4
    #   gemaakt met Thonny, ziet er daar wel goed uit

    voorbeeld = """
morse_codes = {
	'A': '.-',		'B': '-...',	'C': '-.-.',	'D': '-..',
	'E': '.',		'F': '..-.',	'G': '--.',		'H': '....',
	'I': '..',		'J': '.---',	'K': '-.-',		'L': '.-..',
	'M': '--',		'N': '-.',		'O': '---',		'P': '.--.',
	'Q': '--.-',	'R': '.-.',		'S': '...',		'T': '-',
	'U': '..-',		'V': '...-',	'W': '.--',		'X': '-..-',
	'Y': '-.--',	'Z': '--..',
	'1': '.----',	'2': '..---',	'3': '...--',	'4': '....-',
	'5': '.....',	'6': '-....',	'7': '--...',	'8': '---..',
	'9': '----.',	'0': '-----'}
"""
    def tabsToSpaces( txt, tabsize=4 ):
        return txt

    if 0:
        print( voorbeeld )
        print( tabsToSpaces( voorbeeld ) )
    
    def tabsToSpaces2( txt, tabsize=4 ):
        return txt.expandtabs( tabsize )

    if 0:
        print( voorbeeld )
        print( tabsToSpaces2( voorbeeld ) )
    
    def tabsToSpaces3( txt, tabsize=4 ):
        res = ''
        pos = 0
        for ch in txt:
            if ch == '\t':
                cnt = tabsize - pos % tabsize
                res += ' ' * cnt
                pos += cnt
            else:
                res += ch
                if ch == '\n':
                    pos = 0
                else:
                    pos += 1
        return res

    if 0:
        print( voorbeeld )
        res2 = tabsToSpaces2( voorbeeld )
        res3 = tabsToSpaces3( voorbeeld )
        print( res2 )
        print( res3 )
        print( res2 == res3 )

    # gebruik .find methode
    #   in plaats van telkens 1 karakter tegelijk
    # gebruik met .find is lastig als ook \n controle nodig
    #   beter eerst losse regels van maken
    # tabsToSpacesLine negeert \n
    def tabsToSpacesLine( line, tabsize=4 ):
        res = ''
        inpos = 0
        outpos = 0
        size = len( line )
        while inpos < size:
            infnd = line.find( '\t', inpos )
            if infnd < 0:
                break
            inlen = infnd - inpos
            cnt = tabsize - (outpos + inlen) % tabsize
            res += line[ inpos : infnd ] + ' ' * cnt
            outpos += inlen + cnt
            inpos = infnd + 1
        res += line[ inpos : ]
        return res

    def tabsToSpaces4( txt, tabsize=4 ):
        lines = txt.split('\n')
        cnt = len( lines )
        res = ''
        for idx, line in enumerate( lines ):
            res += tabsToSpacesLine( line, tabsize )
            if idx + 1 < cnt: # geen \n bij laatste
            # if True:
                res += '\n'
        return res
    
    if 0:
        print( voorbeeld )
        res2 = tabsToSpaces2( voorbeeld )
        res4 = tabsToSpaces4( voorbeeld )
        print( res2 )
        print( res4 )
        print( res2 == res4 )
        print( repr( res2 ) )
        print( repr( res4 ) )

    def tabsToSpaces5( txt, tabsize=4 ):
        res = []
        for line in txt.split('\n'):
            res.append( tabsToSpacesLine( line, tabsize ) )
        return '\n'.join( res )

    if 0:
        print( voorbeeld )
        res2 = tabsToSpaces2( voorbeeld )
        res5 = tabsToSpaces5( voorbeeld )
        print( res2 )
        print( res5 )
        print( res2 == res5 )
        # print( repr( res2 ) )
        # print( repr( res5 ) )

    # zie ook 12.8 List comprehensions
    def tabsToSpaces6( txt, tabsize=4 ):
        res = [ tabsToSpacesLine( line, tabsize )
                for line in txt.split('\n') ]
        return '\n'.join( res )

    # zie ook 23.2.1 Generator expressies
    def tabsToSpaces7( txt, tabsize=4 ):
        res = ( tabsToSpacesLine( line, tabsize )
                for line in txt.split('\n') )
        return '\n'.join( res )

    def tabsToSpaces8( txt, tabsize=4 ):
        return '\n'.join( [ tabsToSpacesLine( line, tabsize )
                          for line in txt.split('\n') ] )

    def tabsToSpaces9( txt, tabsize=4 ):
        return '\n'.join( ( tabsToSpacesLine( line, tabsize )
                          for line in txt.split('\n') ) )

    def tabsToSpaces10( txt, tabsize=4 ):
        return '\n'.join( tabsToSpacesLine( line, tabsize )
                          for line in txt.split('\n') )

    if 0:
        print( voorbeeld )
        res2 = tabsToSpaces2( voorbeeld )
        res6 = tabsToSpaces6( voorbeeld )
        res7 = tabsToSpaces7( voorbeeld )
        res8 = tabsToSpaces8( voorbeeld )
        res9 = tabsToSpaces9( voorbeeld )
        res10 = tabsToSpaces10( voorbeeld )
        print( res2 )
        # print( res6 )
        # print( res7 )
        # print( res8 )
        # print( res9 )
        # print( res10 )
        print( res2 == res6 )
        print( res2 == res7 )
        print( res2 == res8 )
        print( res2 == res9 )
        print( res2 == res10 )
        # print( repr( res2 ) )
        # print( repr( res6 ) )
        # print( repr( res7 ) )
        # print( repr( res8 ) )
        # print( repr( res9 ) )
        # print( repr( res10 ) )

    # todo: benchmark Tabs2Spaces met find
    #   misschien met .find toch trager
    #    dan elk karakter nakijken
    #   omdat bij .find splitsen in regels nodig
    #    voor \t zoeken in regels zonder \n
    # todo: gebruik lijst van kolom posities
    #   dan meer variatie in tab gebruik
    #    en beperkt aantal kolommen mogelijk
    #   bijvoorbeeld:
    #    tabpos = 0, 4, 8, 12, 16
    #    tabpos = 0, 8, 12, 16

    def spacesToTabs( txt, tabsize=4 ):
        return txt

    # voor spacesToTabs bestaat geen ingebouwde str method
    #   wel omgekeerd .expandtabs voor tabs naar spaties

    def spacesToTabs2( txt, tabsize=4 ):
        res = ''
        pos = 0
        spc = 0 # bewaar aantal spaties zolang niet op tab positie
        # spc = ''  # had ook spaties zelf in string kunnen bewaren
        for ch in txt:
            if ch == ' ':
                spc += 1
                pos += 1
                if pos % tabsize == 0:
                    res += '\t'
                    spc = 0
            else:
                if spc: # spaties voor tab positie wegwerken
                    res += ' ' * spc
                    spc = 0
                res += ch
                if ch == '\n':
                    pos = 0
                else:
                    pos += 1
        res += ' ' * spc
        return res

    if 0:
        print( voorbeeld )
        spac = tabsToSpaces2( voorbeeld )
        tabs = spacesToTabs2( spac )
        print( spac )
        print( tabs )

        if 0:
            print( "1", voorbeeld == tabs )
            # voorbeeld bevat nog spaties die tabs hadden kunnen zijn
            if 0:
                print( repr( voorbeeld ) )
                print( repr( tabs ) )

        if 0:
            spac2 = tabsToSpaces2( tabs ) # na spacesToTabs2
            print( "2", spac == spac2 )
            if 0:
                print( repr( spac ) )
                print( repr( spac2 ) )
            
            if 0:
                tabs2 = spacesToTabs2( spac2 ) # na tabsToSpaces2
                print( "3", tabs == tabs2 )
                if 0:
                    print( repr( tabs ) )
                    print( repr( tabs2 ) )

    # spacesToTabs met .find
    # todo: nakijken spaties aan einde naar tabs of niet
    #   doet wel, is dat goed, andersom niet?!
    def spacesToTabsLine( line, tabsize=4 ):
        res = ''
        pos = 0
        size = len( line )
        while pos < size:
            fnd = line.find( ' ', pos )
            if fnd < 0:
                break
            spc = fnd + 1
            # versie 0.129 en ervoor had bug
            #   in volgende 2 regels met while en if
            #   opgelost in versie 0.130
            # minder dan tabsize spaties aan einde werden toch een tab
            # zoals bijvoorbeeld fout ' ' -> '\t', mag pas vanaf tabsize
            # while spc % tabsize and spc < size:
            #     if line[spc] != ' ':
            while spc % tabsize:
                if spc >= size or line[spc] != ' ':
                    # not enough spaces for tab
                    res += line[ pos : spc ] # add spaces
                    break
                spc += 1
            else:
                # replace spaces upto tabstop
                res += line[ pos : fnd ] + '\t'
            pos = spc
        res += line[ pos : ]
        # print( repr( line ) )
        # print( repr( res ) )
        return res

    def spacesToTabs3( txt, tabsize=4 ): # zie ook tabsToSpaces10
        return '\n'.join( spacesToTabsLine( line, tabsize )
                          for line in txt.split('\n') )

    if 0:
        print( voorbeeld )
        spac = tabsToSpaces2( voorbeeld )
        tabs2 = spacesToTabs2( spac )

        spac2 = tabsToSpaces2( tabs2 )
        tabs3 = spacesToTabs3( spac2 )

        spac3 = tabsToSpaces2( tabs3 )

        print( spac )
        print( tabs2 )
        # print( spac2 )
        print( spac == spac2 )
        # print( tabs3 )
        print( tabs2 == tabs3 )
        # print( spac3 )
        print( spac2 == spac3 )
        # print( repr( tabs2 ) )
        # print( repr( spac2 ) )
        # print( repr( tabs3 ) )
        # print( repr( spac3 ) )

    if 0:
        # tests voor bug in spacesToTabsLine tm versie 0.129
        # opgelost in versie 0.130

        spcs = [ ' ' * i for i in range( 18 ) ]
        # spcs = [ 'aaaa' + ' ' * i for i in range( 18 ) ]
        # spcs = [ 'aaaabbb' + ' ' * i for i in range( 18 ) ]
        print( spcs )
        
        tabs3 = [ spacesToTabs3( spc ) for spc in spcs ]
        print( tabs3 )
        spcs2 = [ tabsToSpaces2( tab3 ) for tab3 in tabs3 ]
        print( spcs2 )
        print( spcs == spcs2 )
        tabs33 = [ spacesToTabs3( spc2 ) for spc2 in spcs2 ]
        print( tabs33 )
        print( tabs3 == tabs33 )
        

###############################################################
# Aanvulling 48 Bestanden met codecs

# lees en schrijf bestanden
#   ook voor Unicode

# todo: probeer bestanden met meer spaties/tabs
#   maak en bekijk met Thonny en Notepad, vergelijk onderling
# todo: probeer bestanden met Unicode tekst
#   met speciale tekens zoals het euroteken, Griekse karakters, emoji's

if 0:
    markeer( 'Aanvulling 48' )

    def printFile0( inFilename ):
        with open( inFilename ) as inFile:
            for line in inFile:
                # line = tabsToSpaces( line )
                # line = spacesToTabs( line )

                # print( line )  # bevat al newline voor volgende regel
                print( line, end='' )

    if 0:
        printFile0( 'pcinput.py' )
        
    def copyFile0( inFilename, outFilename):
        with open( inFilename ) as inFile:
            with open( outFilename, 'w' ) as outFile:
            # with open( outFilename, 'x' ) as outFile:
                for line in inFile:
                    # line = tabsToSpaces( line )
                    # line = spacesToTabs( line )

                    # print( line )  # bevat al newline voor volgende regel
                    print( line, end='', file=outFile )
                    # outFile.write( line )

    if 0:
        copyFile0( 'pcinput.py', 'test.py' )
        
    def printFile1( inFilename, encoding='utf-8' ):
        with open( inFilename, encoding=encoding ) as inFile:
            for line in inFile:
                # line = tabsToSpaces( line )
                # line = spacesToTabs( line )

                print( line, end='' )

    if 0:
        printFile1( 'pcinput.py' )

        # https://docs.python.org/3/library/codecs.html#standard-encodings
        # codec = 'ascii'
        # codec = 'latin-1'
        # codec = 'latinxx'  # error, unknown encoding
        # printFile1( 'pcinput.py', encoding=codec )
        
    def copyFile1( inFilename, outFilename,
                  inEncoding='utf-8', outEncoding='utf-8' ):
        with open( inFilename, encoding=inEncoding ) as inFile:
            with open( outFilename, 'w', encoding=outEncoding ) as outFile:
                for line in inFile:
                    # line = tabsToSpaces( line )
                    # line = spacesToTabs( line )

                    # print( line )  # bevat al newline voor volgende regel
                    print( line, end='', file=outFile )
                    # outFile.write( line )

    if 0:
        copyFile1( 'pcinput.py', 'test.py' )

        incodec = 'ascii'
        # incodec = 'latin-1'
        # incodec = 'utf-8'
        # incodec = 'utf-16'
        outcodec = 'ascii'
        # outcodec = 'latin-1'
        # outcodec = 'utf-8'
        # outcodec = 'utf-16'
        copyFile1( 'pcinput.py', 'test.py', incodec, outcodec )

###############################################################
# Aanvulling 49 socket

# socket module
# low-level networking interface
# https://docs.python.org/3/library/socket.html
# socket type
# dunne laag boven operating system

if 0:
    markeer( 'Aanvulling 49' )

    import socket

    if 0:
        print( 'Client 1' )

        host = 'programmeren.hcc.nl'
        port = 80 # http
        address = host, port    

        s = socket.socket()
        s.connect( address )
        data = s.recv( 1024 )
        s.close()

        print( data )

        # b"HTTP/1.1 408 Request Time-out\r\n
        # Content-length: 110\r\n
        # Cache-Control: no-cache\r\n
        # Connection: close\r\n
        # Content-Type: text/html\r\n
        # \r\n
        # <html><body><h1>408 Request Time-out</h1>\n
        # Your browser didn't send a complete request in time.\n
        # </body></html>\n"

    # bekijk packets met WireShark
    # met display filter zoals tcp.port == 80
    # https://www.wireshark.org
    # https://en.wikipedia.org/wiki/WireShark

    if 0:
        print( 'Client 2' )

        host = 'programmeren.hcc.nl'
        port = 80 # http
        # port = 443 # https # hangt of leeg resultaat
        address = host, port    

        # req = b''
        # req = b'GET / HTTP/1.0'
        # req = b'GET / HTTP/1.0\r\n'
        req = b'GET / HTTP/1.0\r\n\r\n' # geen timeout
        with socket.socket() as s:
            s.connect( address )
            s.sendall( req )
            data = s.recv( 1024 )
        print( data )

        # sendall req met \r\n\r\n
        # b'HTTP/1.1 301 Moved Permanently\r\n
        # Content-length: 0\r\n
        # Location: https:///\r\n
        # Connection: close\r\n\r\n'

    if 0:
        print( 'Client 3' )

        host = 'httpbin.org'
        # host = 'lokker.net'
        port = 80 # http
        # port = 443 # https
        address = host, port    

        req = b'GET / HTTP/1.0\r\n\r\n'
        # req = b'GET / HTTP/1.1\r\nHost: httpbin.org\r\n\r\n'
        # req = b'GET /get HTTP/1.0\r\n\r\n'
        # req = b'GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n'

        # AF_INET en SOCK_STREAM zijn default
        # https://docs.python.org/3/library/socket.html#socket.socket

        # kan ook client poort vast leggen
        #   in plaats van automatisch toegekende poort met hoog nummer
        #   door gebruik van bind() op client socket
        # https://stackoverflow.com/questions/36575944/socket-programming-port-help-python
        # todo: uitproberen

        with socket.socket( socket.AF_INET, socket.SOCK_STREAM ) as s:
            s.connect( address )
            s.sendall( req )
            data = s.recv( 1024 )
        print( data )

        # GET /
        # b'HTTP/1.1 200 OK\r\n
        # Date: Thu, 20 Nov 2025 13:58:52 GMT\r\n
        # Content-Type: text/html; charset=utf-8\r\n
        # Content-Length: 9593\r\nConnection: keep-alive\r\n
        # Server: gunicorn/19.9.0\r\n
        # Access-Control-Allow-Origin: *\r\n
        # Access-Control-Allow-Credentials: true\r\n\r\n'    

        # GET /get
        # b'HTTP/1.1 503 Service Temporarily Unavailable\r\n
        # Server: awselb/2.0\r\n
        # Date: Thu, 20 Nov 2025 14:01:56 GMT\r\n
        # Content-Type: text/html\r\n
        # Content-Length: 162\r\n
        # Connection: keep-alive\r\n\r\n
        # <html>\r\n
        # <head><title>503 Service Temporarily Unavailable</title></head>\r\n
        # <body>\r\n
        # <center><h1>503 Service Temporarily Unavailable</h1></center>\r\n
        # </body>\r\n
        # </html>\r\n'

        # port 443
        # b'HTTP/1.1 400 Bad Request\r\n
        # Server: awselb/2.0\r\n
        # Date: Thu, 20 Nov 2025 14:04:51 GMT\r\n
        # Content-Type: text/html\r\n
        # Content-Length: 220\r\n
        # Connection: close\r\n\r\n
        # <html>\r\n
        # <head><title>400 The plain HTTP request was sent to HTTPS port</title></head>\r\n
        # <body>\r\n
        # <center><h1>400 Bad Request</h1></center>\r\n
        # <center>The plain HTTP request was sent to HTTPS port</center>\r\n
        # </body>\r\n
        # </html>\r\n'

    # ssl module
    # TLS/SSL wrapper for socket objects
    # https://docs.python.org/3/library/ssl.html
    # wrapper om socket
    # functies voor SSLSocket en SSLContext types

    if 0:
        print( 'Client 4' )

        import ssl

        # port = 80 # http
        # port = 443 # https

        if 0:
            host = 'programmeren.hcc.nl'
            # req = b'GET / HTTP/1.0\r\n\r\n' # 500 Internal Server Error
            req = b'GET / HTTP/1.1\r\nHost: programmeren.hcc.nl\r\n\r\n' # 200 OK

        if 1:
            host = 'httpbin.org'
            req = b'GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n'

        address = host, port
        context = ssl.create_default_context()
        with socket.socket() as s:
            with context.wrap_socket( s, server_hostname=host ) as tls:
                tls.connect( address )
                tls.sendall( req )
                data = tls.recv( 1024 )
        print( data )

        # port 80
        # Traceback (most recent call last):
        # ...
        # ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1032)

        # programmeren.hcc.nl port 443
        #   get / zonder host, recv(1024) te klein
        # b'HTTP/1.1 500 Internal Server Error\r\n
        # Date: Thu, 20 Nov 2025 14:42:45 GMT\r\n
        # Server: Apache\r\n
        # ...

        # programmeren.hcc.nl port 443
        #   get / met host programmeren.hcc.nl
        # b'HTTP/1.1 200 OK\r\n
        # Date: Thu, 20 Nov 2025 14:47:08 GMT\r\n
        # Server: Apache\r\n
        # ...
        
        # httpbin.org port 443
        #   get /get met host httpbin.org
        # b'HTTP/1.1 200 OK\r\n
        # Date: Thu, 20 Nov 2025 14:56:39 GMT\r\n
        # Content-Type: application/json\r\n
        # Content-Length: 198\r\n
        # Connection: keep-alive\r\n
        # Server: gunicorn/19.9.0\r\n
        # ...

    # family, socket eerste parameter

    # AF_INET default
    #   host is hostname zoals programmeren.hcc.nl
    #        of IPv4 adres zoals 127.0.0.1
    #   port is 16 bits integer
    # https://en.wikipedia.org/wiki/IPv4

    # AF_INET6
    #   IPv6 adres zoals fe80::1234:5678:8f07:274a
    # https://en.wikipedia.org/wiki/IPv6

    # en andere family constants
    #  zoals AF_UNIX, AF_CAN, AF_PACKET, ...

    # type, socket tweede parameter
    #   hangt af van family
    # voor AF_INET = IPv4
    #   en AF_INET6 = IPv6
    # SOCK_STREAM voor TCP
    # SOCK_DGRAM  voor UDP
    # en meer zoals SOCK_RAW

    # TCP en UDP vormen een laag boven IPv4 en IPv6
    # zie ook extra_starter_kit\17_www\docs\2_tcp_ip.pdf

    # TCP is het meest gebruikt ongeveer 90%
    # door HTTP, HTTPS, FTP, FTPS,
    #   SMTP, IMAP, POP, SSH, Telnet, DNS, ...
    # vormt een stream van bytes
    #    zowel voor versturen als voor ontvangen
    #    combineert automatisch meerdere packets
    #      naar 1 stream
    #    voorkomt duplicaten en garandeert volgorde
    #    optimaliseert snelheid
    # resultaat vergelijkbaar met een binair bestand
    #   zonder seek mogelijkheid
    # https://en.wikipedia.org/wiki/Transmission_Control_Protocol

    # UDP minder vaak gebruikt ongeveer 10%
    # door DNS, SNMP, DHCP, ...
    #   en VPN systemen zoals WireGuard, OpenVPN
    # werkt nog steeds met packets
    # https://en.wikipedia.org/wiki/User_Datagram_Protocol

    # TCP en UDP gebruiken 16 bits poort nummers 0 .. 65535
    #   om te bepalen op een host (computer)
    #     van en naar welke applicatie
    #     de data gestuurd en ontvangen moeten worden
    # de combinatie
    #  client IP adres + poort + server IP adres + poort
    # moet uniek zijn

    # meestal is server poort bekend en varieert client poort
    # zoals HTTP poort 80, HTTPS 443, FTP 20 en 21, FTPS 989 en 990,
    #   SMTP 25, IMAP 143, POP3 110, SSH 22 (ook voor SCP en SFTP)
    # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers

    if 0:
        print( 'Server met client 1' )

        host = ''
        port = 5000
        srvaddr = host, port

        def server():
            with socket.socket() as srv:
                srv.bind( srvaddr )
                srv.listen()  # gebruikt default redelijke waarde
                print( 'server listen', srvaddr )
                clnt, claddr = srv.accept()
                with clnt:
                    print( 'server connect', claddr )
                    while True:
                        data = clnt.recv( 1024 )
                        # print( 'data', data )

                        # if data == b'': 
                        if data == b'stop':
                            print( 'server client done' )
                            break
                        data = b'echo:' + data
                        clnt.sendall( data )
                        print( 'server', data )
            print( 'end server' )

        import threading

        t = threading.Thread(target=server)
        t.start()

        # client
        address = 'localhost', port
        with socket.socket() as s:
            s.connect( address )
            s.sendall( b'hello' )
            print( 'client', s.recv( 1024 ) )
            s.sendall( b'nog een test' )
            print( 'client', s.recv( 1024 ) )

            # s.sendall( b'' ) # werkt niet
            s.sendall( b'stop' )
            print( 'client', s.recv( 1024 ) )

        t.join() # wacht

    if 0:
        print( 'Server extern met client 2' )

        # run eerst externe echo server
        #   anders TimeoutError

        # gebruik kleine bestanden
        # server1.py of server2.py
        #   in dezelfde directory bij
        #   ProgrammeursleerlingExtra.py

        # server1.py met CPython op localhost

        # server2.py met MicroPython op ESP32
        # https://docs.micropython.org/en/latest/library/socket.html
        # https://docs.micropython.org/en/latest/esp32/quickref.html#networking
        # https://docs.micropython.org/en/latest/library/network.WLAN.html#network.WLAN
        #   IP adres nakijken verbeteren
        
        port = 5001
        # address = 'localhost', port
        # address = '127.0.0.1', port

        address = '192.168.4.1', port

        with socket.socket() as s:
            s.connect( address )
            s.sendall( b'hello' )
            print( 'client', s.recv( 1024 ) )
            s.sendall( b'nog een test' )
            print( 'client', s.recv( 1024 ) )

            # s.sendall( b'' ) # werkt niet
            s.sendall( b'stop' )
            print( 'client', s.recv( 1024 ) )
        
    # todo: UDP, IPv6, routing, ICMP, DNS, NAT, firewall
    #  speciale lokale IP adressen
    #    zoals private 10.x.x.x, 192.168.x.x
    #    of localhost 127.0.0.x

    # todo: cmdline tools
    #   ping, ipconfig, ifconfig, tracert,
    #     ip, nslookup, ...

    # todo: in browser F12 developer tools bekijken

    # todo: MicroPython ESP32
    #   tegelijkertijd IF_AP access point
    #               en IF_STA station
    # todo: MicroPython wrapper voor socket
    # zodat __exit__ wel mogelijk is

    # todo: socketserver, select, selectors modules
    #   voor beter gebruik socket

    # socketserver module
    # framework for network servers
    # https://docs.python.org/3/library/socketserver.html
    # laag boven socket
    # xxTCPServer, xxUDPServer en meer types
    #   inheritance nodig

    # select module
    # waiting for I/O completion
    # https://docs.python.org/3/library/select.html
    # select, poll en meer functies
    # bruikbaar met sockets
    #   ook met files behalve bij Windows
    # kan hiermee wachten op meerdere sockets tegelijk
    #   tot een ervan actief wordt
    # verschillende functies
    #   voor verschillende platforms
    #   oorspronkelijk alleen select
    #     verouderde implementatie
    #     traag met veel sockets (of files)
    #   daardoor heeft select verschillende
    #     verbeterde opvolgers

    # selectors module
    # high-level I/O multiplexing
    # DefaultSelector maakt keuze
    #   welke manier beste is
    #   voor platform Linux, Windows, Mac

###############################################################
# Aanvulling 50 VPython, GlowScript en RapydScript-ng

# 3D modellen in een webbrowser

# VPython: 3D Programming for Ordinary Mortals
# https://vpython.org

# voor een eerste indruk bekijk Examples
# https://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples/
# probeer AtomicSolid-VPython of BillboardingText

# probeer de camera positie aan te passen
# met muis of touchpad of touchscreen
#   beweeg naar boven/beneden + linkerknop: in/uitzoomen
#   touchpad met 2 vingers of scrollwheel: in/uitzoomen
#   beweeg + rechterknop: draaien
#   beweeg + ctrl: draaien
#   beweeg + shift + linkerknop: schuiven
# beweegt soms alsof shift is ingedrukt
# aanpassing van de positie kan soms niet
#   hangt ook af van de ingestelde mogelijkheden
#   in de gebruikte Python code

# https://vpython.org/presentation2018/install.html
# works with Python versions 3.8-3.12
# proberen met Python 3.13

# command prompt, maak venv
# zie ook Aanvulling 3 Pip, PyPI en virtuele environments

# > py -m venv VPyVenv # of andere directory naam
# > VPyVenv\Scripts\activate
# (VPyVenv)> pip install vpython
# installeert veel modules

# (VPyVenv)> py
# >>> import vpython
# ModuleNotFoundError: No module named 'pkg_resources'
# https://stackoverflow.com/questions/7446187/no-module-named-pkg-resources

# (VPyVenv)> pip install setuptools
# Requirement already satisfied: setuptools in
#  ...\vpythonvenv\lib\site-packages (82.0.0)

# verderop stackoverflow pagina
# > 2026 update: setuptools 82.0 removed pkg_resources,
#   so if you still need it, make sure to install version 81 or lower.
# https://pypi.org/project/setuptools/#files
#   setuptools-82.0.0.tar.gz (1.1 MB view details)
#   Uploaded Feb 8, 2026 Source

# (VPyVenv)> pip install setuptools==81

# (VPyVenv)> py
# >>> import vpython
#   ...\VPythonVenv\Lib\site-packages\vpython\__init__.py:1:
#   UserWarning: pkg_resources is deprecated as an API.
#  See https://setuptools.pypa.io/en/latest/pkg_resources.html.
#   The pkg_resources package is slated for removal as early
#   as 2025-11-30. Refrain from using this package or pin to Setuptools<8.1r
#   from pkg_resources import get_distribution, DistributionNotFound
# geen error, wel warning

# >>> vpython.box()
# Windows komt eerste keer met dialog
#   Een app kiezen
#   webbrowser keuze zoals Firefox
# opent http://localhost:54329/
#   of ander poortnummer, is willekeurig
# witte box met zwarte achtergrond
# <vpython.vpython.box object at 0x0000023AC2FBC1A0>
# kan box in webbrowser van verschillende kanten bekijken
#   draaien en in/uitzoemen
# met F5 in webbrowser stopt Python
#   gaat naar cmd prompt, webpagina verdwijnt
#   Python crash?

# open IDLE in VPython venv
# (VPyVenv)> py -m idlelib

# open ProgrammeursleerlingExtra.py
# probeer onderstaande code
#   if 0: -> if 1:
#   VPython werkt lokaal

if 0:
    import vpython

    vpython.box()

    # box blijft staan in webbrowser
    # IDLE Shell komt met prompt terug

    if 0:
        # IDLE Shell wacht
        # maar F5 stopt webbrowser
        while True:
            vpython.rate(30)
        # IDLE stop met Ctrl+F6 of menu Shell, Restart Shell

# Web VPython: webvpython.org
# (formerly GlowScript VPython)
# https://webvpython.org
#   redirect naar
# https://glowscript.org

# Welcome to VPython verwijst naar een tutorial op Trinket
# Trinket is een online ontwikkel systeem vergelijkbaar met Hedy

# https://trinket.io/announcement
# Trinket will be shutting down in early August 2026

# https://trinket.io/matter-interactions/courses/00_welcome_to_vpython
# met knop > rechtsboven naar volgende
#   of via item in lijst links
# volgende pagina Dancing pendulum simulatie
# klik op potlood Code voor bekijken main.py
#   voorbeeld is slechts 37 regels code

# volg een deel van de trinket tutorial
#   tot en met 2 objecten met ongelijke position
# behandelt verder ook Python

# Documentation op vpython.org
# en Help op glowscript.org
# verwijzen naar de uitgebreide VPython documentatie
# https://glowscript.org/docs/VPythonDocs/index.html
#   met 3D plaatjes erin

# 3D Objects in VPython
# https://www.glowscript.org/docs/VPythonDocs/objects.html
# bekijk voorbeeld model VPythonObjects-VPython
# https://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples/program/VPythonObjects-VPython
# probeer: drag the mouse
# in code zie events: down, move, up

# 3D object types
#   arrow, box, cone, curve, cylinder, ellipsoid, helix,
#   label, points, pyramid, ring, sphere, 3D text
# animations: rate
# canvas: screen capture
# camera, lighting
# color, opacity, glow
# design new objects
#   compound of group
#   vertex, triangle and quad
#     uitleg hoe objecten zijn opgebouwd
#     kan vorm dynamisch wijzigen
#       voorbeelden Plot3D en Rug
#     triangle bestaat uit 3 vertex objecten
#       order, smoothing
#     quad, texture
# files, library alleen voor web vpython
# graphs, plotting: voorbeeld HardSphereGas
# textures, bumpmaps: voorbeeld Textures-VPython
# user input: controls

# Stonehenge-VPython voorbeeld
# rotate en zoom zijn uitgeschakeld
#   kan daardoor door scene gaan
#   vooruit, achteruit, links en rechts
#     niet omhoog of omlaag

# ook JavaScript bruikbaar
# https://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples-JavaScript/

if 0:
    # VPython license
    # https://www.glowscript.org/docs/VPythonDocs/license.html
    # http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1
    # VPython (c) Copyright 2011, 2023 David Scherer and Bruce Sherwood
    # VPython by David Scherer and Bruce Sherwood is licensed under CC BY 4.0
    # VPython is an open source project. It is free to use or modify, within the terms of the license.
    
    # voorbeeld BillBoardingText
    # https://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples/program/BillboardingText/edit
    # gebruik de Download link bovenaan
    #   of copy paste, dan wijziging nodig
    #     maak commentaar van regel met Web VPython 3.2

    from vpython import *
    #Web VPython 3.2
    b = box(pos=vec(-4,2,0), color=color.red)
    c1 = cylinder(pos=b.pos, radius=0.1, axis=vec(0,1.5,0), color=color.yellow)
    s = sphere(pos=vec(4,-4,0), radius=0.5, color=color.green)
    c2 = cylinder(pos=s.pos, radius=0.1, axis=vec(0,1.5,0), color=color.yellow)
    t1 = text(text='box', pos=c1.pos+c1.axis, align='center', height=0.5,
              color=color.yellow, billboard=True, emissive=True)
    t2 = text(text='sphere', pos=c2.pos+c2.axis, align='center', height=0.5,
              color=color.yellow, billboard=True, emissive=True)
    t3 = text(text='Faces forward', pos=vec(-4,0,0),
              color=color.cyan, billboard=True, emissive=True)
    box(pos=t3.start, size=0.1*vec(1,1,1), color=color.red)
    t4 = text(text='Regular text', pos=vec(-4,-1,0), depth=0.5, color=color.yellow,
            start_face_color=color.red, end_face_color=color.green)
    box(pos=t4.start, size=0.1*vec(1,1,1), color=color.red)

    scene.caption = """<b>3D text can be "billboard" text -- always facing you.</b>
    Note that the "Regular text" has different colors on the front, back and sides.
    To rotate "camera", drag with right button or Ctrl-drag.
    To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.
      On a two-button mouse, middle is left + right.
    To pan left/right and up/down, Shift-drag.
    Touch screen: pinch/extend to zoom, swipe or two-finger rotate."""

# kan Web VPython online gebruiken op GlowScript website
#   met gratis account via Google

# meer voorbeelden
# https://www.glowscript.org/#/user/GlowScriptDemos/folder/matterandinteractions/program/MatterAndInteractions
#   zoals 02-Newton-grid en 23-positron-EMwave
# voor Matter & Interactions boek
# https://matterandinteractions.org
# Bruce Sherwood is auteur van boek en van VPython

# VPython gebruikt in de webbrowser RapydScript-ng
#   dus geen CPython of MicroPython
# https://github.com/kovidgoyal/rapydscript-ng
# transpiler, converteert RapydScipt-ng naar JavaScript

# RapydScript-ng lijkt op Python maar er zijn verschillen
# https://github.com/kovidgoyal/rapydscript-ng#gotchas
# Kovid Goyal is bekend als developer van calibre e-book manager
# https://en.wikipedia.org/wiki/Calibre_(software)

# RapyScript-ng online proberen
# https://sw.kovidgoyal.net/rapydscript/repl/
# probeer print('Hello world') of [ i*i for i in range(5) ]

# RapydScript-ng is een fork van RapydScript
# RapydScript en RapydScript-ng zijn geschreven in JavaScript
# https://github.com/atsepkov/RapydScript
# Alexander Tsepkov is ook developer van
# RapydML XML/HTML Abstraction Markup Language
# https://github.com/atsepkov/RapydML

###############################################################
# Aanvulling 51 Visual Studio Code

# source code editor
# https://code.visualstudio.com

# Wikipedia historie, mogelijkheden, links
# https://nl.wikipedia.org/wiki/Visual_Studio_Code
# https://en.wikipedia.org/wiki/Visual_Studio_Code

# beschikbaar voor Windows, Linux en MacOS
# is gemaakt met TypeScript en JavaScript
#   maakt gebruik van Electron, Chromium, Node.js en V8 engine

# Visual Studio Code is een ander product dan Visual Studio
#   Visual Studio is ook een Editor/IDE van Microsoft
#   maar is niet open source, en werkt alleen op Windows

# er zijn veel extensies beschikbaar
#   uitgebreide mogelijkheiden
# lijkt daardoor op een IDE - Integrated Development Environment
# wel ingewikkeld door vele opties
# wordt met name veel gebruikt door ervaren software ontwikkelaars

# heeft extensies voor Python
#  en voor nog veel meer programmeertalen

# Visual Studio Code is open source
# https://github.com/microsoft/vscode/
#   groot aantal forks op GitHub 39.2k
#   ook vanwege AI toepassingen

# download van Microsoft bevat closed source code customizations
#   waaronder code voor telemetry/tracking
# bij gebruik van download akkoord met Microsoft licentie
# https://code.visualstudio.com/docs/supporting/faq#_licensing

# volledig open source fork van Visual Studio Code
#   met binaries downloads
# https://vscodium.com
# documentatie
# https://github.com/VSCodium/vscodium/blob/master/docs/index.md
# enkele closed source proprietary extensies zijn niet beschikbaar
#   zoals Microsoft C# en C++ debuggers
# https://github.com/VSCodium/vscodium/blob/master/docs/extensions.md#proprietary-debugging-tools

# Google Antigravity AI-powered IDE
# proprietary Visual Studio Code fork
# https://antigravity.google/
# https://en.wikipedia.org/wiki/Google_Antigravity

# distributie van Microsoft
# rechtsboven blauwe Download button
# https://code.visualstudio.com/Download

# Windows 10, 11
#   User Installer voor 1 gebruiker, geen admin rechten nodig
#   System Installer voor alle gebruikers, wel admin rechten
#   .zip portable versie, CLI command line interace
# Linux
#   Debian, Ubuntu .deb
#   Red Hat, Fedora, SUSE .rpm
#   .tar.gz, Snap, CLI
# Mac
#   .dmg, CLI

# download Windows 11 x64 Installers
# system of user, 151 MB
#   VSCodeSetup-x64-1.116.0.exe
#   VSCodeUserSetup-x64-1.116.0.exe

# run installer
# soms Select language
#   geen Nederlands in lijst
# accept license
# bij system install
#   location
#   start menu
# additional tasks, bijvoorbeeld
#   desktop icon
#   register supported filetypes
#   add to PATH
# install

# uninstall via Windows Settings, Apps
# Geïnstalleerde apps, zoeken naar code
# in lijst gevonden rechts ... 

# run Code.exe
# default
# system "C:\Program Files\Microsoft VS Code\Code.exe"
# user "C:\Users\User\AppData\Local\Programs\Microsoft VS Code\Code.exe"

# voor uitvoeren 'code' in shell zoals cmd.exe
# > code
# als toegevoegd aan PATH

# bovenaan main menu
#   met daarin Help, Documentation
#     gaat naar https://code.visualstudio.com/docs#vscode
#     zie daar ook Learn VS Code basics

# vergroten en verkleinen, zoom in out
#   met Ctrl met plus en min, Ctrl+= en Ctrl+-
# zoals in Webbrowser, past alles aan
#   windows, fonts, icons, menus
# menu opties blijven bruikbaar via ... dropdown
# reset met Ctrl+0 zoals in Webbrowser werkt niet
#   wel met Ctrl+Numpad0
# kan ook instellen via Settings
# https://stackoverflow.com/questions/69748790/how-to-reset-vs-codes-window-zoom-setting

# selecteer kleuren, theme
# menu File, Preferences, Themes, Color Theme
#   of Ctrl+K, Ctrl+T
# maak keuze uit lijst, Default Light 2026
#   theme verandert mee tijdens kiezen

# links bovenaan Activity Bar
# schakelen tussen verschillende views
# Explorer, Search, Source Control, Run and Debug,
#   Extensions, Testing
#   en meer afhankelijk van Extensies

# links onderaan
# Accounts
#   Sign in to use AI features
# Manage
#   Command Palette, Ctrl+Shift+P
#   Profiles
#   Settings, Ctrl+,
#   Themes, Color Theme

# Settings, Ctrl+,
# search settings: telemetry
# alles uitzetten
# rechts boven 3e icon van rechts
#   open modal in main window
#   Settings naar Tab
# soms restart nodig, require restart
# Visual Studio Code afsluiten
#   en weer starten
# of Command Palette, Ctrl+Shift+P
#   intypen reload
#   Developer: Reload Window

# display language packs
# type Ctrl+Shift+P display
# selecteer configure display language
# geen Nederlands beschikbaar in lijst

# gebruik als Python editor en debugger
# open map met .py code
# menu File, Open Folder, Ctrl+K Ctrl+O
# bijvoorbeeld map c:\Users\User\HccPython\
# open ProgrammeursleerlingExtra.py

# als nog andere extensies aanwezig
# links Activity Bar, Extensions
#   kan uninstall doen, soms restart nodig

# Python extensie nodig
# als geen enkele extensie aanwezig
#   Extensions, Installed 0
# verschijnt soms rechts onder
#   Do you want to install
#   the recommended Python extension
#   from Microsoft for the Python language?
#   Install
# of zelf Python extensie zoeken
#   op Install klikken
# Installing
# daarna 4 extensies installed
#   Pylance, Python, Python Debugger en
#     Python Environments

# in ProgrammeursleerlingExtra.py
#   Problems 58
#   import could not be resolved
# allemaal binnen if 0: code
# https://github.com/microsoft/pylance-release/blob/main/docs/diagnostics/reportMissingImports.md

# open ook floodfill.py
#   na unzip uit PythonCursusXxx.zip
# No problems
# rechts boven driehoek > Run Python File
# output in Terminal onderaan

# Python help
# https://code.visualstudio.com/docs/python/python-quick-start

# Editor
# Keyboard Shortcuts PDFs
# https://code.visualstudio.com/docs/configure/keybindings#_keyboard-shortcuts-reference

# navigeer door tabs
# Ctrl+Tab, Ctrl+Shift+Tab

# zoeken, find, replace
# menu Edit, Find Ctrl+F
# bijvoorbeeld Aanvulling in ..Extra.py
#   volgende F3, vorige Shift+F3
#   gaat rond, bij volgende aan einde terug naar begin
#     of bij vorige aan begin terug naar einde

# menu Edit, Find in Files
# bijvoorbeeld import
# 572 results
# met klik naar toe
# Open in editor
# meer overzicht
# ook met klik naar toe

# gebruikt vanzelf VPyVenv subdir
#   is niet nodig

# status balk rechts onder
# minder rechts Python versie
#  klik: Select Interpreter
#  bestaande kiezen, Enter path,
#    Create Virtual Environment 
# kies Python in program files
# Run floodfill.py
#   blijft environment gebruiken?!

# status balk, helemaal rechts environment naam
#  klik: Select a Python environment
#  bestaande kiezen, Create of Browse
# kies Python in program files
# Run floodfill.py
#   blijft environment gebruiken?!

# voeg tijdelijke Python test code toe
# bijvoorbeeld bovenaan floodfill.py
"""
import sys

print(sys.argv[0])
exit()
""" 
# conclusie: werkt met/zonder environment
#   zoals rechtsonder aangegeven
#   maar prompt in terminal wordt niet aangepast
# https://github.com/microsoft/vscode-python/issues/22917
# https://github.com/microsoft/vscode-python/issues/22611
# https://github.com/microsoft/vscode-python-environments/wiki/Terminal-Auto%E2%80%90Activation-Explained
# code weer weghalen

# Python commands
# Command palette Ctrl+Shift+P
# type python
# veel mogelijkheden in lijst beschikbaar

# menu Run, Run Without Debugging Ctrl+F5
#   zoals > rechts boven

# zet breakpoint in code
# klik met muis voor regelnummer
#   rode cirkel ervoor
#   weghalen nog een keer klikken
# menu Run, Start Debugging F5
# Select a debug configuration
#   kies currently active Python file
# links wordt Run And Debug actief
#   Variables: Locals, Globals
#   Watch, Call Stack, Breakpoints
# menu Run Step Over F10, Step Info F11,
#   Step Out Shift+F11, Continue F5
#   Stop Debugging Shift+F5

# wandel met F10 en F11 door floodfill.py
# probeer over en in functies te tracen
# bekijk locals, call stack
#   uit functies met Shift+F11

# na eerder gebruik van VS code
#   eerdere extensies en instellingen in gebruik
#   zoals C/C++, ESP-IDF, PlatformIO extensies
# kan meer profielen aanmaken
# zodat Python zonder C++ extensies te gebruiken
# links onder Manage, Profiles
# knop New profile
#   Name HccPython
#   + Use as default for new windows
#   copy from Default
#     Contents op Default (Copy)
#     behalve Extensions: None
#   Create
# Manage, Profiles
#   kies HccPython

###############################################################
# Aanvulling 52 Classes en inheritance

# zie de voorbeeld programma's van Marco Kurvers
#   in de extra_oop directory
#     in PythonCursusXX.zip

###############################################################
# Appendix A Problemen Oplossen

# zie boek

###############################################################
# Appendix B Verschillen met Python 2

# zie boek

###############################################################
# Appendix C pcinput.py

if 0:
    markeer( 'Appendix C' )

    # schrijf pcinput.py als niet aanwezig
    import os

    pcinput = '''\
def getFloat( prompt ):
    while True:
        try:
            num = float( input( prompt ) )
        except ValueError:
            print( "Geen getal -- probeer het opnieuw" )
            continue
        return num

def getInteger( prompt ):
    while True:
        try:
            num = int( input( prompt ) )
        except ValueError:
            print( "Geen geheel getal -- probeer het opnieuw" )
            continue
        return num

def getString( prompt ):
    line = input( prompt )
    return line.strip()

def getLetter( prompt ):
    while True:
        line = input( prompt )
        line = line.strip()
        line = line.upper()
        if len( line ) != 1:
            print( "Geef precies een letter in" )
            continue
        if line < 'A' or line > 'Z':
            print( "Geef een letter van het alfabet in" )
            continue
        return line
'''
    outf = 'pcinput.py'
    if os.path.isfile( outf ):
        print( outf, 'bestaat al' )
    else:
        with open( outf, 'w' ) as fp:
            fp.write( pcinput )
        print( outf, 'gemaakt'  )

###############################################################
# Appendix D pcmaze.py

if 0:
    markeer( 'Appendix D' )

    # schrijf pcmaze.py als niet aanwezig
    import os

    pcmaze = '''\
def connected( x, y ):
    if x > y:
        return connected( y, x )
    if (x,y) in ((1,5),(2,3),(3,7),(4,8),(5,6),(5,9),(6,7),
        (8,12),(9,10),(9,13),(10,11),(10,14),(11,12),(11,15),
        (15,16)):
        return True
    return False
    
def entrance():
    return 1
    
def exit():
    return 16
'''
    outf = 'pcmaze.py'
    if os.path.isfile( outf ):
        print( outf, 'bestaat al' )
    else:
        with open( outf, 'w' ) as fp:
            fp.write( pcmaze )
        print( outf, 'gemaakt'  )

###############################################################
# Appendix E Test Tekstbestanden

if 0:
    markeer( 'Appendix E' )

    # schrijf test bestanden als niet aanwezig
    import os

# pc_rose.txt
    pc_rose = '''\
Het is slechts je naam die mijn vijand is;
Jij bent wie je bent, jezelf, niet een Montague.
Wat is een Montague? Is het een hand, een voet,
een arm, een gezicht, of enig ander deel
behorend aan een man? O, had je maar een andere naam!
Wat betekent een naam? Dat wat wij een roos noemen
zou met een andere naam net zo zoet geuren;
Zo zou ook Romeo, had hij een andere naam,
even volmaakt zijn als hij nu is.
Romeo, doe weg die naam,
Hij is geen deel van jou, verruil hem
voor alles wat ik ben.
'''

# pc_woodchuck.txt
    pc_woodchuck = '''\
Hoeveel hout kan een houthakker hakken
Als een houthakker hout kan hakken?
Hij kan hakken zoveel als hij kan hakken
En hij hakt zoveel als een houthakker kan hakken
Als een houthakker hout kan hakken.
'''

# pc_jabberwocky.txt
    # spaties aan begin van regels, indents
    # niet in boek De Programmeursleerling 1.1.1 blz. 336
    # wel in pc_jabberwocky.txt uit
    #   https://spronck.net/pythonbook/pythonboeklistings.zip

    pc_jabberwocky = '''\
    WAUWELWOK

    't Wier bradig, en de spiramants
      Bedroorden slendig in het zwiets:
    Hoe klarm waren de ooiefants,
     Bij 't bluifen der beriets.

    Pas op de Wauwelwok, mijn kind!
     Zo scherp getand, van klauw zo wreed!
    Zorg dat Tsjoep-Tsjoep je nimmer vindt,
     Vermijd de Barbeleet.

    Hij nam zijn gnijpend zwaard ter hand:
     Lang zocht hij naar den aarts-schavoest
    Maar nam rust in lommers lust
     Op een tumtumboomknoest.

    En toen hij zat in diep gedenk,
     Kwam Wauwelwok met vlammend oog,
    Dwars door het bos met zwalpse zwenk,
     Sluw borbelend wijl hij vloog.

    Een, twee! Hup twee. En door en door
     Ging kler de kling toen krissekruis.
    Hij sloeg hem dood en blodd'rig rood
     Bracht hij het tronie thuis.

    Hebt gij versnaggeld Wauwelwok?
     Kom aan mijn hart, o jokkejeugd!
    O, heerlijkheid, fantabeltijd!
     Hij knorkelde van vreugd.

    't Wier bradig, en de spiramants
     Bedroorden slendig in het zwiets:
    Hoe klarm waren de ooiefants,
     Bij 't bluifen der beriets.
'''

# pc_inventory.csv
    pc_inventory = '''\
ID,CATEGORIE,NAAM,VOORRAAD,PRIJS
1,Fruit,appel,1000,0.87
2,Fruit,banaan,2500,0.34
3,Fruit,kers,11225,0.07
4,Fruit,doerian,0,5.52
5,Kaas,Roquefort,46,12.23
6,Kaas,Blue Stilton,1,19.88
7,Kaas,Gouda,7,11.99
8,Fruit,aalbes,355,0.77
9,Fruit,mango,24,1.56
10,Kaas,Cheddar,333,13.15
'''
    outs = ( ( 'pc_rose.txt',        pc_rose ),
             ( 'pc_woodchuck.txt',   pc_woodchuck ),
             ( 'pc_jabberwocky.txt', pc_jabberwocky ),
             ( 'pc_inventory.csv',   pc_inventory ) )
    for outf, outtxt in outs:
        if os.path.isfile( outf ):
            print( outf, 'bestaat al' )
        else:
            with open( outf, 'w' ) as fp:
                fp.write( outtxt )
            print( outf, 'gemaakt'  )

###############################################################
# Appendix F Antwoorden

#=============================================================#
# Opgave 1.1 F
# zie boek Antwoord 1.1

#=============================================================#
# Opgave 1.2 F
# zie boek Antwoord 1.2

#=============================================================#
# Opgave 2.1 F
# zie boek Antwoord 2.1

#=============================================================#
# Opgave 2.2 F
# zie boek Antwoord 2.2

#=============================================================#
# Opgave 3.1 F

if 0:
    markeer( 'Opgave 3.1 F' )

    # answer0301.py

    print( 60 * (0.6 * 24.95 + 0.75) + (3 - 0.75) )

#=============================================================#
# Opgave 3.2 F
# zie boek Antwoord 3.2

#=============================================================#
# Opgave 3.3 F

if 0:
    markeer( 'Opgave 3.3 F' )

    # answer0303.py

    print( 1/0 )

#=============================================================#
# Opgave 3.4 F
# zie boek Antwoord 3.4

#=============================================================#
# Opgave 3.5 F

if 0:
    markeer( 'Opgave 3.5 F' )

    # answer0305.py

    print( str( (14 + 535) % 24 ) + ".00" )

#=============================================================#
# Opgave 4.1 F

if 0:
    markeer( 'Opgave 4.1 F' )

    # answer0401.py

    # Dit programma berekent het gemiddelde van 3 variabelen:
    # var1, var2, en var3
    var1 = 12.83
    var2 = 99.99
    var3 = 0.12
    gemiddelde = (var1 + var2 + var3) / 3 # Berekening
    print( gemiddelde ) # Ziet er wat lelijk uit, maar
    # dat wordt beter na een discussie over formattering

#=============================================================#
# Opgave 4.2 F

if 0:
    markeer( 'Opgave 4.2 F' )

    # answer0402.py

    pi = 3.14159
    straal = 12
    print( "De oppervlakte van een cirkel met straal",
        straal , "is", pi * straal * straal )

#=============================================================#
# Opgave 4.3 F

if 0:
    markeer( 'Opgave 4.3 F' )

    # answer0403.py

    CENTEN_IN_DOLLAR = 100
    CENTEN_IN_KWARTJE = 25
    CENTEN_IN_DUBBELTJE = 10
    CENTEN_IN_STUIVER = 5

    bedrag = 1156
    centen = bedrag

    dollars = int( centen / CENTEN_IN_DOLLAR )
    centen -= dollars * CENTEN_IN_DOLLAR
    kwartjes = int( centen / CENTEN_IN_KWARTJE )
    centen -= kwartjes * CENTEN_IN_KWARTJE
    dubbeltjes = int( centen / CENTEN_IN_DUBBELTJE )
    centen -= dubbeltjes * CENTEN_IN_DUBBELTJE
    stuivers = int( centen / CENTEN_IN_STUIVER )
    centen -= stuivers * CENTEN_IN_STUIVER
    centen = int( centen )

    print( bedrag / CENTEN_IN_DOLLAR , "bestaat uit:" )
    print( "Dollars:", dollars )
    print( "Kwartjes:", kwartjes )
    print( "Dubbeltjes:", dubbeltjes )
    print( "Stuivers:", stuivers )
    print( "Centen:", centen )

#=============================================================#
# Opgave 4.4 F

if 0:
    markeer( 'Opgave 4.4 F' )

    # answer0404.py

    a = 17
    b = 23
    print( "a =", a, "en b =", b )
    a += b
    b = a - b
    a -= b
    print( "a =", a, "en b =", b )

#=============================================================#
# Opgave 5.1 F

if 0:
    markeer( 'Opgave 5.1 F' )

    # answer0501.py

    s = input( "Geef een string: " )
    print( "Je hebt", len( s ), "letters ingegeven" )

#=============================================================#
# Opgave 5.2 F

if 0:
    markeer( 'Opgave 5.2 F' )

    # answer0502.py

    from pcinput import getFloat
    from math import sqrt

    zijde1 = getFloat( "Geef de lengte van de eerste zijde: " )
    zijde2 = getFloat( "Geef de lengte van de tweede zijde: " )
    zijde3 = sqrt( zijde1 * zijde1 + zijde2 * zijde2 )
    print( "De lengte van de diagonaal is {:.3f}.".format( zijde3 ) )

#=============================================================#
# Opgave 5.3 F

if 0:
    markeer( 'Opgave 5.3 F' )

    # answer0503.py

    from pcinput import getFloat

    num1 = getFloat( "Geef nummer 1: " )
    num2 = getFloat( "Geef nummer 2: " )
    num3 = getFloat( "Geef nummer 3: " )

    print( "De grootste is", max( num1, num2, num3 ) )
    print( "De kleinste is", min( num1, num2, num3 ) )
    print( "Het gemiddelde is", round( (num1 + num2 + num3)/3, 2 ) )

#=============================================================#
# Opgave 5.4 F

if 0:
    markeer( 'Opgave 5.4 F' )

    # answer0504.py

    from math import exp

    s = "e tot de macht {:2d} is {:>9.5f}"
    print( s.format( -1, exp( -1 ) ) )
    print( s.format( 0, exp( 0 ) ) )
    print( s.format( 1, exp( 1 ) ) )
    print( s.format( 2, exp( 2 ) ) )
    print( s.format( 3, exp( 3 ) ) )

#=============================================================#
# Opgave 5.5 F

if 0:
    markeer( 'Opgave 5.5 F' )

    # answer0505.py

    from random import random

    print( "Een toevalsgetal tussen 1 en 10 is",
        1 + int( random() * 10 ) )

#=============================================================#
# Opgave 6.1 F

if 0:
    markeer( 'Opgave 6.1 F' )

    # answer0601.py

    from pcinput import getFloat

    grade = getFloat( "Geef een cijfer: " )
    check = int( grade * 10 )
    if grade < 0 or grade > 10:
        print( "Cijfers liggen tussen 0 en 10." )
    elif check%5 != 0 or check != grade*10:
        print( "Cijfers zijn afgerond op halve punten." )
    elif grade >= 8.5:
        print( "A" )
    elif grade >= 7.5:
        print( "B" )
    elif grade >= 6.5:
        print( "C" )
    elif grade >= 5.5:
        print( "D" )
    else:
        print( "F" )

#=============================================================#
# Opgave 6.2 F
# zie boek Antwoord 6.2

#=============================================================#
# Opgave 6.3 F

if 0:
    markeer( 'Opgave 6.3 F' )

    # answer0603.py

    from pcinput import getString

    s = getString( "Geef een string: " )
    count = 0
    if ("a" in s) or ("A" in s):
        count += 1
    if ("e" in s) or ("E" in s):
        count += 1
    if ("i" in s) or ("I" in s):
        count += 1
    if ("o" in s) or ("O" in s):
        count += 1
    if ("u" in s) or ("U" in s):
        count += 1
    if count == 0:
        print( "Er zitten geen klinkers in de string." )
    elif count == 1:
        print( "Er zit maar 1 verschillende klinker in de string." )
    else:
        print( "Er zijn", count, "verschillende klinkers." )

#=============================================================#
# Opgave 6.4 F

if 0:
    markeer( 'Opgave 6.4 F' )

    # answer0604.py

    from pcinput import getFloat
    from math import sqrt

    a = getFloat( "A: " )
    b = getFloat( "B: " )
    c = getFloat( "C: " )

    if a == 0:
        if b == 0:
            print( "Deze vergelijking heeft geen onbekende!" )
        else:
            print( "Er is 1 oplossing , namelijk", -c/b )
    else:
        discriminant = b*b - 4*a*c
        if discriminant < 0:
            print( "Er zijn geen oplossingen" )
        elif discriminant == 0:
            print( "Er is 1 oplossing , namelijk", -b/(2*a) )
        else:
            print( "Er zijn 2 oplossingen , namelijk",
                    (-b+sqrt(discriminant))/(2*a), "en",
                    (-b-sqrt(discriminant))/(2*a) )

#=============================================================#
# Opgave 7.1 F

if 0:
    markeer( 'Opgave 7.1 F' )

    # answer0701.py

    from pcinput import getInteger

    num = getInteger( "Geef een nummer: " )
    i = 1
    while i <= 10:
        print( i, "*", num, "=", i*num )
        i += 1

#=============================================================#
# Opgave 7.2 F

if 0:
    markeer( 'Opgave 7.2 F' )

    # answer0702.py

    from pcinput import getInteger

    num = getInteger( "Geef een nummer: " )
    for i in range( 1, 11 ):
        print( i, "*", num, "=", i*num )

#=============================================================#
# Opgave 7.3 F

if 0:
    markeer( 'Opgave 7.3 F' )

    # answer0703.py

    from pcinput import getInteger

    AANTAL = 10
    grootste = 0
    kleinste = 0
    deelbaar3 = 0

    for i in range( AANTAL ):
        num = getInteger( "Geef nummer "+str( i+1 )+": " )
        if num%3 == 0:
            deelbaar3 += 1
        if i == 0:
            kleinste = num
            grootste = num
            continue  # niet echt nodig, toch sneller en beter te begrijpen
        if num < kleinste:
            kleinste = num
        if num > grootste:
            grootste = num

    print( "Kleinste is", kleinste )
    print( "Grootste is", grootste )
    print( "Deelbaar door 3 is", deelbaar3 )

#=============================================================#
# Opgave 7.4 F

if 0:
    markeer( 'Opgave 7.4 F' )

    # answer0704.py

    # flessen = 10
    flessen = 100
    s = "s"
    while flessen != "geen":
        print( "{0} flesje{1} met bier op de muur, "\
            "{0} flesje{1} met bier.".format( flessen , s ) )
        flessen -= 1
        if flessen == 1:
            s = ""
        elif flessen == 0:
            s = "s"
            flessen = "geen"
        print( "Open er een, drink hem meteen, {} flesje{} "\
            "met bier op de muur.".format( flessen , s ) )

#=============================================================#
# Opgave 7.5 F

if 0:
    markeer( 'Opgave 7.5 F' )

    # answer0705.py

    num1 = 0
    num2 = 1
    print( 1, end=" " )
    while True:
        num3 = num1 + num2
        if num3 > 1000:
            break
        print( num3, end=" " )
        num1 = num2
        num2 = num3

#=============================================================#
# Opgave 7.6 F

if 0:
    markeer( 'Opgave 7.6 F' )

    # answer0706.py

    from pcinput import getString

    woord1 = getString( "Geef woord 1: " )
    woord2 = getString( "Geef woord 2: " )
    gemeen = ""
    for letter in woord1:
        if (letter in woord2) and (letter not in gemeen):
            gemeen += letter

    if gemeen == "":
        print( "De woorden hebben geen tekens gemeen." )
    else:
        print( "De woorden delen de volgende tekens:", gemeen )

#=============================================================#
# Opgave 7.7 F

if 0:
    markeer( 'Opgave 7.7 F' )

    # answer0707.py

    from random import random

    DARTS = 100000
    raak = 0
    for i in range( DARTS ):
        x = random()
        y = random()
        if x*x + y*y < 1:
            raak += 1

    print( "Een redelijke benadering van pi is", 4 * raak / DARTS )

#=============================================================#
# Opgave 7.8 F

if 0:
    markeer( 'Opgave 7.8 F' )

    # answer0708.py

    from random import randint
    from pcinput import getInteger

    antwoord = randint( 1, 1000 )
    teller = 0
    while True:
        poging = getInteger( "Raad een getal: " )
        if poging < 1 or poging > 1000:
            print( "Je moet een getal tussen de 1 en 1000 noemen" )
            continue
        teller += 1
        if poging < antwoord:
            print( "Hoger" )
        elif poging > antwoord:
            print( "Lager" )
        else:
            print( "Je hebt het geraden!" )
            break

    if teller == 1:
        print( "Je hoefde maar 1 keer te raden. Geluksvogel." )
    else:
        print( "Je moest", teller , "keer raden." )

#=============================================================#
# Opgave 7.9 F

if 0:
    markeer( 'Opgave 7.9 F' )

    # answer0709.py

    from pcinput import getLetter
    from sys import exit

    teller = 0
    laagste = 0
    hoogste = 1001
    print( "Denk aan een getal tussen 1 en 1000." )

    while True:
        poging = int( (laagste + hoogste) / 2 )
        prompt = "Ik raad "+str( poging )+". Is jouw getal"+\
            " (L)ager of (H)oger, of is dit (C)orrect? "
        antwoord = getLetter( prompt )
        if antwoord == "C":
            teller += 1
            break
        elif antwoord == "L":
            hoogste = poging
        elif antwoord =="H":
            laagste = poging
        else:
            print( "Antwoord H, L, of C." )
            continue
        teller += 1
        if laagste >= hoogste-1:
            print( "Je moet een fout gemaakt hebben,",
                "want je hebt gezegd dat het getal hoger is dan",
                laagste , "maar ook lager dan", hoogste )
            print( "Ik stop ermee" )
            exit()
    if teller == 1:
        print( "In 1 keer geraden! Ik kan gedachten lezen!" )
    else:
        print( "Ik moest", teller , "keer raden." )

#=============================================================#
# Opgave 7.10 F

if 0:
    markeer( 'Opgave 7.10 F' )

    # answer0710.py

    from pcinput import getInteger

    num = getInteger( "Geef een nummer: " )
    if num < 2:
        print( num, "is niet priem" )
    else:
        i = 2
        while i*i <= num:
            if num%i == 0:
                print( num, "is niet priem" )
                break
            i += 1
        else:
            print( num, "is priem" )

#=============================================================#
# Opgave 7.11 F

if 0:
    markeer( 'Opgave 7.11 F' )

    # answer0711.py

    num = 9
    print( ". |", end="" )
    for i in range( 1, num+1 ):
        print( "{:>3}".format( i ), end="" )
    print()
    for i in range( 3*(num+1) ):
        print( "-", end="" )
    print()
    for i in range( 1, num+1 ):
        print( i, "|", end="" )
        for j in range( 1, num+1 ):
            print( "{:>3}".format( i*j ), end="" )
        print()

#=============================================================#
# Opgave 7.12 F

if 0:
    markeer( 'Opgave 7.12 A F' )

    # answer0712.py

    for i in range( 1, 101 ):
        for j in range( 1, i ):
            for k in range( j, i ):
                if j*j + k*k == i:
                    print( "{} = {}**2 + {}**2".format( i, j, k ) )

if 0:
    markeer( 'Opgave 7.12 B F' )

    # answer0712a.py

    from math import sqrt

    for i in range( 1, 101 ):
        for j in range( 1, int( sqrt( i ) )+1 ):
            for k in range( j, int( sqrt( i ) )+1 ):
                if j*j + k*k == i:
                    print( "{} = {}**2 + {}**2".format( i, j, k ) )

#=============================================================#
# Opgave 7.13 F

if 0:
    markeer( 'Opgave 7.13 F' )

    # answer0713.py

    from random import randint

    POGINGEN = 10000
    DOBBELSTENEN = 5

    succes = 0

    for i in range( POGINGEN ):
        laatste = 0
        for j in range( DOBBELSTENEN ):
            waarde = randint( 1, 6 )
            if waarde < laatste:
                break
            laatste = waarde
        else:
            succes += 1

    print( "De waarschijnlijkheid van een oplopende serie van vijf",
        "dobbelsteen worpen is {:.3f}".format( succes/POGINGEN ) )

#=============================================================#
# Opgave 7.14 F

if 0:
    markeer( 'Opgave 7.14 F' )

    # answer0714.py

    for A in range( 1, 10 ):
        for B in range( 10 ):
            if B == A:
                continue
            for C in range( 10 ):
                if C == A or C == B:
                    continue
                for D in range( 1, 10 ):
                    if D == A or D == B or D == C:
                        continue
                    num1 = 1000*A + 100*B + 10*C + D
                    num2 = 1000*D + 100*C + 10*B + A
                    if num1 * 4 == num2:
                        print( "A={}, B={}, C={}, D={}".format(
                            A, B, C, D ) )

#=============================================================#
# Opgave 7.15 F

if 0:
    markeer( 'Opgave 7.15 F' )

    # answer0715.py

    PIRATES = 5
    kokosnoten = 0
    while True:
        kokosnoten += 1
        stapel = kokosnoten
        for i in range( PIRATES ):
            if stapel % PIRATES != 1:
                break
            stapel = (PIRATES -1) * int( (stapel - 1) / PIRATES )
        if stapel % PIRATES == 1:
            break
    print( kokosnoten )

    print( stapel )

#=============================================================#
# Opgave 7.16 F

if 0:
    markeer( 'Opgave 7.16 A F' )

    # answer0716.py

    from random import randint

    NUMKRUIPERS = 100000
    leeftijd = NUMKRUIPERS # Ze leven minstens een dag

    for i in range( NUMKRUIPERS ):
        if randint( 0, 2 ): # Sterf niet op dag 1
            leeftijd += 1
            while randint( 0, 1 ): # Sterf niet
                leeftijd += 1

    print( "{:.2f}".format( leeftijd / NUMKRUIPERS ) )

if 0:
    markeer( 'Opgave 7.16 B F' )

    # answer0716a.py

    from random import randint

    NUMKRUIPERS = 1000000000
    num = NUMKRUIPERS
    dood = int( num / 3 )
    if NUMKRUIPERS % 3:
        if randint( 0, NUMKRUIPERS % 3 ): # Kruiper over op dag 1
            dood += 1
    leeftijd = dood # Dag-1 doden leven 1 dag
    num -= dood

    dag = 2
    while num > 0:
        dood = int( num / 2 )
        num -= dood # Dood de helft
        if dood != num: # Er is er 1 over
            if randint( 0, 1 ): # Besluit of 1 sterft
                dood, num = num, dood # Verwissel waardes
        leeftijd += dood * dag
        dag += 1

    print( "{:.2f}".format( leeftijd / NUMKRUIPERS ) )

if 0:
    markeer( 'Opgave 7.16 C F' )

    # answer0716b.py

    schatting = 1/3
    rest = 2/3
    for dagen in range( 2, 101 ):
        rest /= 2
        schatting += rest * dagen
    print( "{:.2f}".format( schatting ) )

#=============================================================#
# Opgave 8.1 F

if 0:
    markeer( 'Opgave 8.1 F' )

    # answer0801.py

    from pcinput import getInteger

    # tafel krijgt een integer als parameter. Het drukt de
    # tafel van vermenigvuldiging voor deze integer af.
    def tafel( n ):
        i = 1
        while i <= 10:
            print( i, "*", n, "=", i*n )
            i += 1

    num = getInteger( "Geef een getal: " )
    tafel( num )

#=============================================================#
# Opgave 8.2 F

if 0:
    markeer( 'Opgave 8.2 F' )

    # answer0802.py

    from pcinput import getString

    # gemeen krijgt twee strings als parameters. Het retourneert
    # het aantal tekens dat de strings gemeen hebben.
    def gemeen( w1, w2 ):
        tekens = ""
        for letter in w1:
            if (letter in w2) and (letter not in tekens):
            # if (letter in w2) and (letter not in tekens) and letter != ' ': # niet in boek
                tekens += letter
        # print( tekens ) # niet in boek
        return len( tekens )
        # return len( tekens ), tekens

    woord1 = getString( "Geef woord 1: " )
    woord2 = getString( "Geef woord 2: " )

    num = gemeen( woord1 , woord2 )
    # num, tekst = gemeen( woord1 , woord2 )
    print( tekst )
    if num <= 0:
        print( "De woorden delen geen tekens." )
    elif num == 1:
        print( "De woorden hebben 1 teken gemeen" )
    else:
        print( "De woorden hebben", num, "tekens gemeen" )

#=============================================================#
# Opgave 8.3 F

if 0:
    markeer( 'Opgave 8.3 F' )

    # answer0803.py

    # gregoryLeibnitz benadert pi middels de Gregory -Leibnitz
    # reeks. Hij krijgt 1 parameter, een integer , die aangeeft
    # hoeveel termen berekend worden. De benadering wordt als
    # float geretourneerd.
    def gregoryLeibnitz( num ): # spelfout Grerory in tekst van boek
        appr = 0
        for i in range( num ):
            if i%2 == 0:
                appr += 1/(1 + i*2)
            else:
                appr -= 1/(1 + i*2)
        return 4*appr

    print( gregoryLeibnitz( 50 ) )

    # niet in boek
    print( gregoryLeibnitz( 0 ) )
    print( gregoryLeibnitz( 1 ) )
    print( gregoryLeibnitz( 2 ) )
    print( gregoryLeibnitz( 1_000_000 ) )

#=============================================================#
# Opgave 8.4 F

if 0:
    markeer( 'Opgave 8.4 F' )

    # answer0804.py

    from pcinput import getFloat
    from math import sqrt

    # Deze functie lost een kwadratische vergelijking op.
    # De parameters zijn numerieke waardes voor A, B, and C in de
    # vergelijking Ax **2 + Bx + C = 0. Het retourneert drie waardes.
    # De eerste is een integer 0, 1, of 2, die aangeeft hoeveel
    # oplossingen er zijn. Daarna volgen de oplossingen.
    # Zonder oplossingen zijn beide 0. 1 oplossing wordt geretourneerd
    # als de eerste van de twee, en de tweede is 0.
    def wortelformule( a, b, c ):
        if a == 0:
            if b == 0:
                return 0, 0, 0
            return 1, -c/b, 0
        discriminant = b*b - 4*a*c
        if discriminant < 0:
            return 0, 0, 0
        elif discriminant == 0:
            return 1, -b/(2*a), 0
        else:
            return 2, (-b+sqrt(discriminant))/(2*a), \
                (-b-sqrt(discriminant))/(2*a)

    num, opl1, opl2 = wortelformule( getFloat( "A: " ),
    getFloat( "B: " ), getFloat( "C: " ) )
    if num == 0:
        print( "Er zijn geen oplossingen" )
    elif num == 1:
        print( "Er is 1 oplossing , namelijk", opl1 )
    else:
        print( "Er zijn 2 oplossingen , namelijk:", opl1, "en", opl2 )

#=============================================================#
# Opgave 8.5 F

if 0:
    markeer( 'Opgave 8.5 F' )

    # answer0805.py

    from pcinput import getInteger

    def getNummer( prompt ):
        while True:
            num = getInteger( prompt )
            if num < 0 or num > 1000:
                print( "Geef een getal tussen 1 en 1000" )
                continue
            return num

    def main():
        while True:
            x = getNummer( "Geef nummer 1: " )
            if x == 0:
                break
            y = getNummer( "Geef nummer 2: " )
            if y == 0:
                break
            if x%y == 0 or y%x == 0:
                print( "Fout: de nummers mogen geen delers zijn" )
                return
            print( "Vermenigvuldiging van",x,"met",y,"geeft",x*y )
        print( "Tot ziens!" )

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 8.6 F

if 0:
    markeer( 'Opgave 8.6 F' )

    # answer0806.py

    # Berekent de faculteit van de paremeter n, een integer.
    # Retourneert de uitkomst als integer.
    def faculteit( n ):
        waarde = 1
        for i in range( 2, n+1 ):
            waarde *= i
        return waarde

    # Berekent n boven k; n en k zijn integer parameters;
    # Retourneert n boven k als integer (want dat is het altijd).
    def binomiaalcoefficient( n, k ):
        if k > n:
            return 0
        return int( faculteit( n ) /
            (faculteit( k )* faculteit( n - k )) )

    def main():
        print( 'fac', 5, faculteit( 5 ) )
        print( 'bin', 8, 3, binomiaalcoefficient( 8, 3 ) )

        # niet in boek
        print( 'bin', 2, 3, binomiaalcoefficient( 2, 3 ) )
        print( 'bin', 3, 2, binomiaalcoefficient( 3, 2 ) )
        print( 'bin', 800, 300, binomiaalcoefficient( 800, 300 ) )

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 8.7 F
# zie boek Antwoord 8.7

#=============================================================#
# Opgave 9.1 F

if 0:
    markeer( 'Opgave 9.1 F' )

    # answer0901.py

    def fib( n ):
        if n <= 2:
            return 1
        return fib( n-1 ) + fib( n-2 )

    print( fib( 20 ) )

#=============================================================#
# Opgave 9.2 F

if 0:
    markeer( 'Opgave 9.2 F' )

    # answer0902.py

    def fib( n, depth ):
        indent = 6 * depth * " "
        print( "{}fib({})".format( indent , n ) )
        if n <= 2:
            print( "{}return {}".format( indent , 1 ) )
            return 1
        value = fib( n-1, depth+1 ) + fib( n-2, depth+1 )
        print( "{}return {}".format( indent , value ) )
        return value

    print( fib( 5, 0 ) )

#=============================================================#
# Opgave 9.3 F
# zie boek Antwoord 9.3

#=============================================================#
# Opgave 9.4 F

if 0:
    markeer( 'Opgave 9.4 F' )

    # answer0904.py

    def gcd( m, n ):
        if m % n == 0:
            return n
        return gcd( n, m%n )

    print( gcd( 7*5*13 , 2*3*7*11 ) )

#=============================================================#
# Opgave 9.5 F
# zie boek Antwoord 9.5

#=============================================================#
# Opgave 9.6 F

if 0:
    markeer( 'Opgave 9.6 F' )

    # answer0906.py

    GROOTTE = 4

    def hanoi( p_van, p_tmp, p_naar , grootte ):
        if grootte == 1:
            print( "Schijf 1 van", p_van, "naar", p_naar )
            return 1
        stappen = hanoi( p_van, p_naar , p_tmp, grootte -1 )
        print( "Schijf", grootte , "van", p_van, "naar", p_naar )
        stappen += 1+hanoi( p_tmp, p_van, p_naar , grootte -1 )
        return stappen

    stappen = hanoi( 'A', 'B', 'C', GROOTTE )
    print( stappen , "stappen gedaan" )

#=============================================================#
# Opgave 10.1 F

if 0:
    markeer( 'Opgave 10.1 F' )

    # answer1001.py

    text = """En Sint Atilla hief de handgranaat ten hemel, en
zeide, "O Here, zegen deze handgranaat , zodat daarmee u uw
vijanden tot kleine stukjes kunt blazen, in uwer genade."
En de Here grinnikte. En het volk laafde zich aan lammeren ,
en luiaards , en karpers , en anchovis , en orang oetans, en
cornflakes , en fruitvliegjes , en grote stu..."""

    tela, tele, teli, telo, telu = 0, 0, 0, 0, 0
    for c in text:
        if c.upper() == "A":
            tela += 1
        elif c.upper() == "E":
            tele += 1
        elif c.upper() == "I":
            teli += 1
        elif c.upper() == "O":
            telo += 1
        elif c.upper() == "U":
            telu += 1
    print( "tels: a={}, e={}, i={}, o={}, u={}".format(
        tela, tele, teli, telo, telu ) )

#=============================================================#
# Opgave 10.2 F

if 0:
    markeer( 'Opgave 10.2 F' )

    # answer1002.py

    tekst = """En ze stu[re]n [i]ngekleurde prentbriefkaarten van
plekken waarvan ze zich niet reali[s]eren dat ze er nooit
geweest zijn [a]an 'Iedereen op nummer 22, weer is prachti[g],
onz[e] kamer is aa[n]gekruisd. Missen jullie. E[t]en[ ]i[s]
vettig, maar we hebben een geweldig leuk restaurantje gevonden
in de achterstraatjes waar ze Heine[ke]n hebben en kaas en
uien chips en iemand die "Een beetje verliefd" speel[t] op een
a[c]cordeon ' en je zit vier dagen vast op Schip[h]ol voor je
vijfdaagse vliegvakantie met niks anders te eten dan
uitgedroogde voorverpakte boterhammen..."""

    start = -1
    while True:
        start = tekst.find( "[", start+1 )
        if start < 0:
            break
        eind = tekst.find( "]", start )
        if eind < 0:
            break
        print( tekst[start+1:eind], end="" )
        start = eind
    print()

#=============================================================#
# Opgave 10.3 F

if 0:
    markeer( 'Opgave 10.3 F' )

    # answer1003.py

    ch = "A"
    while ch <= "Z":
        print( ch, end=" " )
        ch = chr( ord( ch )+1 )
    print()

    for i in range( 26 ):
        rotr13 = (i + 13)%26
        ch = chr( ord( "A" ) + rotr13 )
        print( ch, end=" " )

#=============================================================#
# Opgave 10.4 F

if 0:
    markeer( 'Opgave 10.4 F' )

    # answer1004.py

    tekst = """Kapper Knap, de knappe kapper, knipt en kapt heel
knap, maar de knecht van kapper Knap, de knappe kapper,
knipt en kapt nog knapper dan kapper Knap, de knappe kapper."""

    def schoon( s ):
        ns = ""
        s = s.lower()
        for c in s:
            if c >= "a" and c <= "z":
                ns += c
            else:
                ns += " "
        return ns

    tel = 0
    for woord in schoon( tekst ).split():
        if woord == "knap":
            tel += 1

    print( "Aantal keren dat het woord \"knap\" voorkomt:", tel )

#=============================================================#
# Opgave 10.5 F

if 0:
    markeer( 'Opgave 10.5 F' )

    # answer1005.py

    tekst = "Hello, world!"
    ntekst = ""
    while len( tekst ) > 0:
        i = 0
        ch = tekst[i]
        j = 1
        while j < len( tekst ):
            if tekst[j] < ch:
                ch = tekst[j]
                i = j
            j += 1
        tekst = tekst[:i] + tekst[i+1:]
        ntekst += ch
    print( ntekst )

#=============================================================#
# Opgave 10.6 F

if 0:
    markeer( 'Opgave 10.6 F' )

    # answer1006.py

    from sys import exit

    zin = "en zo gebeurde het dat dat onze toevallige ontmoeting \
met de EErwaarde aRTHUR BElling een ommekeer betekende in ons \
leven, en vanaf dat moment gingen we iedere zondag naar \
de kerk van Sint sIMPEL bij Roombroodje MEt Jam."

    # Test of het echt wel een zin is
    if len( zin  ) <= 0:
        exit()

    # Eerste letter hoofdletter
    nieuwezin = zin[0].upper() + zin[1:]

    woordlijst = nieuwezin.split()
    laatstewoord = ""
    nieuwezin = ""

    for w in woordlijst:

        # Dubbele hoofdletters correctie
        if len( w ) > 2 and w[0] >= "A" and w[0] <= "Z" and \
            w[1] >= "A" and w[1] <= "Z" and w[2] >= "a" and \
            w[2] <= "z":
            w = w[0] + w[1].lower() + w[2:]

        # Dagen met een hoofdletter
        dag = w.lower()
        if dag == "zondag" or dag == "maandag" or dag == "dinsdag" \
            or dag == "woensdag" or dag == "donderdag" or \
            dag == "vrijdag" or dag == "zaterdag":
            w = dag[0].upper() + dag[1:]

        # CAPS LOCK correctie
        if w[0] >= "a" and w[0] <= "z":
            hoofdletters = True
            for c in w[1:]:
                if not (c >= "A" and c <= "Z"):
                    hoofdletters = False
                    break
            if hoofdletters:
                w = w[0].upper() + w[1:].lower()

        # Duplicaten verwijderen
        if w == laatstewoord:
            continue

        nieuwezin += w + " "
        laatstewoord = w

    nieuwezin = nieuwezin.strip()
    print( nieuwezin )

#=============================================================#
# Opgave 11.1 F

if 0:
    markeer( 'Opgave 11.1 F' )

    # answer1101.py

    def plus_complex( c1, c2 ):
        return (c1[0] + c2[0], c1[1] + c2[1])

    def toon_complex( c ):
        s = "("
        if c[1] == 0:
            return str( c[0] )
        elif c[0] != 0:
            s += str( c[0] )
            if c[1] > 0:
                s += "+"
        if c[1] != 1:
            if c[1] == -1:
                s += "-"
            else:
                s += str( c[1] )
        s += "i)"
        return s

    num1 = (2,1)
    num2 = (0,2)
    # num2 = (0,-2) # niet in boek
    print( toon_complex( num1 ), "+", toon_complex( num2 ), "=",
        toon_complex( plus_complex( num1, num2 ) ) )

#=============================================================#
# Opgave 11.2 F

if 0:
    markeer( 'Opgave 11.2 F' )

    # answer1102.py

    def maal_complex( c1, c2 ):
        return (c1[0]*c2[0] - c2[1]*c1[1], c1[0]*c2[1] + c1[1]*c2[0])

    def toon_complex( c ):
        return "({},{}i)".format( c[0], c[1] )

    num1 = (2,1)
    num2 = (0,2)
    # num2 = (0,-2) # niet in boek
    # num2 = (0,2.5)  # ook floats
    print( toon_complex( num1 ), "*", toon_complex( num2 ), "=",
        toon_complex( maal_complex( num1, num2 ) ) )

#=============================================================#
# Opgave 11.3 F

if 0:
    markeer( 'Opgave 11.3 F' )

    # answer1103.py

    inttuple = ( 1, 2, ( 3, 4 ), 5, ( ( 6, 7, 8, ( 9, 10 ), 11 ), 12,
        13 ), ( ( 14, 15, 16 ), ( 17, 18, 19, 20 ) ) )

    def toon_inttuple( it ):
        for element in it:
            if isinstance( element , int ):
                print( element , end=" ")
            else:
                toon_inttuple( element )

    toon_inttuple( inttuple )

#=============================================================#
# Opgave 12.1 F

if 0:
    markeer( 'Opgave 12.1 F' )

    # answer1201.py

    from random import choice

    antwoord = [ "Dat is zeker", "Het is zeker zo", "Zonder twijfel",
    "Ja, zeker", "Je kunt erop vertrouwen", "Zoals ik het zie, ja",
    "Waarschijnlijk", "Ziet er goed uit", "Ja", "Lijkt van wel",
    "Vaag, probeer het nog eens", "Vraag later nog eens", "Kan ik \
    beter niet zeggen", "Kan ik nu niet voorspellen", "Concentreer \
    je en vraag nog eens", "Reken er maar niet op", "Ik zeg van \
    niet", "Mijn bronnen zeggen van niet", "Lijkt er niet op",
    "Zeer twijfelachtig" ]

    input( "Stel je vraag aan de magische bol: " )
    print( "De magische bol zegt:", choice( antwoord ) )

#=============================================================#
# Opgave 12.2 F

if 0:
    markeer( 'Opgave 12.2 F' )

    # answer1202.py

    from random import randint

    stok = []  # "9" ontbreekt in answer1202.py in boek
    for value in ("Aas", "2", "3", "4", "5", "6", "7", "8", "9",
        "10", "Boer", "Vrouw", "Heer"):
        for kleur in ("Harten", "Schoppen", "Klaveren", "Ruiten"):
            stok.append( kleur + ' ' + value )

    for i in range( len( stok ) ):
        j = randint( i, len( stok )-1 )
        stok[i], stok[j] = stok[j], stok[i]

    for kaart in stok:
        print( kaart )

    # opgave vraagt naar functie die stok schudt
    #  in bovenstaand antwoord in boek geen functie gebruikt
    # print(len(stok))

#=============================================================#
# Opgave 12.3 F

if 0:
    markeer( 'Opgave 12.3 F' )

    # answer1203.py

    fifo = []
    while True:
        k = input( "> " )
        if k == "":
            break
        if k != "?":
            fifo.append( k )
        elif len( fifo ) > 0:
            print( fifo.pop(0) )
        else:
            print( "Lijst is leeg" )

#=============================================================#
# Opgave 12.4 F

if 0:
    markeer( 'Opgave 12.4 F' )

    # answer1204.py

    tekst = """Let op, het is heel eenvoudig om je te verdedigen
tegen een man die gewapend is met een banaan. Eerst dwing je
hem de banaan te laten vallen; dan ontwapen je hem door de
banaan op te eten. Dat maakt hem onschadelijk."""

    def tel_letter( x ):
        return x[0], -ord(x[1])

    tellist = []
    for i in range( 26 ):
        tellist.append( [0, chr(ord("a")+i)] )

    for letter in tekst.lower():
        if letter >= "a" and letter <= "z":
            tellist[ord(letter)-ord("a")][0] += 1

    tellist.sort( reverse=True, key=tel_letter )

    for tel in tellist:
        print( "{:3}: {}".format( tel[0],tel[1] ) )         

#=============================================================#
# Opgave 12.5 F

if 0:
    markeer( 'Opgave 12.5 A F' )

    # answer1205.py

    nummers = 101 * [True]
    nummers[1] = False
    for i in range( 1, len( nummers ) ):
        if not nummers[i]:
            continue
        print( i, end=" " )
        j = 2
        while j*i < len( nummers ):
            nummers[j*i] = False
            j += 1

if 0:
    markeer( 'Opgave 12.5 B F' )

    # answer1205a.py

    MAXNUM = 100
    nummers = list( range(2,MAXNUM+1) )
    i = 0
    while i < len( nummers ):
        j = i+1
        while j < len( nummers ):
            if nummers[j]%nummers[i]:
                j += 1
            else:
                nummers.pop(j)
        i += 1
    for i in nummers:
        print( i, end=" " )

#=============================================================#
# Opgave 12.6 F

if 0:
    markeer( 'Opgave 12.6 F' )

    # answer1206.py

    from pcinput import getInteger

    LEEG = "-"
    SPELERX = "X"
    SPELERO = "O"
    MAXZET = 9

    def toon_bord( b ):
        print( "  1 2 3" )
        for rij in range( 3 ):
            print( rij+1, end=" ")
            for kol in range( 3 ):
                print( b[rij][kol], end=" " )
            print()

    def opponent( p ):
        if p == SPELERX:
            return SPELERO
        return SPELERX

    def neemRowKolom( speler , wat ):
        while True:
            num = getInteger( "Speler "+speler+", welke "+wat+
                " kies je? " )
            if num < 1 or num > 3:
                print( "Geef 1, 2, of 3" )
                continue
            return num

    def winnaar( b ):
        for rij in range( 3 ):
            if b[rij][0] != LEEG and b[rij][0] == b[rij][1] \
                and b[rij][0] == b[rij][2]:
                return True
        for kol in range( 3 ):
            if b[0][kol] != LEEG and b[0][kol] == b[1][kol] \
                and b[0][kol] == b[2][kol]:
                return True
        if b[1][1] != LEEG:
            if b[1][1] == b[0][0] and b[1][1] == b[2][2]:
                return True
            if b[1][1] == b[0][2] and b[1][1] == b[2][0]:
                return True
        return False

    bord = [[LEEG,LEEG,LEEG],[LEEG,LEEG,LEEG],[LEEG,LEEG,LEEG]]
    speler = SPELERX

    toon_bord( bord )
    zet = 0
    while True:
        rij = neemRowKolom( speler , "rij" )
        kol = neemRowKolom( speler , "kolom" )
        if bord[rij -1][kol-1] != LEEG:
            print( "Rij", rij, "en kolom", kol, "is niet leeg" )
            continue
        bord[rij -1][kol-1] = speler
        toon_bord( bord )
        if winnaar( bord ):
            print( "Speler", speler , "wint!" )
            break
        zet += 1
        if zet == MAXZET:
            print( "Het is gelijkspel." )
            break
        speler = opponent( speler )

#=============================================================#
# Opgave 12.7 F

if 0:
    markeer( 'Opgave 12.7 F' )

    # answer1207.py

    from pcinput import getString
    from random import randint

    LEEG = "."
    SCHIP = "X"
    SCHEPEN = 3
    BREEDTE = 4
    HOOGTE = 3

    def toonBord( b ):
        print( "  ", end="" )
        for kol in range( BREEDTE ):
            print( chr( ord("A")+kol ), end=" " )
        print()
        for rij in range( HOOGTE ):
            print( rij+1, end=" ")
            for kol in range( BREEDTE ):
                print( b[rij][kol], end=" " )
            print()

    def plaatsSchepen( b ):
        for i in range( SCHEPEN ):
            while True:
                x = randint( 0, BREEDTE -1 )
                y = randint( 0, HOOGTE -1 )
                if b[y][x] == SCHIP:
                    continue
                if x > 0 and b[y][x-1] == SCHIP:
                    continue
                if x < BREEDTE -1 and b[y][x+1] == SCHIP:
                    continue
                if y > 0 and b[y-1][x] == SCHIP:
                    continue
                if y < HOOGTE -1 and b[y+1][x] == SCHIP:
                    continue
                break
            b[y][x] = SCHIP

    def neemDoel():
        while True:
            cel = getString( "Welke cel kies je? " ).upper()
            if len( cel ) != 2:
                print( "Geef een cel in als XY,",
                    "met X een letter en Y een cijfer" )
                continue
            if cel[0] not in "ABCD":
                print( "De letter moet tussen A en",
                    chr( ord("A")+BREEDTE -1 ), "liggen" )
                continue
            if cel[1] not in "123":
                print( "Cijfer moet tussen 1 en", HOOGTE , "liggen" )
                continue
            return ord(cel[0])-ord("A"), ord(cel[1])-ord("1")

    bord = []
    for i in range( HOOGTE ):
        rij = BREEDTE * [LEEG]
        bord.append( rij )
    plaatsSchepen( bord )
    toonBord( bord )

    raak = 0
    zetten = 0
    while raak < SCHEPEN:
        x, y = neemDoel()
        if bord[y][x] == SCHIP:
            print( "Raak!" )
            bord[y][x] = LEEG
            raak += 1
        else:
            print( "Mis!" )
        zetten += 1

    print( "Je had", zetten , "zetten nodig om mij te verslaan." )

#=============================================================#
# Opgave 12.8 F

if 0:
    markeer( 'Opgave 12.8 F' )

    # answer1208.py

    # Recursieve functie die bepaalt of intlist (integer -list) een
    # subset heeft die optelt tot totaal. Het retourneert de
    # subset, of een lege list als er geen subset is die werkt.
    def subset_telt_op_tot( intlist , totaal ):
        for num in intlist:
            if num == totaal:
                return [num]
            nlist = intlist[:]
            nlist.remove( num )
            retlist = subset_telt_op_tot( nlist, totaal -num )
            if len( retlist ) > 0:
                retlist.insert( 0, num )
                return( retlist )
        return []

    numlist = [ 3, 8, -1, 4, -5, 6 ]

    # niet in boek
    # vindt niet alle oplossingen
    # numlist = [ 3, -3, 4, -4 ]

    oplossing = subset_telt_op_tot( numlist , 0 )
    if len( oplossing ) <= 0:
        print( "Geen subset telt op tot nul" )
    else:
        for i in range( len( oplossing ) ):
            if oplossing[i] < 0 or i == 0:
                print( oplossing[i], end="" )
            else:
                print( "+{}".format( oplossing[i] ), end="" )
        print( "=0" )

#=============================================================#
# Opgave 13.1 F

if 0:
    markeer( 'Opgave 13.1 F' )

    # answer1301.py

    tekst = """Kapper Knap, de knappe kapper, knipt en kapt heel
knap, maar de knecht van kapper Knap, de knappe kapper, knipt
en kapt nog knapper dan kapper Knap, de knappe kapper."""

    def schoon( s ):
        ns = ""
        s = s.lower()
        for c in s:
            if c >= "a" and c <= "z":
                ns += c
            else:
                ns += " "
        return ns

    wdict = {}
    for woord in schoon( tekst ).split():
        wdict[woord] = wdict.get( woord, 0 ) + 1

    keylist = list( wdict.keys() )
    keylist.sort()
    for key in keylist:
        print( "{}: {}".format( key, wdict[key] ) )

#=============================================================#
# Opgave 13.2 F

if 0:
    markeer( 'Opgave 13.2 F' )

    # answer1302.py

    movies = {  "Monty Python and the Holy Grail":
                [ 9, 10, 9.5, 8.5, 3, 7.5,8 ],
                "Monty Python's Life of Brian":
                [ 10, 10, 0, 9, 1, 8, 7.5, 8, 6, 9 ],
                "Monty Python's Meaning of Life":
                [ 7, 6, 5 ],
                "And Now For Something Completely Different":
                [ 6, 5, 6, 6 ] }

    keylist = list( movies.keys() )
    keylist.sort()
    for key in keylist:
        print( "{}: {}".format( key, round(
            sum( movies[key] )/len( movies[key] ), 1 ) ) )

#=============================================================#
# Opgave 13.3 F
# zie boek Antwoord 13.3

#=============================================================#
# Opgave 14.1 F

if 0:
    markeer( 'Opgave 14.1 F' )

    # answer1401.py

    alles = { "Socrates", "Plato", "Eratosthenes", "Zeus", "Hera",
        "Athene", "Acropolis", "Kat", "Hond" }
    mensen = { "Socrates", "Plato", "Eratosthenes" }
    sterfelijken = { "Socrates", "Plato", "Eratosthenes", "Kat",
        "Hond" }

    print( mensen.issubset( sterfelijken ) ) # (a)
    print( "Socrates" in mensen ) # (b)
    print( "Socrates" in sterfelijken ) # (c)
    print( len( sterfelijken.difference( mensen ) ) > 0 ) # (d)
    print( len( alles.difference( sterfelijken ) ) > 0 ) # (e)

    # niet in boek, kan eenvoudiger
    print( '(d)', bool( sterfelijken.difference( mensen ) ) ) # (d)
    print( '(e)', bool( alles.difference( sterfelijken ) ) ) # (e)

#=============================================================#
# Opgave 14.2 F

if 0:
    markeer( 'Opgave 14.2 F' )

    # answer1402.py

    set3 = set( [3*x for x in range( 1, int( 1001/3 )+1 )] )
    set7 = set( [7*x for x in range( 1, int( 1001/7 )+1 )] )
    set11 = set( [11*x for x in range( 1, int( 1001/11 )+1 )] )

    seta = set3 & set7 & set11
    setb = (set3 & set7) - set11
    setc = set( range( 1, 1001 ) ) - set3 - set7 - set11

    # niet in boek
    print( set3 )
    # print( sorted( set3 ) )
    # print( set7 )
    # print( sorted( set7 ) )
    # print( set11 )
    # print( sorted( set11 ) )

    # print( seta )
    # print( sorted( seta ) )
    # print( setb )
    # print( sorted( setb ) )
    # print( setc )
    # print( sorted( setc ) )

    # volgens opgave getallen tussen 1 en 1000
    # set7 en set11 bevatten ook 1001
    # hoogste waarde voor x is int(1001/n)*n inclusief
    #   voor n =  3,    7,   11
    #   hoogste 999, 1001, 1001
    if 0:
        print( 1001 in set3 )  # False
        print( 1001 in set7 )  # True
        print( 1001 in set11 ) # True

#=============================================================#
# Opgave 15.1 F

if 0:
    markeer( 'Opgave 15.1 F' )

    # answer1501.py

    from os import listdir, getcwd

    flist = listdir( "." )
    for name in flist:
        print( getcwd() + "/" + name )

#=============================================================#
# Opgave 16.1 F

if 0:
    markeer( 'Opgave 16.1 F' )

    # answer1601.py

    def schoon( s ):
        nieuws = ""
        s = s.lower()
        for c in s:
            if c >= "a" and c <= "z":
                nieuws += c
            else:
                nieuws += " "
        return nieuws

    fp = open( "pc_woodchuck.txt" )
    tekst = fp.read()
    fp.close()

    wdict = {}
    for woord in schoon( tekst ).split():
        wdict[woord] = wdict.get( woord, 0 ) + 1

    keylist = list( wdict.keys() )
    keylist.sort()
    for key in keylist:
        print( "{}: {}".format( key, wdict[key] ) )

#=============================================================#
# Opgave 16.2 F

if 0:
    markeer( 'Opgave 16.2 F' )

    # answer1602.py

    def schoon( s ):
        nieuws = ""
        s = s.lower()
        for c in s:
            if c >= "a" and c <= "z":
                nieuws += c
            else:
                nieuws += " "
        return nieuws

    wdict = {}
    fp = open( "pc_woodchuck.txt" )
    while True:
        regel = fp.readline()
        if regel == "":
            break
        for woord in schoon( regel ).split():
            wdict[woord] = wdict.get( woord, 0 ) + 1
    fp.close()

    keylist = list( wdict.keys() )
    keylist.sort()
    for key in keylist:
        print( "{}: {}".format( key, wdict[key] ) )

#=============================================================#
# Opgave 16.3 F

if 0:
    markeer( 'Opgave 16.3 F' )

    # answer1603.py

    from os.path import join
    from os import getcwd

    def verwijderklinkers( regel ):
        nieuweregel = ""
        for c in regel:
            if c not in "aeiouAEIOU":
                nieuweregel += c
        return nieuweregel

    inputnaam = join( getcwd(), "pc_woodchuck.txt" )
    outputnaam = join( getcwd(), "pc_woodchuck.tmp" )

    fpi = open( inputnaam )
    fpo = open( outputnaam, "w" )

    telread = 0
    telwrite = 0

    while True:
        regel = fpi.readline()
        if regel == "":
            break
        telread += len( regel )
        regel = verwijderklinkers( regel )
        fpo.write( regel )
        telwrite += len( regel )

    fpo.close()
    fpi.close()

    print( "Gelezen:", telread )
    print( "Geschreven:", telwrite )

#=============================================================#
# Opgave 16.4 F

if 0:
    markeer( 'Opgave 16.4 F' )

    # answer1604.py

    from os.path import join
    from os import getcwd

    LEN = 3

    def schoon( s ):
        nieuws = ""
        s = s.lower()
        for c in s:
            if c >= "a" and c <= "z":
                nieuws += c
            else:
                nieuws += " "
        return nieuws

    lijst = ["pc_jabberwocky.txt","pc_rose.txt","pc_woodchuck.txt"]
    setlist = []

    for naam in lijst:
        bestandnaam = join( getcwd(), naam )
        woordset = set()
        setlist.append( woordset )
        fp = open( bestandnaam )
        while True:
            regel = fp.readline()
            if regel == "":
                break
            woordlist = schoon( regel ).split()
            for woord in woordlist:
                if len( woord ) >= LEN:
                    woordset.add( woord )
        fp.close()

    combinatie = setlist[0].copy()
    i = 1
    while i < len( setlist ):
        combinatie = combinatie & setlist[i]
        i += 1

    for woord in combinatie:
        print( woord )

#=============================================================#
# Opgave 16.5 F

if 0:
    markeer( 'Opgave 16.5 F' )

    # answer1605.py

    from os.path import join
    from os import getcwd

    lijst = ["pc_jabberwocky.txt","pc_rose.txt","pc_woodchuck.txt"]
    letterlist = [ len( lijst )*[0] for i in range( 26 ) ]
    totaallist = len( lijst ) * [0]

    # Verwerk de invoerlijst regel voor regel, maak kleine letters
    # en tel de letters in letterlist, en houd totaaltellingen
    # bij in totaallist.
    aantal = 0
    for naam in lijst:
        bestandnaam = join( getcwd(), naam )
        fp = open( bestandnaam )
        while True:
            regel = fp.readline()
            if regel == "":
                break
            regel = regel.lower()
            for c in regel:
                if c >= 'a' and c <= 'z':
                    totaallist[aantal] += 1
                    letterlist[ord(c)-ord("a")][aantal] += 1
        fp.close()
        aantal += 1

    # Schrijf tellingen in CSV formaat.
    uitvoerbestand = join( getcwd(), "pc_writetest.csv" )
    fp = open( uitvoerbestand , "w" )
    for i in range( len( letterlist ) ):
        s = "\"{}\"".format( chr( ord("a")+i ) )
        for j in range( len( lijst ) ):
            s += ",{:.5f}".format( letterlist[i][j]/totaallist[j] )
        fp.write( s+"\n" )
    fp.close()

    # Laat de inhoud van de CSV zien ter controle.
    fp = open( uitvoerbestand )
    print( fp.read() )
    fp.close()

#=============================================================#
# Opgave 17.1 F

if 0:
    markeer( 'Opgave 17.1 F' )

    # answer1701.py

    numlist = [ 100, 101, 0, "103", 104 ]
    try:
        i1 = int( input( "Geef een index: " ) )
        print( "100 /", numlist[i1], "=", 100 / numlist[i1] )
    except ValueError:
        print( "Je gaf geen geheel getal" )
    except IndexError:
        print( "De index moet tussen -5 en 4 liggen" )
    except ZeroDivisionError:
        print( "Het lijkt erop dat de list een 0 bevat" )
    except TypeError:
        print( "Het lijkt erop er een niet-numeriek element is" )
    except:
        print( "Iets onverwachts is gebeurd" )
        raise

#=============================================================#
# Opgave 18.1 F

if 0:
    markeer( 'Opgave 18.1 F' )
    
    # answer1801.py

    NAAM = "pc_rose_copy.txt"

    def toon_inhoud( filename ):
        fp = open( filename , "rb" )
        print( fp.read() )
        fp.close()

    def encryptie( filename ):
        fp = open( filename , "r+b" )
        buffer = fp.read()
        fp.seek(0)
        for c in buffer:
            if c >= 128:
                fp.write( bytes( [c-128] ) )
            else:
                fp.write( bytes( [c+128] ) )
        fp.close()

    toon_inhoud( NAAM )
    encryptie( NAAM )
    toon_inhoud( NAAM )

#=============================================================#
# Opgave 18.2 F

if 0:
    markeer( 'Opgave 18.2 F' )

    # answer1802.py

    letters = "etaoinshrdlcum "
    ongecodeerd = "Hello, world!"
    # ongecodeerd = "PP" # niet in boek

    # Toon de string zonder codering , ter controle
    print( ongecodeerd , len( ongecodeerd ) )

    # Creeer een half-byte-list als basis voor codering
    halfbytelist = []
    for c in ongecodeerd:
        if c in letters:
            halfbytelist.append( letters.index( c )+1 )
        else:
            byte = ord( c )
            halfbytelist.extend( [0, int( byte/16 ), byte%16 ] )
    if len( halfbytelist )%2 != 0:
        halfbytelist.append( 0 )

    # Maak van de half-byte-list een bytelist.
    bytelist = []
    for i in range( 0, len( halfbytelist ), 2 ):
        bytelist.append( 16* halfbytelist[i] + halfbytelist[i+1] )

    # Maak van de bytelist een bytestring en toon die ter controle.
    gecodeerd = bytes( bytelist )
    print( gecodeerd , len( gecodeerd ) )

#=============================================================#
# Opgave 18.3 F

if 0:
    markeer( 'Opgave 18.3 F' )

    # answer1803.py

    letters = "etaoinshrdlcum "
    gecodeerd = b'\x04\x81\xbb@,\xf0wI\xba\x02\x10'

    # niet in boek
    # gecodeerd = b'\x05\x00P' # ongecodeerd = 'PP'
    # bug in code voor 'PP'
    # IndexError: pop from empty list
    # PP = 0x50 0x50
    # codeert naar hex 050 050 wordt 05 00 50
    # eindigt met halve byte 0
    # bij decompress wordt laatste halve byte onterecht verwijderd

    # Toon de gecodeerde byte string ter controle.
    print( gecodeerd , len( gecodeerd ) )

    # Creeer een half-byte-list op basis van de byte string.
    halfbytelist = []
    for c in gecodeerd:
        halfbytelist.extend( [ int( c/16 ), c%16 ] )
    if halfbytelist[-1] == 0:
        del halfbytelist[-1]

    # Maak van de half-byte-string een string.
    gedecodeerd = ""
    while len( halfbytelist ) > 0:
        num = halfbytelist.pop(0)
        if num > 0:
            gedecodeerd += letters[num-1]
            continue
        # if len(halfbytelist) < 2:
        #     halfbytelist = []
        #     continue
        num = 16* halfbytelist.pop(0) + halfbytelist.pop(0)
        gedecodeerd += chr( num )

    # Toon de string, ter controle.
    print( gedecodeerd , len( gedecodeerd ) )

#=============================================================#
# Opgave 18.4 F

if 0:
    markeer( 'Opgave 18.4 F' )

    # answer1804.py

    from pcinput import getString, getLetter
    from os.path import exists, getsize

    LETTERS = b"etaoinshrdlcum "

    # Comprimeer byte string ongecodeerd , retourneer gecomprimeerd
    def comprimeer( ongecodeerd ):
        halfbytelist = []
        for c in ongecodeerd:
            if c in LETTERS:
                halfbytelist.append( LETTERS.index( c )+1 )
            else:
                halfbytelist.extend( [0, int( c/16 ), c%16 ] )
        if len( halfbytelist )%2 != 0:
            halfbytelist.append( 0 )
        bytelist = []
        for i in range( 0, len( halfbytelist ), 2 ):
            bytelist.append( 16* halfbytelist[i] + halfbytelist[i+1] )
        return bytes( bytelist )

    # Decomprimeer byte string gecodeerd , retourneer gedecomprimeerd
    def decomprimeer( gecodeerd ):
        halfbytelist = []
        for c in gecodeerd:
            halfbytelist.extend( [ int( c/16 ), c%16 ] )
        if halfbytelist[-1] == 0:
            del halfbytelist[-1]
        bytelist = []
        while len( halfbytelist ) > 0:
            num = halfbytelist.pop(0)
            if num > 0:
                bytelist.append( LETTERS[num-1] )
                continue
            num = 16* halfbytelist.pop(0) + halfbytelist.pop(0)
            bytelist.append( num )
        return bytes( bytelist )

    # Vraag om input bestand en lees de inhoud
    while True:
        filein = getString( "Geef input bestand: " )
        if not exists( filein ):
            print( filein , "bestaat niet" )
            continue
        try:
            fp = open( filein , "rb" )
            buffer = fp.read()
            fp.close()
        except IOError as ex:
            print( filein , "kan niet verwerkt worden, kies andere" )
            print( "Error [{}]: {}".format( ex.args[0], ex.args[1] ))
            continue
        break

    # Vraag om output bestand en creeer het
    while True:
        fileout = getString( "Geef output bestand: " )
        if exists( fileout ):
            print( fileout , "bestaat al" )
            continue
        try:
            fp = open( fileout , "wb" )
        except IOError as ex:
            print( fileout , "kan niet aangemaakt worden,",
                "kies een andere naam" )
            print( "Error [{}]: {}".format( ex.args[0], ex.args[1] ))
            continue
        break

    # Vraag of de gebruiker wil comprimeren of decomprimeren.
    while True:
        dc = getLetter( "Wil je (C)omprimeren of (D)ecomprimeren? " )
        if dc != 'C' and dc != 'D':
            print( "Kies C of D" )
            continue
        break

    # Comprimeer of decomprimeer de buffer.
    if dc == 'C':
        buffer = comprimeer( buffer )
    else:
        buffer = decomprimeer( buffer )

    # Sla de verwerkte buffer op in het output bestand.
    try:
        fp.write( buffer )
        fp.close()
    except IOError as ex:
        print( "Het schrijven lukte niet" )
        print( "Error [{}]: {}".format( ex.args[0], ex.args[1] ))

    # Rapporteer de groottes van input en output.
    print( getsize( filein ), "bytes gelezen" )
    print( getsize( fileout ), "bytes geschreven" )

#=============================================================#
# Opgave 19.1 F

if 0:
    markeer( 'Opgave 19.1 F' )

    # answer1901.py

    s = "Hello, world!"
    mask = (1<<5) | (1<<3) | (1<<1)   # 00101010

    codering = ""
    for c in s:
        codering += chr(ord(c)^mask)
    print( codering )

    decodering = ""
    for c in codering:
        decodering += chr(ord(c)^mask)
    print( decodering )

#=============================================================#
# Opgave 19.2 F

if 0:
    markeer( 'Opgave 19.2 F' )

    # answer1902.py

    def setBit( opslag , index, value ):
        masker = 1<<index
        if value:
            opslag |= masker
            # opslag = opslag | masker # niet in boek, zie 4.5 Verkorte operatoren
        else:
            opslag &= ~masker
        return opslag

    # getBit() retourneert 0 als de bit behorende bij de index op 1
    # staat, en anders retourneert het iets anders. Omdat alleen 0
    # als False wordt beschouwd , kun je deze functie gebruiken om de
    # waarde van een bit te testen.
    def getBit( opslag , index ):
        masker = 1<<index
        return opslag & masker

    def toonBits( opslag ):
        for i in range( 8 ):
            index = 7 - i
            if getBit( opslag , index ):
                print( "1", end="" )
            else:
                print( "0", end="" )
        print()

    opslag = 0
    opslag = setBit( opslag , 0, True )
    opslag = setBit( opslag , 1, True )
    opslag = setBit( opslag , 2, False )
    opslag = setBit( opslag , 3, True )
    opslag = setBit( opslag , 4, False )
    opslag = setBit( opslag , 5, True )
    toonBits( opslag )

    opslag = setBit( opslag , 1, False )
    toonBits( opslag )

#=============================================================#
# Opgave 20.1 F

if 0:
    markeer( 'Opgave 20.1 F' )

    # deze oplossing heeft problemen
    #   in de overlap methode
    # zie Opgave 20.1 C X hieronder

    # answer2001.py

    from copy import copy

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )

    class Rechthoek:
        def __init__( self, punt, breedte , hoogte ):
            self.punt = copy( punt )
            self.breedte = abs( breedte )
            self.hoogte = abs( hoogte )
            if self.breedte == 0:
                self.breedte = 1
            if self.hoogte == 0:
                self.hoogte = 1
        def __repr__( self ):
            return "[{},w={},h={}]".format( self.punt,
                self.breedte , self.hoogte )
        def oppervlakte( self ):
            return self.breedte * self.hoogte
        def omtrek( self ):
            return 2*( self.breedte + self.hoogte)
        def rechtsonder( self ):
            return Punt( self.punt.x + self.breedte ,
                self.punt.y + self.hoogte )
        def overlap( self, r ):
            r1, r2 = self, r
            if self.punt.x > r.punt.x or \
                (self.punt.x == r.punt.x and \
                self.punt.y > r.punt.y):
                r1, r2 = r, self
            if r1.rechtsonder().x <= r2.punt.x or \
                r1.rechtsonder().y <= r2.punt.y:
                return None
            return Rechthoek( r2.punt,
                min( r1.rechtsonder().x - r2.punt.x, r2.breedte ),
                min( r1.rechtsonder().y - r2.punt.y, r2.hoogte ) )

    r1 = Rechthoek( Punt( 1, 1 ), 8, 5 )
    r2 = Rechthoek( Punt( 2, 3 ), 9, 2 )

    print( r1.oppervlakte() )
    print( r1.omtrek() )
    print( r1.rechtsonder() )
    r = r1.overlap( r2 )
    if r:
        print( r )
    else:
        print( "De rechthoeken overlappen niet" )

#=============================================================#
# Opgave 20.2 F

if 0:
    markeer( 'Opgave 20.2 F' )

    # answer2002.py

    from datetime import date
    from random import random

    class Cursus:
        def __init__( self, naam, nummer ):
            self.naam = naam
            self.nummer = nummer
        def __repr__( self ):
            return "{}: {}".format( self.nummer , self.naam )

    class Student:
        def __init__( self, achternaam , voornaam , geb_datum , anr):
            self.achternaam = achternaam
            self.voornaam = voornaam
            self.geboortedatum = geb_datum
            self.anr = anr
            self.cursussen = []
        def __str__( self ):
            return self.voornaam+" "+self.achternaam
        def age( self ):
            vandaag = date.today()
            jaren = vandaag.year - self.geboortedatum.year
            if vandaag.month < self.geboortedatum.month or \
                (vandaag.month == self.geboortedatum.month \
                and vandaag.day < self.geboortedatum.day):
                jaren -= 1
            return jaren
        def inschrijven( self, cursus ):
            if cursus not in self.cursussen:
                self.cursussen.append( cursus )

    studenten = [
        Student( "Alikruik", "Adrie", date( 1989, 10, 3 ), 453211 ),
        Student( "Bonzo", "Beatrijs", date( 1991, 12, 29 ), 476239 ),
        Student( "Continuum", "Carola", date( 1992, 3, 7 ), 784322 ),
        Student( "Domoor", "Dirk", date( 1993, 7, 11 ), 995544 ) ]
    cursussen =[
        Cursus( "Vinologie", 787656 ),
        Cursus( "Lepelbuigen voor gevorderden", 651121 ),
        Cursus( "Onderzoeksvaardigheden: Babbelen", 433231 )]

    for student in studenten:
        for cursus in cursussen:
            if random() > 0.3:
                student.inschrijven( cursus )

    for student in studenten:
        print( "{}: {} {} ({})".format( student.anr,
            student.voornaam , student.achternaam , student.age() ) )
        if len( student.cursussen ) == 0:
            print( "\tNo cursussen" )
        for cursus in student.cursussen:
            print( "\t{}".format( cursus ) )


#=============================================================#
# Opgave 21.1 F

if 0:
    markeer( 'Opgave 21.1 F' )

    # answer2101.py

    KLEUR = ["Harten","Schoppen","Klaveren","Ruiten"]
    RANG = ["2","3","4","5","6","7","8","9","10",
        "Boer","Vrouw","Heer","Aas"]

    class Kaart:
        def __init__( self, kleur, rang ):
            self.kleur = kleur # index in KLEUR list
            self.rang = rang # index in RANG list
        def __repr__( self ):
            return "({},{})".format( self.kleur, self.rang )
        def __str__( self ):
            return "{} {}".format(KLEUR[self.kleur],RANG[self.rang])
        def __eq__( self, c ):
            if isinstance( c, Kaart ):
                return self.rang == c.rang
            return NotImplemented
        def __gt__( self, c ):
            if isinstance( c, Kaart ):
                return self.rang > c.rang
            return NotImplemented
        def __ge__( self, c ):
            if isinstance( c, Kaart ):
                return self.rang >= c.rang
            return NotImplemented

    k5 = Kaart( 2, 3 )
    r5 = Kaart( 3, 3 )
    sh = Kaart( 1, 11 )
    print( "{}, {}, {}".format( k5, r5, sh ) )
    print( k5 == r5 )
    print( k5 == sh )
    print( k5 > sh )
    print( k5 >= sh )
    print( k5 < sh )
    print( k5 <= sh )

#=============================================================#
# Opgave 21.2 F

if 0:
    markeer( 'Opgave 21.2 F' )

    # answer2102.py

    KLEUR = ["Harten","Schoppen","Klaveren","Ruiten"]
    RANG = ["2","3","4","5","6","7","8","9","10",
        "Boer","Vrouw","Heer","Aas"]

    class Kaart:
        def __init__( self, kleur, rang ):
            self.kleur = kleur
            self.rang = rang
        def __str__( self ):
            return "{} {}".format(KLEUR[self.kleur],RANG[self.rang])

    class Trekstapel:
        def __init__( self, stapel=[] ):
            self.stapel = stapel
        def __len__( self ):
            return len( self.stapel )
        def __getitem__( self, n ):
            return self.stapel[n]
        def voegtoe( self, c ):
            self.stapel.append( c )
        def trek( self ):
            if len( self ) <= 0:
                return None
            return self.stapel.pop(0)
        def __repr__( self ):
            sep = ""
            s = ""
            for c in self.stapel:
                s += sep + str( c )
                sep = ", "
            return s

    ts1 = Trekstapel( [Kaart(0,1),Kaart(0,5),Kaart(2,4),Kaart(1,12)])
    print( ts1 )
    print( ts1[1] )
    ts1.voegtoe( Kaart(3,12) )
    print( ts1 )
    print( ts1.trek() )
    print( ts1 )

#=============================================================#
# Opgave 21.3 F

if 0:
    markeer( 'Opgave 21.3 F' )

    # answer2103.py

    KLEUR = ["Harten","Schoppen","Klaveren","Ruiten"]
    RANG = ["2","3","4","5","6","7","8","9","10",
        "Boer","Vrouw","Heer","Aas"]

    class Kaart:
        def __init__( self, kleur, rang ):
            self.kleur = kleur
            self.rang = rang
        def __repr__( self ):
            return "({},{})".format( self.kleur, self.rang )
        def __str__( self ):
            return "{} {}".format(KLEUR[self.kleur],RANG[self.rang])
        def __eq__( self, c ):
            if isinstance( c, Kaart ):
                return self.rang == c.rang
            return NotImplemented
        def __gt__( self, c ):
            if isinstance( c, Kaart ):
                return self.rang > c.rang
            return NotImplemented
        def __ge__( self, c ):
            if isinstance( c, Kaart ):
                return self.rang >= c.rang
            return NotImplemented

    class Trekstapel:
        def __init__( self, stapel=[] ):
            self.stapel = stapel
        def __len__( self ):
            return len( self.stapel )
        def __getitem__( self, n ):
            return self.stapel[n]
        def voegtoe( self, c ):
            self.stapel.append( c )
        def trek( self ):
            if len( self ) <= 0:
                return None
            return self.stapel.pop(0)
        def __repr__( self ):
            sep = ""
            s = ""
            for c in self.stapel:
                s += sep + str( c )
                sep = ", "
            return s

    ts1 = Trekstapel( [Kaart(3,0), Kaart(0,11), Kaart(2,5)] )
    ts2 = Trekstapel( [Kaart(3,2), Kaart(3,1), Kaart(1,6)] )

    i = 1
    while len( ts1 ) > 0 and len( ts2 ) > 0:
        print( "Ronde", i )
        print( "Stapel1:", ts1 )
        print( "Stapel2:", ts2 )
        c1 = ts1.trek()
        c2 = ts2.trek()
        if c1 > c2:
            ts1.voegtoe( c1 )
            ts1.voegtoe( c2 )
        else:
            ts2.voegtoe( c2 )
            ts2.voegtoe( c1 )
        i += 1

    print( "Het spel is uit" )
    if len( ts1 ) > 0:
        print( "Stapel1:", ts1 )
        print( "De eerste stapel wint!" )
    else:
        print( "Stapel2:", ts2 )
        print( "De tweede stapel wint!" )

#=============================================================#
# Opgave 21.4 F

# zie ook Opgave 21.4 X

if 0:
    markeer( 'Opgave 21.4 F' )

    # answer2104.py

    from copy import deepcopy

    class Fruitmand(object):
        def __init__( self, stukken={} ):
            self.stukken = stukken

        def __repr__( self ):
            s = ""
            sep = "["
            for fruit in self.stukken:
                s += sep+fruit+":"+str( self.stukken[fruit] )
                sep = ", "
            s += "]"
            return s

        def __contains__( self, fruit ):
            return fruit in self.stukken

        def __add__( self, fruit ):
            fmcopy = deepcopy( self )
            fmcopy.stukken[fruit] = fmcopy.stukken.get( fruit, 0 )+1
            return fmcopy

        def __iadd__( self, fruit ):
            self.stukken[fruit] = self.stukken.get( fruit, 0 )+1
            return self

        def __sub__( self, fruit ):
            if fruit not in self.stukken:
                return self
            fmcopy = deepcopy( self )
            fmcopy.stukken[fruit] = fmcopy.stukken.get( fruit, 0 )-1
            if fmcopy.stukken[fruit] <= 0:
                del fmcopy.stukken[fruit]
            return fmcopy

        def __isub__( self, fruit ):
            self.stukken[fruit] = self.stukken.get( fruit, 0 )-1
            if self.stukken[fruit] <= 0:
                del self.stukken[fruit]
            return self

        def __len__( self ):
            return len( self.stukken )

        def __getitem__( self, fruit ):
            return self.stukken.get( fruit, 0 )

        def __setitem__( self, fruit, n ):
            if n <= 0:
                if fruit in self.stukken:
                    del self.stukken[fruit]
            else:
                self.stukken[fruit] = n

    fm = Fruitmand()
    fm += "appel"
    fm += "appel"
    fm += "banaan"
    fm = fm + "kers"
    fm["mango"] = 20
    print( len( fm ) )
    print( fm )
    print( "banaan" in fm )
    print( "doerian" in fm )
    fm -= "appel"
    fm -= "banaan"
    fm = fm - "kers"
    fm -= "doerian"
    print( fm )
    print( "banaan" in fm )
    fm["mango"] = 0
    print( fm )

    # fouten in code uit boek
    # 1) tweede Fruitmand krijgt kopie van vorige
    #    vanwege __init__ ={} dict mutable default argument
    #    zoals eerste lege Fruitmand() hieronder
    #    zie https://docs.python-guide.org/writing/gotchas/
    if 0:
        def init1( self, stukken=None ):
            if stukken is None:
               stukken = {}
            self.stukken = stukken
        def init2( self, stukken={} ):
            self.stukken = stukken.copy()
        def init3( self, stukken={}.copy() ):
            self.stukken = stukken
        Fruitmand.__init__ = init1
    # 2) lege Fruitmand print alleen ']'
    #    zie eerste print hieronder na 1) opgelost
    if 0:
        def repr1( self ):
            s = "["
            sep = ""
            for fruit in self.stukken:
                s += sep+fruit+":"+str( self.stukken[fruit] )
                sep = ", "
            s += "]"
            return s
        Fruitmand.__repr__ = repr1
        
    # 3) __sub__ returnt self als fruit niet in self.stukken
    #    return is dan alias voor linker argument
    #    zie '9 mand4' en '10 mand' hieronder
    #    horen verschillend te zijn
    if 0:
        def sub1( self, fruit ):
            # if fruit not in self.stukken:
            #     return self
            fmcopy = deepcopy( self )
            fmcopy.stukken[fruit] = fmcopy.stukken.get( fruit, 0 )-1
            if fmcopy.stukken[fruit] <= 0:
                del fmcopy.stukken[fruit]
            return fmcopy
        Fruitmand.__sub__ = sub1

    def main21_4( waar ): # in 21.4 F en 21.4 X
        print( waar )
        mand = Fruitmand()
        print( '1 mand', mand )
        mand += 'appel'
        mand += 'banaan'
        mand += 'appel'
        # mand.__iadd__( 'mandarijn' )
        # mand.__add__( 'perzik' )
        print( '2 mand', mand )
        mand3 = mand + 'appel' # __add__
        print( '3 mand3', mand3 )
        print( '4 mand', mand )
        mand -= 'banaan'
        print( '5 mand', mand )
        mand -= 'appel'
        print( '6 mand', mand )
        mand -= 'peer'
        print( '7 mand', mand )
        print( id(mand) )
        mand4 = mand - 'peer'
        print( id(mand), id(mand4) )
        print( '8 mand4', mand4 )
        mand4 -= 'appel'
        print( '9 mand4', mand4 )
        print( '10 mand', mand )
        if waar != 'F':
            mand2 = 'kers' + mand  # __radd__ niet in F
            print( '11 mand2', mand2 )
        print( 'peer in mand?', 'peer' in mand )
        print( 'appel in mand?', 'appel' in mand )
        print( 'appel aantal', mand['appel'] )
        mand += 'appel'
        mand += 'appel'
        print( 'appel aantal', mand['appel'] )
        mand['kers'] = 4
        print( 'mand', mand)
        print( 'mand len', len(mand))

    if __name__ == '__main__':
        main21_4( 'F' )  # zie ook Opgave 21.4 X

#=============================================================#
# Opgave 22.1 F

if 0:
    markeer( 'Opgave 22.1 F' )

    # answer2201.py

    class Rechthoek:
        def __init__( self, x, y, b, h ):
            self.x, self.y, self.b, self.h = x, y, b, h
        def __repr__( self ):
            return "[({},{}),b={},h={}]".format( self.x, self.y,
              self.b, self.h )
        def oppervlakte( self ):
            return self.b * self.h
        def omtrek( self ):
            return 2*( self.b + self.h)

    class Vierkant( Rechthoek ):
        def __init__( self, x, y, b ):
            super().__init__( x, y, b, b )

    s = Vierkant( 1, 1, 4 )
    print( s, s.oppervlakte(), s.omtrek() )

#=============================================================#
# Opgave 22.2 F

if 0:
    markeer( 'Opgave 22.2 F' )

    # answer2202.py

    from math import pi

    class Vorm:
        def oppervlakte( self ):
            return NotImplemented
        def omtrek( self ):
            return NotImplemented

    class Cirkel( Vorm ):
        def __init__( self, x, y, s ):
            self.x, self.y, self.s = x, y, s
        def __repr__( self ):
            return "[({},{}),r={}]".format( self.x, self.y, self.s )
        def oppervlakte( self ):
            return pi * self.s * self.s
        def omtrek( self ):
            return 2 * pi * self.s

    class Rechthoek( Vorm ):
        def __init__( self, x, y, b, h ):
            self.x, self.y, self.b, self.h = x, y, b, h
        def __repr__( self ):
            return "[({},{}),w={},h={}]".format( self.x, self.y,
                self.b, self.h )
        def oppervlakte( self ):
            return self.b * self.h
        def omtrek( self ):
            return 2*( self.b + self.h)

    class Vierkant( Rechthoek ):
        def __init__( self, x, y, b ):
            super().__init__( x, y, b, b )

    v = Vierkant( 1, 1, 4 )
    print( v, v.oppervlakte(), v.omtrek() )
    c = Cirkel( 1, 1, 4 )
    print( c, c.oppervlakte(), c.omtrek() )

#=============================================================#
# Opgave 22.3 F

if 0:
    markeer( 'Opgave 22.3 F' )

    # answer2203.py

    from random import random

    COOPERATIE = 'C'
    DEFECTIE = 'D'
    RONDES = 100

    class Strategie:
        def __init__( self, naam="" ):
            self.naam = naam
            self.score = 0
        def keuze( self ):
            # Moet COOPERATIE of DEFECTIE retourneren
            return NotImplemented
        def laatstezet( self, mijnzet , tegenstanderzet ):
            # Krijgt de laatste zet die gedaan is, na keuze()
            pass
        def plusscore( self, n ):
            self.score += n

    class AltijdD( Strategie ):
        def keuze( self ):
            return DEFECTIE

    class Random( Strategie ):
        def keuze( self ):
            if random() >= 0.5:
                return COOPERATIE
            return DEFECTIE

    class GeheugenStrategie( Strategie ):
        def __init__( self, naam="" ):
            super().__init__( naam )
            self.historie = []
        def laatstezet( self, mijnzet , tegenstanderzet ):
            self.historie.append( (mijnzet , tegenstanderzet) )

    class OogOmOog( GeheugenStrategie ):
        def keuze( self ):
            if len( self.historie ) < 1:
                return COOPERATIE
            return self.historie[-1][1]

    class OogOmTweeOgen( GeheugenStrategie ):
        def keuze( self ):
            if len( self.historie ) < 2:
                return COOPERATIE
            if self.historie[-1][1] == DEFECTIE and \
                self.historie[-2][1] == DEFECTIE:
                return DEFECTIE
            return COOPERATIE

    class Meerderheid( GeheugenStrategie ):
        def keuze( self ):
            telD = 0
            for i in range( len( self.historie ) ):
                if self.historie[i][1] == DEFECTIE:
                    telD += 1
            if telD > len( self.historie ) / 2:
                return DEFECTIE
            return COOPERATIE

    strategie1 = AltijdD( "Altijd Defectie" )
    strategie2 = Meerderheid( "Meerderheid" )

    for i in range( RONDES ):
        c1 = strategie1.keuze()
        c2 = strategie2.keuze()
        if c1 == c2:
            strategie1.plusscore( 3 if c1 == COOPERATIE else 1 )
            strategie2.plusscore( 3 if c2 == COOPERATIE else 1 )
        else:
            strategie1.plusscore( 0 if c1 == COOPERATIE else 6 )
            strategie2.plusscore( 0 if c2 == COOPERATIE else 6 )
        strategie1.laatstezet( c1, c2 )
        strategie2.laatstezet( c2, c1 )

    print( "Score van", strategie1.naam, "is", strategie1.score )
    print( "Score van", strategie2.naam, "is", strategie2.score )

#=============================================================#
# Opgave 23.1 F

if 0:
    markeer( 'Opgave 23.1 F' )

    # answer2301.py

    from pcinput import getInteger

    class NietDeelbaarDoor:
        def __init__( self ):
            self.seq = list( range( 1, 101 ) )
        def __iter__( self ):
            return self
        def __next__( self ):
            if len( self.seq ) > 0:
                return self.seq.pop(0)
            raise StopIteration()
        def verwerk( self, num ):
            i = len( self.seq )-1
            while i >= 0:
                if self.seq[i]%num == 0:
                    del self.seq[i]
                i -= 1
        def __len__( self ):
            return len( self.seq )

    ndd = NietDeelbaarDoor()
    while True:
        num = getInteger( "Geef een getal: " )
        if num < 0:
            print( "Negatieve getallen worden overgeslagen" )
            continue
        if num == 0:
            break
        ndd.verwerk( num )

    if len( ndd ) <= 0:
        print( "Er zijn geen getallen meer" )
    else:
        for num in ndd:
            print( num, end=" " )

#=============================================================#
# Opgave 23.2 F

if 0:
    markeer( 'Opgave 23.2 F' )

    # answer2302.py

    def faculteit():
        totaal = 1
        for i in range( 1, 11 ):
            totaal *= i
            yield totaal

    fseq = faculteit()
    for n in fseq:
        print( n, end=" " )

#=============================================================#
# Opgave 23.3 F

if 0:
    markeer( 'Opgave 23.3 F' )

    # answer2303.py

    from itertools import permutations

    woord = input( "Geef een woord: " )
    seq = permutations( woord )
    for item in seq:
        print( "".join( item ) )

#=============================================================#
# Opgave 23.4 F

if 0:
    markeer( 'Opgave 23.4 F' )

    # answer2304.py

    from itertools import permutations

    woord = input( "Geef een woord: " )
    seq = permutations( woord )
    for item in set( seq ):
        print( "".join( item ) )

#=============================================================#
# Opgave 23.5 F

if 0:
    markeer( 'Opgave 23.5 F' )

    # answer2305.py

    from itertools import combinations

    numlist = [ 3, 8, -1, 4, -5, 6 ]
    oplossing = []

    for i in range( 1, len( numlist )+1 ):
        seq = combinations( numlist , i )
        for item in seq:
            if sum( item ) == 0:
                oplossing = item
                break
        if len( oplossing ) > 0:
            break

    if len( oplossing ) <= 0:
        print( "Er is geen subset die optelt tot nul" )
    else:
        for i in range( len( oplossing ) ):
            if oplossing[i] < 0 or i == 0:
                print( oplossing[i], end="" )
            else:
                print( "+{}".format( oplossing[i] ), end="" )
        print( "=0" )

#=============================================================#
# Opgave 23.6 F

if 0:
    markeer( 'Opgave 23.6 F' )

    # answer2306.py

    from itertools import combinations

    testdict = {"a":1, "b":2, "c":3, "d":4 }
    resultaat = [ {} ]

    keylist = list( testdict.keys() )
    for lengte in range( 1, len( testdict)+1 ):
        for item in combinations( keylist , lengte ):
            subdict = {}
            for key in item:
                subdict[key] = testdict[key]
            resultaat.append( subdict )

    print( resultaat )

#=============================================================#
# Opgave 23.7 F

if 0:
    markeer( 'Opgave 23.7 F' )

    # answer2307.py

    from itertools import permutations

    SIZE = 8 # Bord grootte

    def toon_bord( kols ):
        for i in kols :
            for j in range( len( kols ) ):
                if i == j:
                    print( 'K', end=" " )
                else:
                    print( '-', end=" " )
            print()

    def is_oplossing( kols ):
        for rij in range( len( kols ) ):
            kol = kols[rij]
            for i in range( rij+1, len( kols ) ):
                if i - rij == abs( kols[i] - kol ):
                    return False
        return True

    kols = list( range( SIZE ) )

    for p in permutations( kols ):
        if is_oplossing( p ):
            toon_bord( p )
            break
    else:
        print( "Geen oplossing gevonden" ) # Should not happen.

#=============================================================#
# Opgave 24.1 F

if 0:
    markeer( 'Opgave 24.1 F A' )

    # zoals in boek, met fouten
    # zie hieronder voor verbeteringen

    # answer2401.py

    import sys

    totaal = 0
    for i in sys.argv[1:]:
        try:
            totaal += float( sys.argv[i] )
        except TypeError:
            print( sys.argv[i], "is geen getal." )
            sys.exit(1)

    print( "The arguments add up to", totaal )

if 0:
    markeer( 'Opgave 24.1 F B' )

    # zoals in boek, met verbeteringen
    # zie hierboven voor fouten

    # answer2401.py

    import sys

    totaal = 0
    if 0:
        # gaat mis met 1 of meer command line argumenten
        # for i in sys.argv[1:]: # fout in boek, i is geen integer
        for i in range( 1, len( sys.argv ) ):
            try:
                totaal += float( sys.argv[i] )
            # Python 3.11 geeft ValueError bij float( 'x' )
            # except TypeError: 
            except ValueError:
                print( sys.argv[i], "is geen getal." )
                sys.exit(1)
            
    else:
        for arg in sys.argv[1:]:
            try:
                totaal += float( arg )
            except ValueError:
                print( arg, "is geen getal." )
                sys.exit(1)

    # tekst niet vertaald
    # print( "The arguments add up to", totaal )
    print( "De argumenten tellen op naar", totaal )

#=============================================================#
# Opgave 25.1 F

if 0:
    markeer( 'Opgave 25.1 F' )

    # answer2501.py

    import re

    zin = "De prijs voor een 2-kamer appartement op  Manhattan \
start bij 1 miljoen dollars , en kan oplopen tot het tienvoudige \
op 42nd Street."

    pwoord = re.compile( r"[A-Za-z]+" )
    woordlist = pwoord.findall( zin )
    for woord in woordlist:
        print( woord )

#=============================================================#
# Opgave 25.2 F

if 0:
    markeer( 'Opgave 25.2 F' )

    # answer2502.py

    import re

    zin = "De diender arresteerde de doerak op de Dam."

    pde = re.compile( r"\bde\b", re.I )
    delist = pde.findall( zin )
    print( len( delist ) )

#=============================================================#
# Opgave 25.3 F

if 0:
    markeer( 'Opgave 25.3 F' )

    # answer2503.py

    import re

    zin = "Michael Jordan, Bill Gates, and de Dalai Lama besloten \
om samen een vliegtochtje te ondernemen , toen ze een hippie \
zagen op de landingsbaan."
#    zin = "Pipo de Clown Mama Loe"

    pnaam = re.compile( r"\b([A-Z][a-z]*\s+[A-Z][a-z]*)\b" )
#    pnaam = re.compile( r'\b[A-Z][a-z]*\b \b[A-Z][a-z]*\b' )
    naamlist = pnaam.findall( zin )
    for naam in naamlist:
#        print( naam )
        print( f"<{naam}>" )

#=============================================================#
# Opgave 25.4 F

if 0:
    markeer( 'Opgave 25.4 F' )

    # answer2504.py

    import re

    zin = "William Randolph Hearst trachtte alle exemplaren van \
Orson Welles' meesterwerk 'Citizen Kane', te vernietigen , omdat \
hij het bezwaarlijk vond dat de protagonist een nauwelijks -\
verhulde karikatuur van hemzelf was. Ik vraag me af of William \
Henry Gates De Derde hetzelfde zou doen."
# verhulde karikatuur van hemzelf was. Ik vraag me af of William3 \
# Henry Gates De Derde hetzelfde zou doen."

    pnaam = re.compile( r"\b([A-Z][a-z]*(\s+[A-Z][a-z]*)+)\b" )
#                 rex = r'(\b[A-Z][a-z]*\b( +\b[A-Z][a-z]*\b)+)'
    naamlist = pnaam.finditer( zin )
    for naam in naamlist:
        print( naam.group(1) )

#=============================================================#
# Opgave 25.5 F

if 0:
    markeer( 'Opgave 25.5 F' )

    # answer2505.py

    import re

    zin = "Klant: \"Ik wens een klacht te deponeren! \
Hallo mevrouw!\"\n\
Winkelier: \"Hoe bedoelt u, mevrouw?\"\n\
Klant: \"Het spijt me, ik ben verkouden.\"\n"

    pgesproken = re.compile( r"\"[^\"]*\"" )
#    pgesproken = re.compile( r'\"[^"]*\"' )
#    pgesproken = re.compile( r'"[^"]*"' )
#    pgesproken = re.compile( r'".*?"' )
    gesprokenlist = pgesproken.findall( zin )
    for tekst in gesprokenlist:
        print( tekst )

#=============================================================#
# Opgave 25.6 F

if 0:
    markeer( 'Opgave 25.6 F' )

    # answer2506.py

    import re

    tekst = "<html><head><title>Lijst van personen met ids</title >\
</head><body>\
<p><id>123123123</id><naam>Groucho Marx</naam>\
<p><id>123123124</id><naam>Harpo Marx</naam>\
<p><id>123123125</id><naam>Chico Marx</naam>\
<randomcrap >Etaoin<id>Shrdlu</id>qwerty</naam></randomcrap>\
<nocrap><p><id>123123126</id><naam>Zeppo Marx</naam></nocrap>\
<address>Chicago </address>\
<morerandomcrap><id>999999999</id>geennaamtezien!\
</morerandomcrap>\
<p><id>123123127</id><naam>Gummo Marx</naam>\
<note>Zoek hem op via <a href=\"http://www.google.com\">\
Google.</a></note>\
</body></html>"

    pidnaam = re.compile( r"<id>([^<]+)</id><naam>([^<]+)</naam>" )
    mlist = pidnaam.finditer( tekst )
    for m in mlist:
        print( m.group(1), m.group(2) )

#=============================================================#
# Opgave 26.1 F

if 0:
    markeer( 'Opgave 26.1 F' )

    # answer2601.py

    from csv import reader , writer

    fp = open( "pc_inventory.csv", newline= '' )
    fpo = open( "pc_writetest.csv", "w", newline= '' )
    csvreader = reader( fp )
    csvwriter = writer( fpo, delimiter= ' ' , quotechar="'" )
    for line in csvreader:
        csvwriter.writerow( line )
    fp.close()
    fpo.close()

    fp = open( "pc_writetest.csv" )
    print( fp.read() )
    fp.close()

#=============================================================#
# Opgave 26.2 F

if 0:
    markeer( 'Opgave 26.2 F' )

    # answer2602.py

    from csv import reader
    from json import dump

    data = []

    # verkeerd in boek: newregel
    # fp = open( "pc_inventory.csv", newregel= '' )
    fp = open( "pc_inventory.csv", newline= '' )
    csvreader = reader( fp )
    for regel in csvreader:
        data.append( regel )
    fp.close()

    fp = open( "pc_writetest.json", "w" )
    dump( data, fp )
    fp.close()

    fp = open( "pc_writetest.json" )
    print( fp.read() )
    fp.close()

#=============================================================#
# Opgave 27.1 F

if 0:
    markeer( 'Opgave 27.1 F' )

    # answer2701.py

    from collections import Counter

    z = "Je moeder was een hamster en je vader rook naar vlierbessen"
    zin2 = ""
    for c in z.lower():
        if c >= 'a' and c <= 'z':
            zin2 += c

    clist = Counter( zin2 ).most_common( 5 )
    for c in clist:
        print( "{}: {}".format( c[0], c[1] ) )

#=============================================================#
# Opgave 27.2 F

if 0:
    markeer( 'Opgave 27.2 F' )

    # answer2702.py

    from collections import Counter
    from pcinput import getInteger
    from statistics import mean, median
    from sys import exit

    numlist = []
    while True:
        num = getInteger( "Geef een getal: " )
        if num == 0:
            break
        numlist.append( num )

    if len( numlist ) <= 0:
        print( "Geen getallen ingegeven" )
        exit()

    print( "Gemiddelde:", mean( numlist ) )
    print( "Mediaan:", median( numlist ) )

    clist = Counter( numlist ).most_common()
    if clist[0][1] <= 1:
        print( "Er is geen modus" )
    else:
        mlist = [str( x[0] ) for x in clist if x[1] == clist[0][1] ]
        s = ", ".join( mlist )
        print( "Modus:", s )

###############################################################
# Appendix G Engelse Terminologie

# zie boek

###############################################################
# Appendix X Extra

# alternatieve antwoorden van opgaven aan einde van hoofdstukken
# zie ook Appendix F Antwoorden in het boek

#=============================================================#
# Opgave 1.1

# eerst sorteren van 2 kaarten
#   is eenvoudig, verwisselen als nodig

# sorteren van 3 kaarten
#   eerst hoogste zoeken
#     gebruik telkens manier van 2 kaarten sorteren
#   dan hoogste van 2 resterende

# sorteren van 4 kaarten
#   eerst hoogste zoeken
#   resterende zoals sorteren van 3 kaarten

# kan slimmer
#   zowel hoogste als laagste zoeken

# voorbeeld
# 5 7 3 9

# 5 7
# vraag laag/hoog?
#   laag links hoog rechts
# niet verwisselen

# 7 3
# vraag laag/hoog?
#   hoog links laag rechts
# wel verwisselen

# 5 7 3
# vraag laag/hoog linker en middelste?
#   laag linker hoog middelste
# linker en middelste niet verwisselen
#   middelste hoogste van deze 2
# 5 7 3
# vraag laag/hoog rechter en middelste?
#   laag rechter hoog middelste
# rechter en middelste verwisselen
#   rechter hoogste van deze 2
# 5 3 7
# vraag laag/hoog linker en middelste?
#   laag middelste hoog linker
# linker en middelste wel verwisselen
#   middelste hoogste van deze 2
# 3 5 7

# 5 7 3
# vraag laag/hoog linker en rechter?
#   hoog linker rechter laag
# 3 7 5
# vraag laag/hoog middelste en rechter?
#   hoog middelste rechter laag
# 3 5 7
# vraag laag/hoog linker en middelste?
#   hoog middelste linker laag
# 3 5 7

# 5 7 3 9
# A B C D
# vraag A en B?
#   5 en 7 al goed, B is hoogste van A/B
# 5 7 3 9
# vraag B en C?
#   7 en 3 niet goed, B is hoogste van A/B/C -> B naar C
# 5 3 7 9
# vraag C en D?
#   7 en 9 al goed, D is hoogste van C/D
# A/B/C verder zoals hierboven bij 3 kaarten
#   3 vragen nodig

# 5 7 3 9   9 5 3 7
# A B C D
# vraag A en B?
#   5 en 7 al goed, B is hoogste van A/B
# 5 7 3 9   5 9 3 7
# vraag C en D?
#   3 en 9 al goed, D is hoogste van C/D
# 5 7 3 9   5 9 3 7
# vraag A en C? dit is de laagste van alle 4
#   5 en 3 niet goed, A is hoogste
# 3 7 5 9   3 9 5 7
# vraag B en D? dit is de hoogste van alle 4
#   7 en 9 al goed, D is de hoogste
# 3 7 5 9   3 7 5 9
# vraag B en C? dit zijn de 2 middelste
#   7 en 5 niet goed, verwisselen
# 3 5 7 9   3 5 7 9

#=============================================================#
# Opgave 1.2

# trek 4 willekeurige verschillende kaarten uit een set van 52
# leg de 4 kaarten met de waarde naar beneden op een rij van links naar rechts
# er is dan niet te zien welke waarde een kaart heeft
# onthoudt of beter schrijf er op een papier de positie bij
# de linker is a, die ernaast b, dan c, de meest rechtse is d
# deze postities a b c d blijven hetzelfde
# de 4 kaarten kunnen van plaats wijzigen
# onthoudt dat links nabij a of richting a is en rechts richting d nabij d
# of schrijf links bij de a en rechts bij de d erbij
# dan kunnen we 2 kaarten uitkiezen waarvan er telkens 1 de linker en 1 de rechter is

# begin met de kaarten op positie a en b
# die van a is de linker, de kaart bij b is de rechter kaart
# volg dan voor a en b de eerste keer het volgende sorteerTwee recept voor 2 kaarten

# recept sorteerTwee voor 2 kaarten
# wijs de linker en rechter kaarten aan
# laat die 2 kaarten door iemand anders bekijken
# die zal of de linker of de rechter als hoogste aanwijzen
#   of bekijk ze zelf, welke is de hoogste?
# is dit de linker? zo ja verwissel dan de linker en de rechter met elkaar
# is dit de rechter? laat de kaarten dan ongewijzigd liggen
# einde recept sorteerTwee

# daarmee is de eerste stap voor a en b gereed

# doe daarna weer het sorteerTwee recept voor 2 kaarten
# voor posities met linkerkaart op b en rechterkaart op c
# dit is de tweede stap

# dan de derde stap voor c en d

# dan de vierde voor a en b en de vijfde met b en c
# dan als zesde de laatste voor a en b

# nu de kaarten omdraaien en controleren

# dit zoekt telkens de hoogste a en b met b als hoogste
# dan na b met c de hoogste van a b en c komt op positie c
# dan met d komt hoogste op d

# 1  sortTwee a b
# 2  sortTwee b c
# 3  sortTwee c d  hoogste dan in d

# 4  sortTwee a b
# 5  sortTwee b c  hoogste op 1 na dan in c

# 6  sortTwee a b  hoogste op 2 na dan in b, laagste op positie a

# laagste is hierdoor niet duidelijk
# door hoogste naar d een klein beetje te wijzigen
# kan ook laagste met maar 1 extra stap naar a verplaatst worden

# 1  sortTwee a b  hoogste van a en b op b, laagste op a
# 2  sortTwee c d  hoogste van c en d op d, laagste op c
# 3  sortTwee b d  hoogste dan op d

# 4  sortTwee a c  laagste dan op a

# 5  sortTwee b c  alleen nog middelste goed plaatsen

# Algorithm - Sort 4 number with few comparisons - Stack Overflow
# https://stackoverflow.com/questions/6145364/sort-4-number-with-few-comparisons

# Sorting network - Wikipedia
# https://en.wikipedia.org/wiki/Sorting_network

#=============================================================#
# Opgave 2.1

if 0:
    markeer( 'Opgave 2.1 X' )

    print( "Hello, world!" )

#=============================================================#
# Opgave 2.2

if 0:
    markeer( 'Opgave 2.2 A X' )

    print(7/4)

if 0:
    markeer( 'Opgave 2.2 B X' )

    7/4

#=============================================================#
# Opgave 3.1

if 0:
    markeer( 'Opgave 3.1 X' )

    # maakt gebruik van Variabelen uit hoofdstuk 4

    kosten = 24.95  # euro

    inkoopKortingProcent = 40
    inkoopKorting = inkoopKortingProcent / 100.0
    inkoopKosten = kosten - kosten * inkoopKorting

    verschepenEerste = 3.0  # euro
    verschepenOverige = 0.75  # euro

    inkoopEerste = inkoopKosten + verschepenEerste
    inkoopTweede = inkoopKosten + verschepenOverige
    inkoopDerde = inkoopTweede
    inkoopVierde = inkoopTweede

    inkoopEen  = inkoopEerste
    inkoopTwee = inkoopEen + inkoopTweede
    inkoopDrie = inkoopTwee + inkoopDerde
    inkoopVier = inkoopDrie + inkoopVierde

    inkoopEen2  = inkoopKosten * 1 + verschepenEerste + verschepenOverige * 0
    inkoopTwee2 = inkoopKosten * 2 + verschepenEerste + verschepenOverige * 1
    inkoopDrie2 = inkoopKosten * 3 + verschepenEerste + verschepenOverige * 2
    inkoopVier2 = inkoopKosten * 4 + verschepenEerste + verschepenOverige * 3

    print(1, inkoopEen, inkoopEen2)
    print(2, inkoopTwee, inkoopTwee2)
    print(3, inkoopDrie, inkoopDrie2)
    print(4, inkoopVier, inkoopVier2)

    print(5, inkoopKosten * 5 + verschepenEerste + verschepenOverige * 4)

    print(60, inkoopKosten * 60 + verschepenEerste + verschepenOverige * 59)

#=============================================================#
# Opgave 3.2

if 0:
    markeer( 'Opgave 3.2 A X' )

    # excercise0302.py
    # print( "Een boodschap" ).
    # print( "Een boodschap' )
    print( 'Een boodschapf"' )
       
if 0:
    markeer( 'Opgave 3.2 B X' )

    # verwijder punt  # Invalid syntax
    print( "Een boodschap" ) # print( "Een boodschap" ).

    # enkele naar dubbele quote # EOL while scanning string literal
    print( "Een boodschap" ) # print( "Een boodschap' )

    # verwijder extra karakters # geen Python foutmelding
    print( 'Een boodschap' ) # print( 'Een boodschapf" ' )

#=============================================================#
# Opgave 3.3

if 0:
    markeer( 'Opgave 3.3 X' )

    # genereer fout
    # ZeroDivisionError: division by zero
    1 / 0

#=============================================================#
# Opgave 3.4

if 0:
    markeer( 'Opgave 3.4 A X' )

    # excercise0304.py
    # print( ((2*3)/4 + (5-6/7) *8 )  # '(' was never closed
    print( ((12*13)/14 + (15-16)/17) *18 )

if 0:
    markeer( 'Opgave 3.4 B X' )

    # print( ((2 * 3)/4 + (5-6/7) * 8 )    # invalid syntax
    print( ((2 * 3)/4 + (5-6)/7) * 8 )   # ) toevoegen zoals bij 2e print
    print( ((12 * 13)/14 + (15-16)/17) * 18 )

#=============================================================#
# Opgave 3.5

if 0:
    markeer( 'Opgave 3.5 X' )

    # maakt gebruik van Variabelen uit hoofdstuk 4

    tijdstipUur = 14
    # tijdstipUur = 23
    alarmUren = 535
    # tijdstipAlarm = tijdstipUur + alarmUren % 24
    tijdstipAlarm = (tijdstipUur + alarmUren) % 24

    print('535 uur is', 535 // 24, 'dagen en', 535 % 24, 'uren')
    print('tijdstip', str(tijdstipUur) + ':00;',
          'alarm', str(tijdstipAlarm) +  ':00' )

    # berekening met % werkt niet altijd goed
    # zoals bij overgang van zomertijd naar wintertijd
    #   en van wintertijd naar zomertijd

    # betere manier met datetime module
    # deze wordt verderop kort besproken
    #   zie 27.1 datetime

    import datetime
    tijdstip = datetime.datetime(2021, 10, 14, 14)
    alarm = tijdstip + datetime.timedelta(hours = 535)
    print( 'tijstip', tijdstip )
    print( 'alarm', alarm )


#=============================================================#
# Opgave 4.1

if 0:
    markeer( 'Opgave 4.1 X' )

    # Versie 1.0.16, 24-10-2017, Hoofdstuk 4, opgave 4.1, bladzijde 34
    # Versie 1.1.0,  04-06-2023, Hoofdstuk 4, opgave 4.1, bladzijde 35
    # Versie 1.1.1,  17-06-2024, Hoofdstuk 4, opgave 4.1, bladzijde 35
    # willekeurige waarden als voorbeeld
    var1 = 42
    # var1 = 42.5
    var2 = 24
    var3 = 55

    gemiddelde = (var1 + var2 + var3) / 3
    print(gemiddelde)

#=============================================================#
# Opgave 4.2

if 0:
    markeer( 'Opgave 4.2 X' )

    straal = 10

    pi = 3.14159
    oppervlaktecirkel = straal * straal * pi
    print('De oppervlakte van een cirkel met straal',
          straal, 'is', oppervlaktecirkel)

    if 0:
        straal = 12
        pi = 314159
        # probeer met straal = 12 en pi = 3.14159
        print('De oppervlakte van een cirkel met straal',
              straal, 'is', straal * straal * pi)
        print('De oppervlakte van een cirkel met straal',
              straal, 'is', pi * straal * straal) # onnauwkeuriger

#=============================================================#
# Opgave 4.3

if 0:
    markeer( 'Opgave 4.3 A X' )

    bedrag = 17238  # centen

    dollars = bedrag // 100
    bedragOnder100 = bedrag - dollars * 100

    kwartjes = bedragOnder100 // 25
    bedragOnder25 = bedragOnder100 - kwartjes * 25

    dubbeltjes = bedragOnder25 // 10
    bedragOnder10 = bedragOnder25 - dubbeltjes * 10

    stuivers = bedragOnder10 // 5
    bedragOnder5 = bedragOnder10 - stuivers * 5

    centen = bedragOnder5

    print('bedrag', bedrag)
    print('aantal munten', dollars + kwartjes + dubbeltjes + stuivers + centen)

    print(dollars, kwartjes, dubbeltjes, stuivers, centen)

    totaal = dollars * 100 + kwartjes * 25 + dubbeltjes * 10 + stuivers * 5 + centen

    print('controle bedrag', bedrag, totaal)

if 0:
    markeer( 'Opgave 4.3 B X' )

    # kan ook % modulo operator gebruiken

    # kan ook divmod functie gebruiken
    # delen en mod in 1 stap
    # zie ook
    # https://docs.python.org/3/library/functions.html#divmod

    bedrag = 17238  # centen
    # bedrag = 123456 # centen
    # bedrag = 123466 # centen

    print( 'bedrag centen', bedrag )

    dollars = bedrag // 100
    print( 'dollars', dollars )

    bedrag -= dollars * 100
    # bedrag %= 100

    if 1:
        kwartjes = bedrag // 25
        print( 'kwartjes', kwartjes )

        bedrag -= kwartjes * 25
    else:
        kwartjes, bedrag = divmod( bedrag, 25 )
        print( 'kwartjes', kwartjes )

    dubbeltjes = bedrag // 10
    print( 'dubbeltjes', dubbeltjes )

    bedrag -= dubbeltjes * 10

    stuivers = bedrag // 5
    print( 'stuivers', stuivers )

    bedrag -= stuivers * 5

    centen = bedrag // 1

    print( 'centen', centen )

    bedrag -= centen * 1

    print( 'moet 0 zijn', bedrag )

    muntentotaal = dollars + kwartjes + dubbeltjes + stuivers + centen

    print( 'totaal munten', muntentotaal )

#=============================================================#
# Opgave 4.4

if 0:
    markeer( 'Opgave 4.4 A X' )

    # exercise0404.py
    a = 17
    b = 23
    print( "a =", a, "en b =", b )
    a += b
    # Voeg hier twee regels toe om a en b om te wisselen
    print( "a =", a, "en b =", b )

if 0:
    markeer( 'Opgave 4.4 B X' )

    a = 17
    b = 23
    print( "a =", a, "en b =", b )
    a += b
    # Voeg hier twee regels toe om a en b om te wisselen

    # a is zojuist al gewijzigd, dus nu b vullen
    b = a - b # is originele waarde a

    a -= b  # naar originele waarde b

    print( "a =", a, "en b =", b )

#=============================================================#
# Opgave 5.1

if 0:
    markeer( 'Opgave 5.1 X' )

    text = input("Geef string voor lengte:")
    print("Lengte is", len(text))

#=============================================================#
# Opgave 5.2

if 0:
    markeer( 'Opgave 5.2 X' )

    import math
    import pcinput

    zijde1 = pcinput.getFloat("Geef eerste zijde:")
    zijde2 = pcinput.getFloat("Geef tweede zijde:")
    schuinezijde = math.sqrt(pow(zijde1, 2) + pow(zijde2, 2))
    print("{:.3f}".format(schuinezijde))

#=============================================================#
# Opgave 5.3

if 0:
    markeer( 'Opgave 5.3 X' )

    import pcinput

    getal1 = pcinput.getFloat("Geef eerste getal:")
    getal2 = pcinput.getFloat("Geef tweede getal:")
    getal3 = pcinput.getFloat("Geef derde getal:")
    grootste = max(getal1, getal2, getal3)
    print("Grootste", grootste)
    kleinste = min(getal1, getal2, getal3)
    print("Kleinste", kleinste)
    gemiddelde = (getal1 + getal2 + getal3) / 3
    print("Gemiddelde {:.2f}".format(gemiddelde))

#=============================================================#
# Opgave 5.4

if 0:
    markeer( 'Opgave 5.4 X' )

    import math

    exp = math.exp

    e_1 = exp(-1)
    e0 = exp(0)
    e1 = exp(1)
    e2 = exp(2)
    e3 = exp(3)

    print("{:14.5f}".format(e_1))
    print("{:14.5f}".format(e0))
    print("{:14.5f}".format(e1))
    print("{:14.5f}".format(e2))
    print("{:14.5f}".format(e3))

    # print("{:4.5f}".format(e_1)) # 4. te klein, gebruikt dan toch meer

#=============================================================#
# Opgave 5.5

if 0:
    markeer( 'Opgave 5.5 X' )

    import random

    for test in range(100):
      rnd1 = random.random() # [0.0 .. 1.0>
      rnd10 = rnd1 * 10.0    # [0.0 .. 10.0>
      rnd11 = rnd10 + 1.0    # [1.0 .. 11.0>
      rnd1_10 = int(rnd11)   # [1 .. 11> = [1 .. 10]
      print(rnd1_10)

#=============================================================#
# Opgave 6.1

if 0:
    markeer( 'Opgave 6.1 A X' )

    import sys

    import pcinput

    # probeer bijvoorbeeld -1, 0, 5.5, 10.0, 5.4 en 5.51
    cijfer = pcinput.getFloat('Geef cijfer:')
    if cijfer < 0.0 or cijfer > 10.0:
        print('Cijfer moet tussen nul en tien vallen, inclusief nul en tien')
        sys.exit()

    # if int(cijfer * 10) % 5: # niet goed bij 5.51

    # % werkt ook met float
    # eerst range controleren zodat niet negatief
    #   verschil % en math.fmod
    # https://realpython.com/python-modulo-operator/#modulo-operator-with-float

    if cijfer % 0.5:
        print('Cijfer moet geheel zijn of eindigen in .5')
        sys.exit()

    if cijfer >= 8.5:
        letter = 'A'
    elif cijfer >= 7.5:
        letter = 'B'
    elif cijfer >= 6.5:
        letter = 'C'
    elif cijfer >= 5.5:
        letter = 'D'
    else:
        letter = 'F'
    print('Letter is', letter)

if 0:
    markeer( 'Opgave 6.1 B X' )

    # kan ook met match case

    import sys

    import pcinput

    cijfer = pcinput.getFloat('Geef cijfer:')
    match cijfer:
        case 8.5 | 9.0 | 9.5 | 10.0:
            print( 'A' )
        case 7.5 | 8.0:
            print( 'B' )
        case 6.5 | 7.0:
            print( 'C' )
        case 5.5 | 6.0:
            print( 'D' )
        case 0.0 | 0.5 | 1.0 | 1.5 | 2.0 | 2.5 | 3.0 | 3.5 | 4.0 | 4.5 | 5.0:
            print( 'F' )
        case _:
            print( 'Cijfer moet zijn van 0 tot en met 10,'
                   ' en geheel of eindigen op .5' )

#=============================================================#
# Opgave 6.2

if 0:
    markeer( 'Opgave 6.2 A X' )

    # exercise0602.py
    score = 98.0
    if score >= 60.0:
        oordeel = 'D'
    elif score >= 70.0:
        oordeel = 'C'
    elif score >= 80.0:
        oordeel = 'B'
    elif score >= 90.0:
        oordeel = 'A'
    else:
        oordeel = 'F'
    print( oordeel )

if 0:
    markeer( 'Opgave 6.2 B X' )

    # fout, moet met grootste beginnen bij >=
    # dus eerst >= 90.0 oordeel A, dan >= 80.0 oordeel B etc.
    score = 98.0
    if score >= 90.0:
        oordeel = 'A'
    elif score >= 80.0:
        oordeel = 'B'
    elif score >= 70.0:
        oordeel = 'C'
    elif score >= 60.0:
        oordeel = 'D'
    else:
        oordeel = 'F'
    print( oordeel )

#=============================================================#
# Opgave 6.3

if 0:
    markeer( 'Opgave 6.3 A X' )

    import pcinput

    tekst = pcinput.getString('Geef een string:')
    verschillendeKlinkers = 0
    if 'a' in tekst or 'A' in tekst:
        verschillendeKlinkers += 1
    if 'e' in tekst or 'E' in tekst:
        verschillendeKlinkers += 1
    # if 'è' in tekst or 'È' in tekst:
    #     verschillendeKlinkers += 1
    # if '€' in tekst or '$' in tekst:
    #     verschillendeKlinkers += 1
    if 'i' in tekst or 'I' in tekst:
        verschillendeKlinkers += 1
    if 'o' in tekst or 'O' in tekst:
        verschillendeKlinkers += 1
    if 'u' in tekst or 'U' in tekst:
        verschillendeKlinkers += 1
    if verschillendeKlinkers == 0:
        print('Er zitten geen verschillende klinkers in de string')
    elif verschillendeKlinkers == 1:
        print('Er zit 1 verschillende klinker in de string')
    else:
        print('Er zitten', verschillendeKlinkers, 'verschillende klinkers in de string')

if 0:
    markeer( 'Opgave 6.3 B X' )

    # kan ook met match case

    import pcinput

    tekst = pcinput.getString('Geef een string:')
    a, e, i, o, u = 0, 0, 0, 0, 0  # zie ook 7.2.4 en hoofdstuk 11
    for teken in tekst: # zie ook 7.2.2
        match teken:
            case 'a' | 'A':
                a = 1
            case 'e' | 'E':
                e = 1
            case 'i' | 'I':
                i = 1
            case 'o' | 'O':
                o = 1
            case 'u' | 'U':
                u = 1
    klinkers = a + e + i + o + u
    match klinkers:
        case 0:
            print('Er zitten geen verschillende klinkers in de string')
        case 1:
            print('Er zit 1 verschillende klinker in de string')
        case other:
            print('Er zitten', klinkers, 'verschillende klinkers in de string')

#=============================================================#
# Opgave 6.4

if 0:
    markeer( 'Opgave 6.4 X' )

    import math
    import pcinput

    print('Kwadratische vergelijkingen oplossen')
    print('Ax^2 + Bx + C = 0')
    A = pcinput.getFloat('Geef A:')
    B = pcinput.getFloat('Geef B:')
    C = pcinput.getFloat('Geef C:')
    if A == 0:
        if B == 0:
            if C == 0:
                print('Veel oplossingen voor A, B en C zijn 0: elke waarde is een oplossing')
            else:
                print('Geen oplossingen voor A en B zijn 0, en C is niet 0: aantal oplossingen 0')
        else:
            x = -C / B
            print('Aantal oplossingen: 1; oplossing voor A is 0:', x)
            print('Controle moet 0 zijn:', A * x * x + B * x + C)
    else:
        onderWortel = B * B - 4 * A * C
        if onderWortel < 0:
            print('Aantal oplossingen: 0; er zijn geen oplossingen')
        elif onderWortel == 0:
            x = -B / (2 * A)
            print('Aantal oplossingen: 1; oplossing is:', x)
            print('Controle moet 0 zijn:', A * x * x + B * x + C)
        else:
            print('Aantal oplossingen: 2')
            x1 = (-B + math.sqrt(onderWortel)) / (2 * A)
            print('Eerste oplossing is:', x1)
            print('Eerste controle moet 0 zijn:', A * x1 * x1 + B * x1 + C)
            x2 = (-B - math.sqrt(onderWortel)) / (2 * A)
            print('Tweede oplossing is:', x2)
            print('Tweede controle moet 0 zijn:', A * x2 * x2 + B * x2 + C)

#=============================================================#
# Opgave 7.1

if 0:
    markeer( 'Opgave 7.1 X' )

    from pcinput import getInteger

    getal = getInteger( "Geef getal:" )
    for tafel in range(1, 11):
        print( "{} * {} = {}".format(tafel, getal, tafel * getal) )

#=============================================================#
# Opgave 7.2

if 0:
    markeer( 'Opgave 7.2 X' )

    from pcinput import getInteger

    getal = getInteger( "Geef getal:" )
    tafel = 1
    while tafel < 11:
        print( "{} * {} = {}".format(tafel, getal, tafel * getal) )
        tafel += 1

#=============================================================#
# Opgave 7.3

if 0:
    markeer( 'Opgave 7.3 X' )

    from pcinput import getInteger

    deelbaar3 = 0
    for i in range(10):
        getal = getInteger( "Geef getal:" )
        if i == 0:
            grootste = getal
            kleinste = getal
        else:
            grootste = max(grootste, getal)
            kleinste = min(kleinste, getal)
        if getal % 3 == 0:
            deelbaar3 += 1
    print( "Grootste:", grootste)
    print( "Kleinste:", kleinste)
    print( "Aantal deelbaar door 3:", deelbaar3)

#=============================================================#
# Opgave 7.4

if 0:
    markeer( 'Opgave 7.4 X' )

    for i in range(98, -1, -1):
        if i + 1 == 1: # gelijk aan if i == 0:
            flesjes1 = "flesje"
        else:
            flesjes1 = "flesjes"
        if i == 1:
            flesjes0 = "flesje"
        else:
            flesjes0 = "flesjes"
        print(i + 1, flesjes1, "met bier op de muur,",
              i + 1, flesjes1, "met bier. Open er een, drink hem meteen,",
              i,     flesjes0, "met bier op de muur.")

#=============================================================#
# Opgave 7.5

if 0:
    markeer( 'Opgave 7.5 X' )

    n = 1000
    a = 1
    b = 1
    while a < n:
        print(a)
        c = a + b
        a = b
        b = c

#=============================================================#
# Opgave 7.6

if 0:
    markeer( 'Opgave 7.6 X' )

    woord1 = input("woord1:")
    woord2 = input("woord2:")

    samen = ""
    for letter in woord1:
        if letter in woord2 and not letter in samen:
            samen += letter
    print("Letters gemeen:", samen)

#=============================================================#
# Opgave 7.7

if 0:
    markeer( 'Opgave 7.7 X' )

    import math
    import random

    m = 0
    for n in range(10_000_000):
        x = random.random()
        y = random.random()
        # afstand = math.sqrt(x*x + y*y)
        afstand = x*x + y*y  # sqrt niet nodig als x**2 < 1 ook x < 1
        if afstand <= 1:
            m += 1
    pi = 4 * m / n
    print("Pi is", pi)

#=============================================================#
# Opgave 7.8

if 0:
    markeer( 'Opgave 7.8 X' )

    import random

    from pcinput import getInteger

    pogingen = 0
    toevalsgetal = random.randint(1, 1000)
    while True:
        getal = getInteger("Geef getal:")
        pogingen += 1
        if toevalsgetal < getal:
            print("Lager")
        elif toevalsgetal > getal:
            print("Hoger")
        else:
            print("Je hebt het geraden!")
            break
    print("Pogingen", pogingen)

#=============================================================#
# Opgave 7.9

if 0:
    markeer( 'Opgave 7.9 X' )

    from pcinput import getLetter

    laagste = 1
    hoogste = 1000
    print("Neem getal tussen 1 en 1000 in gedachten, inclusief deze waarden.")
    pogingen = 0
    while True:
        midden = laagste + (hoogste - laagste) // 2
        print("Geraden getal is", midden, "laagste", laagste, "hoogste", hoogste)
        print("Is dit getal (C)orrect of (H)oger of (L)ager?")
        letter = getLetter("Geef letter (C, H of L):").lower()
        pogingen += 1
        if letter == 'l':
            hoogste = midden - 1
        elif letter == 'h':
            laagste = midden + 1
        elif letter == 'c':
            break
        if laagste > hoogste:
            print("Ongeldige keuze, vergissing gemaakt?!")
            break
    print("Pogingen", pogingen)

#=============================================================#
# Opgave 7.10

if 0:
    markeer( 'Opgave 7.10 X' )

    from pcinput import getInteger

    getal = getInteger("Geef getal:")
    for deler in range(2, getal):
        if getal % deler == 0:
            print("Getal is geen priemgetal, deler is", deler)
            break
    else:
        print("Getal is priemgetal")

#=============================================================#
# Opgave 7.11

if 0:
    markeer( 'Opgave 7.11 X' )

    from pcinput import getInteger

    num = getInteger("Geef num:") + 1
    print('. |', end='')
    for col in range(1, num):
        print(' ', col, end='')
    print()
    print('---', end='')
    for col in range(1, num):
        print('---', end='')
    print()
    for rij in range(1, num):
        print(rij, '|', end='')
        for col in range(1, num):
            val = rij * col
            if val < 10:
                print(' ', rij * col, end='')
            else:
                print('', rij * col, end='')
        print()

#=============================================================#
# Opgave 7.12

if 0:
    markeer( 'Opgave 7.12 X' )

    for z in range(1, 101):
        for x in range(1, z):     # x < z
            for y in range(x, z): # x <= y < z
                if z == x**2 + y**2:
                    print(z, '=', str(x) + '**2', '+', str(y) + '**2')

#=============================================================#
# Opgave 7.13

if 0:
    markeer( 'Opgave 7.13 X' )

    import random

    pogingen = 1_000_000
    succes = 0
    for poging in range(pogingen):
        vorige = 0
        for steen in range(5):
            dobbel = random.randint(1, 6)
            if dobbel < vorige:
                break
            vorige = dobbel
        else:
            succes += 1
    print("Pogingen", pogingen, "succes", succes)
    print("Kans is", succes / pogingen)

#=============================================================#
# Opgave 7.14

if 0:
    markeer( 'Opgave 7.14 X' )

    for a in range(1, 10):
        for b in range(10):
            if b != a:
                for c in range(10):
                    if c != a and c != b:
                        for d in range(1, 10):
                            if d != a and d != b and d != c:
                                abcd = 1000 * a + 100 * b + 10 * c + d
                                dcba = 1000 * d + 100 * c + 10 * b + a
                                if abcd == 4 * dcba:
                                    print('Cijfers zijn', abcd, dcba)

#=============================================================#
# Opgave 7.15

if 0:
    markeer( 'Opgave 7.15 A X' )

    # Wikipedia: The monkey and the coconuts
    # https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts

    # stapel = stapel - deel 1e piraat - 1 # nacht
    # stapel = stapel - deel 2e piraat - 1 # nacht
    # stapel = stapel - deel 3e piraat - 1 # nacht
    # stapel = stapel - deel 4e piraat - 1 # nacht
    # stapel = stapel - deel 5e piraat - 1 # nacht

    # aan einde per piraat = stapel // 5 # ochtend, 1 over voor aap

    # nacht telkens 1 deel eraf, 4 delen over
    #  voor elke piraat
    #   deel = stapel // 5
    #   rest = stapel % 5, moet 1 zijn voor aap
    #   stapel = stapel - deel - 1
    # ochtend
    #   kokosnoten per piraat = stapel // 5, 1 over voor aap

    # moet precies uitkomen
    #   rest kokosnoten % 5 moet elke keer 1 zijn voor aap

    # bij 7.15 E X meer uitleg

    piraten = 5

    stapelBegin = 1
    while True:
        stapel = stapelBegin
        for piraat in range(piraten):
            deel = stapel // 5
            rest = stapel % 5
            if False: # True # stapelBegin == 15621:
                print('nacht piraat', piraat, 'stapel', stapel, 'deel', deel, 'rest', rest)
            if rest != 1:
                # print('rest niet 1')
                break
            stapel = stapel - deel - 1
        else:
            deel = stapel // 5
            rest = stapel % 5
            if False: # True: # stapelBegin == 15621:
                print('ochtend stapel', stapel, 'deel', deel, 'rest', rest)
            if rest == 1:
                # print('ochtend stapel', stapel, 'deel', deel, 'rest', rest)
                print('gelukt stapelBegin', stapelBegin)
                break

        stapelBegin += 1
        # input('Enter')

if 0:
    markeer( 'Opgave 7.15 B X' )

    piraten = 5
    
    print("van achteren naar voren")
    print(f"  piraten={piraten}")
    print(f"  weg te nemen deel 1/(piraten-1)=1/{piraten-1}e")

    aaneinde = 1  # per piraat, minimaal 1
    while True:
        kokosnoten = 1 + aaneinde * piraten # 1 voor aap
        # print(f"aan einde {aaneinde}",
        #       f"kokosnoten 1+{aaneinde}*{piraten} = {kokosnoten}")
        for piraat in range(piraten):
            # weg te nemen deel is 1/4e = 1/(piraten-1) van overgebleven
            neemtweg = kokosnoten // (piraten-1)
            rest = kokosnoten % (piraten-1)
            # print(f"    piraat{piraat} neemtweg {kokosnoten}//{piraten-1} = {neemtweg},",
            #       f"rest {kokosnoten}%{piraten-1} = {rest}", end='')
            if rest != 0:
                # print(f" is niet 0, lukt niet")
                break
            kokosnoten += 1 + neemtweg # 1 voor aap 
            # print(f"  kokosnoten+1+{neemtweg}={kokosnoten}")
        else:
            # print("  gelukt")
            break
        aaneinde += 1
        # input('Enter voor volgende')
    print("Originele stapel", kokosnoten)

if 0:
    markeer( 'Opgave 7.15 C X' )

    piraten = 5

    # werk van achteren naar voren
    aaneinde = -1 # per piraat
    # aaneinde = 0 # was een bug voor 2 piraten
    while True:
        aaneinde += 1
        kokosnoten = 1 + aaneinde * piraten
        # print("Aaneinde", aaneinde, "kokosnoten", kokosnoten)
        for piraat in range(piraten):
            if kokosnoten % (piraten - 1) != 0:
                break  # niet deelbaar, lukt niet
            neemtweg = kokosnoten // (piraten - 1) # weggehaalde deel is 1/4e van rest
            kokosnoten += neemtweg
            kokosnoten += 1  # voor aap
            # print("Aaneinde", aaneinde, "piraat", piraat,
            #       "neemtweg", neemtweg, "kokosnoten", kokosnoten)
        else:
            break  # gelukt
    print("Originele stapel", kokosnoten)

if 0:
    markeer( 'Opgave 7.15 D X' )

    piraten = 5

    # werk van achteren naar voren
    aaneinde = -1 # per piraat
    # aaneinde = 0 # was een bug voor 2 piraten
    while True:
        aaneinde += 1
        kokosnoten = 1 + aaneinde * piraten
        # print("Aaneinde", aaneinde, "kokosnoten", kokosnoten)
        for piraat in range(piraten):
            kokosnoten += 1  # voor aap # in plaats van aan einde
            if kokosnoten % (piraten - 1) != 1:  # rest moet dan 1 zijn
                break  # niet deelbaar, lukt niet
            neemtweg = kokosnoten // (piraten - 1) # weggehaalde deel is 1/4e van rest
            kokosnoten += neemtweg
            # print("Aaneinde", aaneinde, "piraat", piraat,
            #       "neemtweg", neemtweg, "kokosnoten", kokosnoten)
        else:
            break  # gelukt
    print("Originele stapel", kokosnoten)

if 0:
    markeer( 'Opgave 7.15 E X' )

# https://en.wikipedia.org/wiki/17-animal_inheritance_puzzle

# erfenis
#  verdeel 17 kamelen
#  oudste helft
#  middelste een derde
#  jongste een negende
# wijze helpt
#   leent hen een kameel
# dan verdelen eenvoudig
#   1 kameel over
#   terug naar wijze

# 17 + 1 = 18
# 18 / 2 = 9
# 18 / 3 = 6
# 18 / 9 = 2
# 18 - 9 - 6 - 2 = 1

# 5 piraten, zonder aap, zonder deel wegnemen
# van einde naar begin
#  aan einde per piraat 1 is minimum
#      ronde  na verdelen voor verdelen
#  ochtend 6     1            5 = 5 ** 1
#  nacht   5     5           25 = 5 ** 2
#  nacht   4    25          125 = 5 ** 3
#  nacht   3   125          625 = 5 ** 4
#  nacht   2   625         3125 = 5 ** 5
#  nacht   1  3125        15625 = 5 ** 6

# kleiner kan niet
#  moet elke ronde deelbaar zijn door 5

# https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts#The_original_version

# 5 piraten, met aap, met 1/5 deel wegnemen 4/5 deel over

#  met vergelijkingen
#    van begin naar einde
#      n = 5 * a + 1
#  4 * a = 5 * b + 1    a = (5 * b + 1) / 4
#  4 * b = 5 * c + 1    b = (5 * c + 1) / 4
#  4 * c = 5 * d + 1    c = (5 * d + 1) / 4
#  4 * d = 5 * e + 1    d = (5 * e + 1) / 4
#  4 * e = 5 * f + 1    e = (5 * f + 1) / 4

#      n = 5 * (5 * b + 1) / 4 + 1
#  4 * n = 5 * (5 * b + 1)     + 4 * 1 =
#          5 *  5 * b + 5      + 4     = 25 * b + 9

#   4 * n = 25 * (5 * c + 1) / 4 + 9
#  16 * n = 25 * (5 * c + 1)     + 4 * 9 =
#           25 *  5 * c + 25     + 36    = 125 * c + 61

#  16 * n = 125 * (5 * d + 1) / 4 + 61
#  64 * n = 125 * (5 * d + 1)     + 4 * 61 =
#           125 *  5 * d + 125    + 244    = 625 * d + 369

#   64 * n = 625 * (5 * e + 1) / 4 + 369
#  256 * n = 625 * (5 * e + 1)    + 4 * 369 =
#            625 *  5 * e + 625   + 1476    = 3125 * e + 2101

#   256 * n = 3125 * (5 * f + 1) / 4 + 2101
#  1024 * n = 3125 * (5 * f + 1)    + 4 * 2101 =
#             3125 * 5 * f + 3125 + 8404 = 15625 * f + 11529

#  1024 * n = 15625 * f + 11529

#  eenvoudige oplossing met negatieve getallen
#   n = -4, f = -1
#    1024 * -4 =  15625 * -1 + 11529
#    -4096     = -15625 + 11529

#  kleinste positieve oplossing
#   n = -4 + 15625 = 15621 , f = -1 + 1024 = 1023
#    1024 * 15621 =  15625 * 1023 + 11529
#    15995904     =  15984375 + 11529

# https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts#Blue_coconuts

#  met blauwe kokosnoten
#    van begin naar einde
#  aan begin 15625 waarvan 4 blauw geverfd
#   zijn in 6 rondes deelbaar door 5 want 15625 = 5 ** 6
#   blauwe gaan begin elke ronde erbij, einde ronde eraf
#    tellen niet mee voor uitkomst

#  ronde nacht 1
#   stapel 15621 gewone + 4 blauwe = 15625
#   5 delen 15625 / 5 = 3125 per deel
#   1 deel 3125 zonder blauwe eraf
#     3124 naar piraat, 1 naar aap
#   4 delen elk met een blauwe
#    stapel 3124 * 4 gewone + 4 blauwe = 12500

#  ronde nacht 2
#   stapel 12496 gewone + 4 blauwe = 12500
#   5 delen 12500 / 5 = 2500 per deel
#   1 deel 2500 zonder blauwe eraf
#     2499 naar piraat, 1 naar aap
#   4 delen elk met een blauwe
#    stapel 2499 * 4 gewone + 4 blauwe = 10000

#  ronde nacht 3
#   stapel 9996 gewone + 4 blauwe = 10000
#   5 delen 10000 / 5 = 2000 per deel
#   1 deel 2000 zonder blauwe eraf
#     1999 naar piraat, 1 naar aap
#   4 delen elk met een blauwe
#    stapel 1999 * 4 gewone + 4 blauwe = 8000

#  ronde nacht 4
#   stapel 7996 gewone + 4 blauwe = 8000
#   5 delen 8000 / 5 = 1600 per deel
#   1 deel 1600 zonder blauwe eraf
#     1599 naar piraat, 1 naar aap
#   4 delen elk met een blauwe
#    stapel 1599 * 4 gewone + 4 blauwe = 6400

#  ronde nacht 5
#   stapel 6386 gewone + 4 blauwe = 6400
#   5 delen 6400 / 5 = 1280 per deel
#   1 deel 1280 zonder blauwe eraf
#     1279 naar piraat, 1 naar aap
#   4 delen elk met een blauwe
#    stapel 1279 * 4 gewone + 4 blauwe = 5120

#  ronde ochtend 6
#   stapel 5116 gewone + 4 blauwe = 5120
#   5 delen 5120 / 5 = 1024 per deel
#   1 deel 1024 zonder blauwe eraf
#     1023 naar piraat, 1 naar aap
#   4 delen elk met een blauwe
#    stapel 1023 * 4 gewone + 4 blauwe = 4096
#  is einde
#   5120 = 5 * 1023 naar piraten, 1 naar aap, 4 blauwe over

# 4096 = 2**12 niet deelbaar door 5
#   kleiner kan niet
#   overblijvende deel is telkens een veelvoud van 4
#     is niet deelbaar door 5
#   stapel met 4 keer een 5e deel
#     zorgt niet voor extra mogelijk delen door 5

# aan begin stapel 15621, blauwe erbij 4
# aan einde elke piraat 1023, aap 1, blauwe over 4
#   5 * 1023 + 1 + 4 = 5 * 1023 + 5 = 5 * 1024 = 5120

# met 4 blauwe
#      ronde  voor verdelen  1/5e   4/5e na verdelen
#  nacht   1  15625        = 3125 + 12500
#  nacht   2  12500        = 2500 + 10000
#  nacht   3  10000        = 2000 +  8000
#  nacht   4   8000        = 1600 +  6400
#  nacht   5   6400        = 1280 +  5120
#  ochtend 6   5120        = 1024 +  4096

# zonder 4 blauwe
#      ronde  voor verdelen  1/5e   4/5e na verdelen
#  nacht   1  15621        = 3124 + 12496 + 1 voor aap
#  nacht   2  12496        = 2499 +  9996 + 1
#  nacht   3   9996        = 1999 +  7996 + 1
#  nacht   4   7996        = 1599 +  6396 + 1
#  nacht   5   6396        = 1279 +  5116 + 1
#  ochtend 6   5116        = 1023 +  4092 + 1

# https://archive.nytimes.com/wordplay.blogs.nytimes.com/2013/10/07/gardner-2/

# met formules
# begin stapel n geheel getal
# elke ronde voor 5 piraten
#  n = 4 * (n - 1) / 5
# herschrijven zoals met blauwe
#  n = 4 * (n + 4) / 5 - 4

#  n = 4 * (n - 1) / 5
#  n = 4 * n / 5 - 4 / 5
#  n = (4 * n / 5 - 4 / 5) + 20 / 5 - 20 / 5
#  n = (4 * n / 5 - 4 / 5 + 20 / 5) - 20 / 5
#  n = (4 * n - 4 + 20) / 5 - 20 / 5
#  n = (4 * n + 16) / 5 - 20 / 5
#  n = 4 (n + 4) / 5 - 4

# voor 5 + 1 = 6 rondes
#    4 * (n + 4) / 5 - 4
#   16 * (n + 4) / 25 - 4
#   64 * (n + 4) / 125 - 4
#  256 * (n + 4) / 625 - 4
# 1024 * (n + 4) / 3125 - 4
# 4096 * (n + 4) / 15625 - 4

# n moet geheel getal zijn
#   n + 4 = 15625
#   n = 15625 - 4 = 15621

# kan niet eenvoudiger als geheel getal
#  bij ontbinden in factoren geen gelijke
#  4096 en 15625

# https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts#Other_variants_and_general_solutions

# voor stapel n en piraten m
#  met m-1 blauwe en m+1 rondes
#   n + (m-1) = m ** (m+1)
#   n = m ** (m+1) - (m-1)

# kan niet eenvoudiger als geheel getal
#  bij ontbinden in factoren geen gelijke
#  (m-1) ** (m+1) en m ** (m+1)

# https://www.debacker.info/kokosnoten_verdelen.php

# oplossing is onduidelijk, formules lijken niet te kloppen
# lijkt op variant met blauwe kokosnoten

# er staat:
#  p aantal in originele stapel

#  4/5 × 4/5 × 4/5 × 4/5 × 4/5 × 4/5 × (p+4) = (46/56) × (p + 4)
#  kleinste geheel getap p + 4 is 56, p = 56 - 4 = 15621

# met op enkele plaatsen ** erbij klopt het wel:

#  4/5 × 4/5 × 4/5 × 4/5 × 4/5 × 4/5 × (p+4) = (4**6 / 5**6) × (p + 4)
#  kleinste geheel getal p + 4 is 5**6, p = 5**6 - 4 = 15621

    # afgeleid van 7.15 F
    def verdeel1(PIRATES):
        # PIRATES = 5
        kokosnoten = 0
        while True:
            kokosnoten += 1
            stapel = kokosnoten
            for i in range( PIRATES ):
                if stapel % PIRATES != 1:
                    break
                stapel = (PIRATES -1) * int( (stapel - 1) / PIRATES )
            if stapel % PIRATES == 1:
                break
        # print( kokosnoten )
        return kokosnoten

    # afgeleid van 7.15 C X
    def verdeel2(piraten):
        # werk van achteren naar voren
        aaneinde = -1 # per piraat
        # aaneinde = 0 # was een bug voor 2 piraten
        while True:
            aaneinde += 1
            kokosnoten = 1 + aaneinde * piraten
            # print("Aaneinde", aaneinde, "kokosnoten", kokosnoten)
            for piraat in range(piraten):
                if kokosnoten % (piraten - 1) != 0:
                    break  # niet deelbaar, lukt niet
                neemtweg = kokosnoten // (piraten - 1) # weggehaalde deel is 1/4e van rest
                kokosnoten += neemtweg
                kokosnoten += 1  # voor aap
                # print("Aaneinde", aaneinde, "piraat", piraat,
                #       "neemtweg", neemtweg, "kokosnoten", kokosnoten)
            else:
                break  # gelukt
        # print("Originele stapel", kokosnoten)
        return kokosnoten

    for piraten in range(2, 7):
    # for piraten in range(2, 9): # langzamer, even wachten
    # for piraten in range(2, 20): # alleen bruikbaar met formule 
        print(piraten,
              verdeel1(piraten),
              verdeel2(piraten),
              piraten ** (piraten + 1) - (piraten - 1)
              )


#=============================================================#
# Opgave 7.16

if 0:
    markeer( 'Opgave 7.16 A X' )

    import random

    aantal = 100_000
    dagen = 0
    for sluiper in range(aantal):
        sluiperdagen = 1
        if random.random() > 1/3: # van a naar b of c, niet naar d
            while True:
                sluiperdagen += 1
                if random.random() < 1/2: # niet naar d
                    break
        dagen += sluiperdagen
    print("dagen", dagen)
    print("Kans", dagen / aantal)
            
if 0:
    markeer( 'Opgave 7.16 B X' )

    aantal = 1_000_000
    # aantal = 1_000_000_000_000
    dagen = aantal # eerste dag

    inAvanB = 0
    inAvanC = 0
    # inBvanA = aantal // 3 + random.randint(0, aantal % 3)
    inBvanA = int(aantal * 1/3)
    inBvanC = 0
    inCvanA = int(aantal * 1/3)
    inCvanB = 0

    while inAvanB + inAvanC + inBvanA + inBvanC + inCvanA + inCvanB:
        dagen += inAvanB + inAvanC + inBvanA + inBvanC + inCvanA + inCvanB
        # niet meteen naar inXvanY, originele nog nodig
        naarAvanB = int(inBvanC * 1/2)
        naarAvanC = int(inCvanB * 1/2)
        naarBvanA = int(inAvanC * 1/2)
        naarBvanC = int(inCvanA * 1/2)
        naarCvanA = int(inAvanB * 1/2)
        naarCvanB = int(inBvanA * 1/2)
        # naarDvanX = ..
        inAvanB = naarAvanB
        inAvanC = naarAvanC
        inBvanA = naarBvanA
        inBvanC = naarBvanC
        inCvanA = naarCvanA
        inCvanB = naarCvanB
        # inDvanX = naarDvanX
        # inDvanX = 0
    print("dagen", dagen)
    print("kans", dagen / aantal)

#=============================================================#
# Opgave 8.1

if 0:
    markeer( 'Opgave 8.1 X' )

    # zie ook opgave 7.1

    # neveneffect: druk tafel van vermenigvuldiging af
    #    voor 1 tot en met 10 van getal
    # argument: getal is int
    # return: geen
    def printTafel(getal):
        for i in range(1, 11):
            print(i, '*', getal, '=', i * getal)
        print()

    printTafel(12)
    printTafel(1)
    printTafel(100)

#=============================================================#
# Opgave 8.2

if 0:
    markeer( 'Opgave 8.2 X' )

    # bepaal aantal tekens dat tekst1 en tekst2 gemeen hebben
    # argument: tekst1 en tekst2 zijn str
    # return: aantal is int
    def aantalGemeen(tekst1, tekst2):
        gemeen = ''
        for teken in tekst1:
            if teken in tekst2:
                if not teken in gemeen:
                    gemeen += teken

            # vergelijkbaar met
            # if teken in tekst2 and not teken in gemeen:
            #     gemeen += teken

        return len(gemeen)

    # print aantal tekens dat tekst1 en tekst2 gemeen hebben
    # argument: tekst1 en tekst2 zijn str
    # return: geen
    def printAantalGemeen(tekst1, tekst2):
        print(tekst1, '><', tekst2, aantalGemeen(tekst1, tekst2))

    printAantalGemeen('een', 'peer')
    printAantalGemeen('parameter', 'peer')
    printAantalGemeen('xylofoon', 'quick brown fox')

#=============================================================#
# Opgave 8.3

if 0:
    markeer( 'Opgave 8.3 X' )

    # benader pi door de Gregory-Leibniz reeks
    # argument: aantal termen in berekening als int
    # return: benadering van pi als float
    def piGregoryLeibniz(aantalTermen):
        quartPi = 0.0
        for index in range(aantalTermen):
            noemer = 2 * index + 1  # 1, 3, 5, 7, ...
            term = 1.0 / noemer  # 1/1, 1/3, 1/5, 1/7, ...
            if index % 2:
            # if (index % 2) != 0:
                term = -term  # -1/3, -1/7, ...
            quartPi += term
            # print(aantalTermen, noemer, term, quartPi)
        return 4 * quartPi

    for term in range(0, 200):
        print(piGregoryLeibniz(term))

#=============================================================#
# Opgave 8.4

if 0:
    markeer( 'Opgave 8.4 X' )

    # wortelformule
    # eerder in opgave 6.4

    import pcinput

    if 1:
        import math

        # bereken oplossingen x van een kwadratische vergelijking
        # a * x^2 + b * x + c = 0
        # parameters float a b c
        # returnt 3 waardes: int aantal oplossingen, float eerste, floar tweede oplossing
        def wortelFormule(a, b, c):
            if a == 0:
                if b == 0:
                    if c == 0:
                        return 2, 0, 0 # elke waarde is een oplossing
                    return 0, 0, 0 # geen oplossingen
                x = -c / b
                return 1, x, 0 # 1 oplossing
            onderWortel = b * b - 4 * a * c
            # print( 'Discriminant:', onderWortel )
            if onderWortel < 0:
                return 0, 0, 0 # geen oplossingen
            if onderWortel == 0:
                x = -b / (2 * a)
                return 1, x, 0 # 1 oplossing
            x1 = (-b + math.sqrt(onderWortel)) / (2 * a)
            x2 = (-b - math.sqrt(onderWortel)) / (2 * a)
            return 2, x1, x2 # 2 oplossingen
    else:
        # niet in boek, gebruik complexe getallen
        import cmath

        def wortelFormule(a, b, c):
            onderWortel = b * b - 4 * a * c
            x1 = (-b + cmath.sqrt(onderWortel)) / (2 * a)
            x2 = (-b - cmath.sqrt(onderWortel)) / (2 * a)
            return 2, x1, x2
        
    print('Kwadratische vergelijkingen oplossen')
    print('Ax^2 + Bx + C = 0')
    # A = pcinput.getFloat('Geef A:')
    # B = pcinput.getFloat('Geef B:')
    # C = pcinput.getFloat('Geef C:')
    # A, B, C = 2, 4, 3   # geen oplossingen
    # A, B, C = 2, 4, 2 # 1 oplossing
    A, B, C = 2, 4, -6  # 2 oplossingen
    print('A', A, 'B', B, 'C', C)
    aantalOplossingen, oplossing1, oplossing2 = wortelFormule(A, B, C)
    print('Aantal oplossingen:', aantalOplossingen,
          'oplossing1', oplossing1, 'oplossing2', oplossing2)
    print('Controle1 wordt 0:', A * oplossing1 * oplossing1 + B * oplossing1 + C)
    print('Controle2 wordt 0:', A * oplossing2 * oplossing2 + B * oplossing2 + C)

#=============================================================#
# Opgave 8.5

if 0:
    markeer( 'Opgave 8.5 A X' )

    # exercise0805.py
    from pcinput import getInteger
    from sys import exit

    while True:
        x = getInteger( "Geef nummer 1: " )
        if x == 0:
            break
        y = getInteger( "Geef nummer 2: " )
        if y == 0:
            break
        if (x < 0 or x > 1000) or (y < 0 or y > 1000):
            print( "De nummers moeten tussen 0 en 1000 liggen" )
            continue
        if x%y == 0 or y%x == 0:
            print( "Fout: de nummers mogen geen delers zijn" )
            exit()
        print( "Vermenigvuldiging van", x, "met", y, "geeft", x * y )

    print( "Tot ziens!" )

if 0:
    markeer( 'Opgave 8.5 B X' )

    from pcinput import getInteger
    # from sys import exit

    # vraag gebruiker om een geheel getal tussen laag en hoog inclusief
    # print een foutmelding als een waarde buiten hoog wordt gevraagd
    # en vraagt dan opniew een waarde
    # prompt str, laag en hoog int
    # returnt het getal tussen laag en hoog
    def getIntegerTussen(prompt, laag, hoog):
        while True:
            val = getInteger(prompt)
            if val >= laag and val <= hoog:
                return val
            print('Het nummer moet tussen', laag, 'en', hoog, 'liggen')

    def main():        
        while True:
            x = getIntegerTussen( "Geef nummer 1: ", 0, 1000 )
            if x == 0:
                break
            y = getIntegerTussen( "Geef nummer 2: ", 0, 1000 )
            if y == 0:
                break
            # if (x < 0 or x > 1000) or (y < 0 or y > 1000):
            #     print( "De nummers moeten tussen 0 en 1000 liggen" )
            #     continue
            if x%y == 0 or y%x == 0:
                print( "Fout: de nummers mogen geen delers zijn" )
                return
            print( "Vermenigvuldiging van", x, "met", y, "geeft", x * y )
        print( "Tot ziens!" )

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 8.6

if 0:
    markeer( 'Opgave 8.6 X' )

    # gebruikt faculteit code uit tweede opgave bij 7.1.5

    # bereken faculteit van factor
    # parameter factor int > 0
    # returnt int faculteit
    def faculteit(factor):
        facult = 1
        while factor > 1:  # factor != 0:  # > 1 is sneller want is al 1
            facult *= factor
            factor -= 1
        return facult

    # print( "Faculteit is", faculteit(5))

    # bereken n boven k, n >= k
    # parameters n en k int
    # returnt int binomiaalcoëfficiënt
    def binomiaalCoefficient(n, k):
        teller = faculteit(n)
        noemer = faculteit(k) * faculteit(n - k)
        return teller // noemer

    def main():
        def printBoven():
            print('n:', n, 'k:', k, 'n boven k:', binomiaalCoefficient(n, k))

        n, k = 4, 2 # 6
        printBoven()
        n, k = 5, 3 # 10
        printBoven()
        n, k = 6, 4 # 15
        printBoven()
        n, k = 6, 3 # 20
        printBoven()
        n, k = 43, 7 # 32224114
        printBoven()

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 8.7

if 0:
    markeer( 'Opgave 8.7 A X' )

    # exercise0807.py
    # Wat is er fout?
    def oppervlakte_van_driehoek( basis, hoogte ):
        opp = 0.5 * basis * hoogte
        # niet in boek, hoort ook 'basis' in tekst
        print( "Een driehoek met basis", basis, "en hoogte",
            hoogte , "heeft oppervlakte", opp )

    print( oppervlakte_van_driehoek( 4.5, 1.0 ) )

if 0:
    markeer( 'Opgave 8.7 B X' )

    # de tweede print geeft None omdat de functie alleen print en niets returnt
    # print("Verbeterde code:")

    def oppervlakte_van_driehoek( basis, hoogte ):
        opp = 0.5 * basis * hoogte
        return opp

    basis = 4.5
    hoogte = 1.0
    print( "Een driehoek met basis", basis, "en hoogte",
           hoogte, "heeft oppervlakte",
           oppervlakte_van_driehoek( basis, hoogte ) )

#=============================================================#
# Opgave 9.1

if 0:
    markeer( 'Opgave 9.1 X' )

    def fib(n):
        if n == 1 or n == 2:
            return 1
        return fib(n - 1) + fib(n - 2)

    # return fib van n en fib van n - 1
    # voor n == 1 tweede resultaat 1 niet gebruikt
    #    had dan eigenlijk return 1, 0 moeten doen
    def fib2(n):
        if n == 1 or n == 2:
            return 1, 1
        fibMin1, fibMin2 = fib2(n - 1)
        return fibMin1 + fibMin2, fibMin1

    for i in range(1, 22):
        print(i, fib(i), fib2(i)[0])

    print(fib2(37)[0])  # fast
    print(fib(37))      # slow

#=============================================================#
# Opgave 9.2

if 0:
    markeer( 'Opgave 9.2 X' )

    def fib(n, diepte):
        print(' ' * diepte, '>', n)
        if n == 1 or n == 2:
            result = 1
        else:
            result = fib(n - 1, diepte + 1) + fib(n - 2, diepte + 1)
        print(' ' * diepte, '<', result)
        return result

    for i in range(1, 8):
        diepte = 0
        print('fib van', i, 'is', fib(i, diepte))
        print()

#=============================================================#
# Opgave 9.3

if 0:
    markeer( 'Opgave 9.3 X' )

    def fib(n): # niet recursief
        a = 0
        b = 1
        for i in range(n):
            a, b = a + b, a
        return a

    for i in range(1, 8):
        print('fib van', i, 'is', fib(i))

    # niet recursief is sneller, gebruikt functie aanroepen
    #   geen parameters doorgeven, geen stack gebruik
    # veel sneller als recursieve telkens 2 aanroepen doet
    # zie hierboven antwoord bij opgave 9.1
    #   langzame en snelle recursieve varianten

#=============================================================#
# Opgave 9.4

if 0:
    markeer( 'Opgave 9.4 X' )

    # grootste gemene deler
    # greates common divisor

    def gcd(left, right):
        if left < right:
            smaller = left
            larger = right
        else:
            smaller = right
            larger = left
        div, mod = divmod(larger, smaller)
        if mod == 0:
            return smaller
        return gcd(smaller, mod)


    print(gcd(14, 21))
    print(gcd(36, 48))

#=============================================================#
# Opgave 9.5

if 0:
    markeer( 'Opgave 9.5 A X' )

    # exercise0905.py
    def vraag_input( prompt ):
        waarde = input( prompt )
        for letter in waarde:
            if letter < 'a' or letter > 'z':
                print( letter , "is niet toegestaan!")
                waarde = vraag_input( prompt ) # DOE DIT NIET!
        return waarde

    s = vraag_input( "Geef een string van kleine letters: " )
    print( "Je gaf in:", s )

    # fout als bij eerste input meer letters die niet zijn toegestaan
    voorbeeld = """
    Geef een string van kleine letters: 12
    1 is niet toegestaan!
    Geef een string van kleine letters: a
    2 is niet toegestaan!
    Geef een string van kleine letters: a
    Je gaf in: a
    """

if 0:
    markeer( 'Opgave 9.5 B X' )

    def vraag_input( prompt ):
        while True:
            waarde = input( prompt )
            for letter in waarde:
                if letter < 'a' or letter > 'z':
                    print( letter, "is niet toegestaan!")
                    break
            else:
                break
        return waarde

    s = vraag_input( "Geef een string van kleine letters: " )
    print( "Je gaf in:", s )

#=============================================================#
# Opgave 9.6

if 0:
    markeer( 'Opgave 9.6 A X' )

    def hanoi(n, x, y, z, i):
        if n > 0:
            i = hanoi(n - 1, x, z, y, i)
            print('Schijf', n, 'van', x, 'naar', y)
            i = 1 + hanoi(n - 1, z, y, x, i)
        return i

    import sys
    try:
        N = int(sys.argv[1])
    except:
        # N = 2
        # N = 3
        N = 4
        # N = 5
        # N = 10

    print("Torens van Hanoi, N is", N)
    print(hanoi(N, 'A', 'C', 'B', 0), "stappen")

if 0:
    markeer( 'Opgave 9.6 B X' )

    def hanoi(n, van, naar, tussen):
        if n <= 0:
            return 0
        aantal = hanoi(n - 1, van, tussen, naar)
        print('Schijf', n, 'van', van, 'naar', naar)
        return aantal + 1 + hanoi(n - 1, tussen, naar, van)

    # N = 2
    # N = 3
    N = 4
    # N = 5
    # N = 10

    print("Torens van Hanoi, N is", N)
    print(hanoi(N, 'A', 'C', 'B'), "stappen")

if 0:
    markeer( 'Opgave 9.6 C X' )

    # niet recursieve versie
    #   gebruikt list als stack
    #   afgeleid van recursieve versie hierboven
    def hanoi(n, van, naar, tussen):
        aantal = 0
        stack = [ ]
        mode = 's' # stack of print
        while True:
            if mode != 's':
                print('Schijf', n, 'van', van, 'naar', naar)
                aantal += 1
            elif n > 0:           # achterstevoren op stack
                stack.append( ('s', n - 1, tussen, naar, van) )
                stack.append( ('p', n, van, naar, tussen) )
                stack.append( ('s', n - 1, van, tussen, naar) )
            if not stack:
                break
            mode, n, van, naar, tussen = stack.pop()
        return aantal

    N = 4

    print("Torens van Hanoi, N is", N)
    print(hanoi(N, 'A', 'C', 'B'), "stappen")

#=============================================================#
# Opgave 10.1

if 0:
    markeer( 'Opgave 10.1 X' )

    tekst = 'Een lange string met enkele klinkers erin'
    # tekst = "Monty Python's Flying Circus"
    tekst = tekst.lower()
    for klinker in 'aeiou':
        aantal = 0
        for letter in tekst:
            if letter == klinker:
                aantal += 1
        print( 'klinker {} aantal {}'.format( klinker, aantal ) )
        
#=============================================================#
# Opgave 10.2

if 0:
    markeer( 'Opgave 10.2 X' )

    # exercise1002.py
    tekst = """En ze stu[re]n [i]ngekleurde prentbriefkaarten van
plekken waarvan ze zich niet reali[s]eren dat ze er nooit
geweest zijn [a]an 'Iedereen op nummer 22, weer is prachti[g],
onz[e] kamer is aa[n]gekruisd. Missen jullie. E[t]en[ ]i[s]
vettig, maar we hebben een geweldig leuk restaurantje gevonden
in de achterstraatjes waar ze Heine[ke]n hebben en kaas en
uien chips en iemand die "Een beetje verliefd" speel[t] op een
a[c]cordeon' en je zit vier dagen vast op Schip[h]ol voor je
vijfdaagse vliegvakantie met niks anders te eten dan
uitgedroogde voorverpakte boterhammen..."""

    erin = False
    for ch in tekst:
        if ch == '[':
            erin = True
        elif ch == ']':
            erin = False
        elif erin:
            print( ch, end = '' )
    print()
            
#=============================================================#
# Opgave 10.3

if 0:
    markeer( 'Opgave 10.3 X' )

    # typefoutje in Opgave tekst
    # in boek versie 1.1.1
    # .. die of 13 letters afstand .. =>
    # .. die op 13 letters afstand ..

    for i in range(26):
        print( chr( ord('A') + i), end = '' )
    print()
    for i in range(26):
        print( chr( ord('A') + (i + 13) % 26), end = '' )
    print()

#=============================================================#
# Opgave 10.4

if 0:
    markeer( 'Opgave 10.4 X' )

    # exercise1004.py
    tekst = """Kapper Knap, de knappe kapper, knipt en kapt heel
knap, maar de knecht van kapper Knap, de knappe kapper,
knipt en kapt nog knapper dan kapper Knap, de knappe kapper."""
# knipt en kapt nog knãpper dan kapper Knãp, de knappe kapper."""

    zoek = 'knap'
    tekst = tekst.lower()
    txt = ''
    for ch in tekst:
        if ch.isalpha():  # niet in boek, ook met speciale tekens
            txt += ch
        else:
            txt += ' '

    # print( txt )

    aantal = 0
    for woord in txt.split():
        # print( woord )
        if woord == zoek:
            aantal += 1
    print( zoek, 'aantal', aantal)
    
#=============================================================#
# Opgave 10.5

if 0:
    markeer( 'Opgave 10.5 X A' )

    tekst = "Hello, world!"
    txt = ''
    for i in range(32, 128):
        ch = chr(i)
        for c in tekst:
            if c == ch:
                txt += ch
    print( txt )

if 0:
    # gebruikt sorted en join uit hoofdstuk 12
    markeer( 'Opgave 10.5 X B' )

    tekst = "Hello, world!"
    print( ''.join( sorted( tekst ) ) )


#=============================================================#
# Opgave 10.6

if 0:
    markeer( 'Opgave 10.6 X' )

    # exercise1006.py
    zin = "en zo gebeurde het dat dat onze toevallige ontmoeting \
met de EErwaarde aRTHUR BElling een ommekeer betekende in ons \
leven, en vanaf dat moment gingen we iedere zondag naar \
de kerk van Sint sIMPEL bij Roombroodje MEt Jam."

    # zin += " nog een zin erbij."
    # zin += " en nog een"
    print( zin )
    print()

    txt = ''
    woord = ''
    vorige = ''
    beginzin = True
    for i in range(len(zin) + 1):
        if i == len(zin):
            ch = '' # gebruikt woord als nog gevuld bij einde
        else:
            ch = zin[i]
        if ch.isalpha():
            woord += ch
        else:
            # print( woord )
            if len(woord):
                # (1)
                if (len(woord) >= 3 and woord[0].isupper()
                    and woord[1].isupper() and woord[2].islower()):
                    woord = woord[0] + woord[1].lower() + woord[2:]
                # (4)
                if woord[0].islower() and woord[1:].isupper():
                    woord = woord[0].upper() + woord[1:].lower()
                # (5)
                if (woord[0].islower() and  # werkt ook bij zondAG
                    woord.lower() in ( 'maandag', 'dinsdag', 'woensdag',
                      'donderdag', 'vrijdag', 'zaterdag', 'zondag' )):
                    woord = woord[0].upper() + woord[1:]
                # (3)
                if beginzin:
                    woord = woord[0].upper() + woord[1:]
                # (2)
                if woord == vorige and ch == ' ':
                    ch = ''
                else:
                    txt += woord
                    beginzin = False
            vorige = woord
            if ch == '.':
                beginzin = True
            woord = ''
            txt += ch
    print( txt )

#=============================================================#
# Opgave 11.1

if 0:
    markeer( 'Opgave 11.1 X' )

    def telOp(complex1, complex2):
        a, b = complex1  # a + bi
        c, d = complex2  # c + di
        return a + c, b + d # (a+b) + (c+d)*i

    c1 = 3, 2   # 3 + 2i
    c2 = 2, -5
    c3 = 4, 3
    c4 = 0, 0

    print(c1, c2, c3, telOp(telOp(c1, c2), c3))

    print(c1, c1, telOp(c1, c1))

    print(c1, c2, telOp(c1, c2))
    print(c2, c1, telOp(c2, c1))

    print(c1, c3, telOp(c1, c3))
    print(c3, c1, telOp(c3, c1))

    print(c1, c4, telOp(c1, c4))
    print(c4, c1, telOp(c4, c1))

    print(c2, c2, telOp(c2, c2))

    print(c2, c3, telOp(c2, c3))
    print(c3, c2, telOp(c3, c2))

    print(c2, c4, telOp(c2, c4))
    print(c4, c2, telOp(c4, c2))

    print(c3, c3, telOp(c3, c3))

    print(c3, c4, telOp(c3, c4))
    print(c4, c3, telOp(c4, c3))

    print(c4, c4, telOp(c4, c4))

#=============================================================#
# Opgave 11.2

if 0:
    markeer( 'Opgave 11.2 X' )

    def Vermenigv(complex1, complex2):
        a, b = complex1  # a + bi
        c, d = complex2  # c + di
        #      (a*c - b*d) + (a*d + b*c)i
        return a * c - b * d, a * d + b * c

    c1 = 3, 2   # 3 + 2i
    # c1 = 3, 2.5   # float mag ook
    c2 = 2, -5
    c3 = 4, 3
    c4 = 0, 0

    print(c1, c2, c3, Vermenigv(Vermenigv(c1, c2), c3))

    print(c1, c1, Vermenigv(c1, c1))

    print(c1, c2, Vermenigv(c1, c2))
    print(c2, c1, Vermenigv(c2, c1))

    print(c1, c3, Vermenigv(c1, c3))
    print(c3, c1, Vermenigv(c3, c1))

    print(c1, c4, Vermenigv(c1, c4))
    print(c4, c1, Vermenigv(c4, c1))

    print(c2, c2, Vermenigv(c2, c2))

    print(c2, c3, Vermenigv(c2, c3))
    print(c3, c2, Vermenigv(c3, c2))

    print(c2, c4, Vermenigv(c2, c4))
    print(c4, c2, Vermenigv(c4, c2))

    print(c3, c3, Vermenigv(c3, c3))

    print(c3, c4, Vermenigv(c3, c4))
    print(c4, c3, Vermenigv(c4, c3))

    print(c4, c4, Vermenigv(c4, c4))

#=============================================================#
# Opgave 11.3

if 0:
    markeer( 'Opgave 11.3 A X' )

    # exercise1103.py
    inttuple = ( 1, 2, ( 3, 4 ), 5, ( ( 6, 7, 8, ( 9, 10 ), 11 ), 12,
    13 ), ( ( 14, 15, 16 ), ( 17, 18, 19, 20 ) ) )

    def printTupleRecursief(tpl):
        for elm in tpl:
            if isinstance(elm, tuple):
                printTupleRecursief(elm)
            else:
                print(elm, end=' ')

    printTupleRecursief(inttuple)
    print()

if 0:
    markeer( 'Opgave 11.3 B X' )

    inttuple = ( 1, 2, ( 3, 4 ), 5, ( ( 6, 7, 8, ( 9, 10 ), 11 ), 12,
    13 ), ( ( 14, 15, 16 ), ( 17, 18, 19, 20 ) ) )

    def printTupleIteratief(tpl):
        # verwijder bovenste nested tuples
        genest = True
        while genest:
            genest = False
            cp = ()
            for elm in tpl:
                if isinstance(elm, tuple):
                    genest = True
                    for subelm in elm:
                        cp += (subelm,)
                else:
                    cp += (elm,)
            tpl = cp
        for elm in tpl:
            print(elm, end = ' ')

    printTupleIteratief(inttuple)
    print()

#=============================================================#
# Opgave 12.1

if 0:
    markeer( 'Opgave 12.1 X' )

    # exercise1201.py
    antwoord = [ "Dat is zeker", "Het is zeker zo", "Zonder twijfel",
"Ja, zeker", "Je kunt erop vertrouwen", "Zoals ik het zie, ja",
"Waarschijnlijk", "Ziet er goed uit", "Ja", "Lijkt van wel",
"Vaag, probeer het nog eens", "Vraag later nog eens", "Kan ik \
beter niet zeggen", "Kan ik nu niet voorspellen", "Concentreer \
je en vraag nog eens", "Reken er maar niet op", "Ik zeg van \
niet", "Mijn bronnen zeggen van niet", "Lijkt er niet op",
"Zeer twijfelachtig" ]

    from random import randint

    def main():
        while True:
            vraag = input('Stel een willekeurige vraag, leeg voor einde: ')
            if vraag == '':
                break
            print(antwoord[randint(0, len(antwoord) - 1)])

    main()

#=============================================================#
# Opgave 12.2

if 0:
    markeer( 'Opgave 12.2 X' )

    from random import randint, shuffle

    kleuren = ("Harten", "Schoppen", "Klaveren", "Ruiten")
    waarden = (2, 3, 4, 5, 6, 7, 8, 9, 10, "Boer", "Vrouw", "Heer", "Aas")

    def printStok(titel, stok):
        print(titel)
        for idx, (kleur, waarde) in enumerate(stok):
            # print(kleur, waarde, end=' ')
            print(kleur + '-' + str(waarde), end=' ')
            if idx % 4 == 3:
                print()
        print()
        
    def schud(inlst):
        inlst = inlst[:] # maakt kopie, gebruikelijk, goed om te weten
        outlst = [ ]
        while inlst:
            outlst.append(inlst.pop(randint(0, len(inlst) - 1)))
        return outlst

    def schud2(inlst):
        inlst = inlst[:]
        return [ inlst.pop(randint(0, len(inlst) - 1))
                 for x in range(len(inlst)) ]

    def schud3(inlst):
        inlst = inlst.copy()  # copy iets beter
        shuffle(inlst)        # shuffle niet in boek
        return inlst

    def main():
        stok = [ ]
        for kleur in kleuren:
            for waarde in waarden:
                stok.append((kleur, waarde))
        printStok('stok', stok)
        # stok = [ (kleur, waarde) for kleur in kleuren for waarde in waarden ]
        # printStok('comprehension', stok)
        rndstok = schud(stok)
        printStok('geschud', rndstok)
        rndstok2 = schud2(stok)
        printStok('geschud2', rndstok2)
        rndstok3 = schud3(stok)
        printStok('geschud3', rndstok3)

    main()

#=============================================================#
# Opgave 12.3

if 0:
    markeer( 'Opgave 12.3 X' )

    def main():
        queue = [ ]
        while True:
            inq = input('Geef item voor queue,'
                       ' haal uit queue met "?", leeg voor einde ')
            if inq == '':
                break
            if inq == '?':
                if len(queue):
                    print('Uit queue', queue.pop(0))
                else:
                    print('Queue is leeg')
            else:
                queue.append(inq)

    main()

#=============================================================#
# Opgave 12.4

if 0:
    markeer( 'Opgave 12.4 X' )

    # lijkt op 12.4.11 Oefening 3e opgave

    def main():
        txt = "Monty Python's Flying Circus"
        counts = [ [0, chr(i + ord('a'))] for i in range(26) ]
        for ch in txt.lower():
            if ch >= 'a' and ch <= 'z':
            # if 'a' <= ch <= 'z':
                counts[ord(ch) - ord('a')][0] -= 1
                # counts[ord(ch) - ord('a')][0] += 1

        counts.sort()
        # counts.sort(reverse=True)  # veel voorkomend vooraan
        # counts.sort(key=lambda x: -x[0]) # beter voor gelijke

        # def sortkey(item):
        #     return -ord(item[1]), item[0]
        # counts.sort(reverse=True, key=sortkey)  # veel voorkomend vooraan

        print('Gesorteerde letter aantallen in:', txt)
        for i in range(len(counts)):
            count = counts[i][0]
            if count:
               print(counts[i][1], -count)
               # print(counts[i][1], count)
        # for i, count in enumerate(counts):
        #     if count[0]:
        #         print(counts[i][1], -count[0])

    main()

#=============================================================#
# Opgave 12.5

if 0:
    markeer( 'Opgave 12.5 X' )

    def main():
        hoogste = 100
        lst = [ x for x in range(1, hoogste + 1) ]
        # print(lst)
        lst[0] = 0
        for x in lst:
            if x:
                factor = 2
                while factor * x <= len(lst):
                    lst[factor * x - 1] = 0
                    factor += 1
        # print(lst)
        lst = [x for x in lst if x ]
        # print(lst)
        for x in lst:
            print(x, end=' ')

    main()

#=============================================================#
# Opgave 12.6

if 0:
    markeer( 'Opgave 12.6 X' )

    from pcinput import getInteger

    speler1 = 'x'
    speler2 = 'o'
    leeg = '.'

    def opponent(speler = speler2):
        return speler1 if speler == speler2 else speler2
        # if expressie werkt gelijk aan statement
        if speler == speler2:
            return speler1
        else:
            return speler2

    def toonBord(bord):
        for rij in bord:
            # print(rij)
            print(''.join(rij))

    def winnaarBord(bord):
        for rij in range(3):
            # if 0 < a and a < 10:
            # if 0 < a < 10:
            # if leeg != bord[rij][0] == bord[rij][1] == bord[rij][2]:
            if (leeg != bord[rij][0] and bord[rij][0] == bord[rij][1]
              and bord[rij][1] == bord[rij][2]):
                return bord[rij][0]
        for kolom in range(3):
            if leeg != bord[0][kolom] == bord[1][kolom] == bord[2][kolom]:
                return bord[0][kolom]
        if leeg != bord[0][0] == bord[1][1] == bord[2][2]:
            return bord[0][0]
        if leeg != bord[2][0] == bord[1][1] == bord[0][2]:
            return bord[2][0]
        return leeg

    def volBord(bord):
        for rij in bord:
            for cel in rij:
                if cel == leeg:
                    return False
        return True

    def main():
        speler = opponent()
        # bord = [ [ leeg ] * 3 ] * 3 # niet goed, alias
        bord = [ [ leeg ] * 3 for x in range(3) ]
        toonBord(bord)
        while True:
            print('Speler', speler)
            rij = getInteger('Rij 1, 2 of 3:')
            kolom = getInteger('Kolom 1, 2 of 3:')
            if rij < 1 or rij > 3 or kolom < 1 or kolom > 3:
                print('Ongeldige rij of kolom')
                continue
            if bord[rij - 1][kolom - 1] != leeg:
                print('Vak rij', rij, 'kolom', kolom, 'al bezet')
                continue
            bord[rij - 1][kolom - 1] = speler
            toonBord(bord)
            winnaar = winnaarBord(bord)
            if winnaar != leeg:
                print('Speler', winnaar, 'wint, gefeliciteerd!')
                break
            if volBord(bord):
                print('Gelijkspel')
                break
            speler = opponent(speler)
    main()

#=============================================================#
# Opgave 12.7

if 0:
    markeer( 'Opgave 12.7 X' )

    from random import randint

    schip = '+'
    leeg = '.'

    def toonBord(debug, bord):
        if debug:
            for rij in bord:
                print(''.join(rij))

    def zetSchip(bord):
        maxkolom = len(bord[0]) - 1
        maxrij = len(bord) - 1
        while True:
            kolom = randint(0, maxkolom)
            rij = randint(0, maxrij)
            if ( bord[rij][kolom] == leeg and
              ( rij == 0 or bord[rij - 1][kolom] == leeg ) and
              ( rij == maxrij or bord[rij + 1][kolom] == leeg) and
              ( kolom == 0 or bord[rij][kolom - 1] == leeg ) and
              ( kolom == maxkolom or bord[rij][kolom + 1] == leeg)):
                bord[rij][kolom] = schip
                return

    def main():
        # bord = [ [ leeg ] * 4 ] * 3 # niet goed, alias
        bord = [ [ leeg ] * 4 for x in range(3) ]
        schepen = 3
        geraakt = 0
        debug = False # True:
        if False: # True:
            for i in range(schepen):
                bord[i][i] = schip
        else:
            for i in range(schepen):
                zetSchip(bord)
        schoten = 0
        toonBord(debug, bord)
        while True:
            print('Geef vak met kolom A, B, C, of D en rij 1, 2 of 3')
            print('Zoals D3 of A2')
            vak = input('Vak:').lower()
            if (len(vak) != 2 or vak[0] < 'a' or vak[0] > 'd' or
                                 vak[1] < '1' or vak[1] > '3'):
                print('Ongeldig vak')
                continue
            kolom = ord(vak[0]) - ord('a')
            rij = ord(vak[1]) - ord('1')
            schoten += 1
            if bord[rij][kolom] == leeg:
                print('Mis!')
            else:
                print('Raak!')
                geraakt += 1
                if geraakt == schepen:
                    print('De', schepen, 'schepen zijn geraakt met', schoten, 'schoten')
                    break
                bord[rij][kolom] = leeg
            toonBord(debug, bord)

    main()

#=============================================================#
# Opgave 12.8

if 0:
    markeer( 'Opgave 12.8 A X' )

    # zie ook Opgave 23.5
    # zie ook https://en.wikipedia.org/wiki/Subset_sum_problem

    # recursief

    def subset(lst, incl, pos, som, count):
        # print(lst, incl, pos, som, count)
        if pos == len(lst):
            if som == 0 and True in incl: # exclusief lege set
            # if som == 0: # inclusief lege set
                print('Gevonden', end = ' ')
                for val, inc in zip(lst, incl):
                    if inc:
                        print(val, end = ' ')
                print()
                count += 1
            return count
        # met lst[pos]
        incl[pos] = True
        count = subset(lst, incl, pos + 1, som + lst[pos], count)
        incl[pos] = False
        # zonder lst[pos]
        return subset(lst, incl, pos + 1, som, count)
        
    def subsetsom(lst):
        print('Subset som van', lst)
        count = subset(lst, [ False ] * len(lst), 0, 0, 0)
        if count == 0:
            print('Geen gevonden')
        else:
            print('Aantal gevonden', count)
        print()

    def main():
        subsetsom( [ 3, 8, -1, 4, -5, 6 ] ) # als 12.8 F
        subsetsom( [ 3, -3, 4, -4 ] )

        subsetsom( [1, 4, -3, -5, 7] )
        subsetsom( [1, 4, -3, 7] )

        subsetsom( [-7, -3, -2, 9000, 5, 8] )

        # zie ook Opgave 23.5 X
        # lege lijst heeft
        #   bij bovenstaande code geen subset
        #     bij if waarbij incl een True bevat
        # lege set is ook een subset met sum 0
        #   kan gezien worden als geldig antwoord
        # sum( set() ) = sum( {} ) = 0
       
        subsetsom( [ ] ) 

        # omdat een list wordt gebruikt
        #   zijn meerdere met dezelfde waarde mogelijk
        #   is dan geen verzameling zoals nodig volgens de opgave
        # bij Python set geen dubbele mogelijk
        #   zie hoofdstuk 14 Sets
        subsetsom( [1, 1, -2] )

    main()

if 0:
    markeer( 'Opgave 12.8 B X' )

    # niet recursief

    def subsetsom( lst ):
        # probeer alle mogelijkheden
        print( lst )
        ln = len( lst )
        count = 0
        for val in range( 1, 2 ** ln ): # exclusief lege
        # for val in range( 2 ** ln ): # inclusief lege
            sum = 0
            for idx in range( ln ):
                if (1 << idx) & val: # bit set
                    sum += lst[idx]
            if sum == 0:
                print('Gevonden', end = ' ')
                for idx in range( ln ):
                    if (1 << idx) & val: # bit set
                        print(lst[idx], end = ' ')
                print()
                count += 1
        if count == 0:
            print('Geen gevonden')
        else:
            print('Aantal gevonden', count)
        print()

    def main():
        subsetsom( [ 3, 8, -1, 4, -5, 6 ] ) # als 12.8 F
        subsetsom( [ 3, -3, 4, -4 ] )

        subsetsom( [1, 4, -3, -5, 7] )
        subsetsom( [1, 4, -3, 7] )
        subsetsom( [ ] ) 
        subsetsom( [-7, -3, -2, 9000, 5, 8] )
        subsetsom( [1, 1, -2] )

    main()

#=============================================================#
# Opgave 13.1

if 0:
    markeer( 'Opgave 13.1 X' )

    # exercise1301.py
    tekst = """Kapper Knap, de knappe kapper, knipt en kapt heel
    knap, maar de knecht van kapper Knap, de knappe kapper, knipt
    en kapt nog knapper dan kapper Knap, de knappe kapper."""

    tekst = tekst.lower()

    tekstsimpel = ''
    for ch in tekst:
        if 'a' <= ch <= 'z':
            tekstsimpel += ch
        else:
            tekstsimpel += ' '
    woorden = tekstsimpel.split()
    aantallen = { }
    for woord in woorden:
        if woord in aantallen:
            aantallen[woord] += 1
        else:
            aantallen[woord] = 1
    for woord in sorted(aantallen):
        print(woord, aantallen[woord])

#=============================================================#
# Opgave 13.2

if 0:
    markeer( 'Opgave 13.2 X' )

    # exercise1302.py
    films = ["Monty Python and the Holy Grail",
             "Monty Python's Life of Brian",
             "Monty Python's Meaning of Life",
             "And Now For Something Completely Different"]

    grail_scores = [ 9, 10, 9.5, 8.5, 3, 7.5,8 ]
    brian_scores = [ 10, 10, 0, 9, 1, 8, 7.5, 8, 6, 9 ]
    life_scores = [ 7, 6, 5 ]
    different_scores = [ 6, 5, 6, 6 ]

    scores = grail_scores, brian_scores, life_scores, different_scores

    for film, scors in zip(films, scores):
        avg = sum(scors) / len(scors)
        print(film, round(avg, 1))
    print()

    flms = { }
    for film, scors in zip(films, scores):
        flms[film] = scors

    for film, scors in flms.items():
        avg = sum(scors) / len(scors)
        print(film, round(avg, 1))
    print()

    if 0:
        # gesorteerd op naam
        for film, scors in sorted(flms.items()):
            avg = sum(scors) / len(scors)
            print(film, round(avg, 1))
        print()

    if 0:
        # gesorteerd op avg
        def keyfunc(item): # moet hier ook avg gebruiken
            return sum(item[1]) / len(item[1])
        for film, scors in sorted(flms.items(), key=keyfunc):
        # for film, scors in sorted(flms.items(), key=keyfunc, reverse=True):
            avg = sum(scors) / len(scors)
            print(film, round(avg, 1))
        print()

#=============================================================#
# Opgave 13.3

if 0:
    markeer( 'Opgave 13.3 X' )

    # schrijver schrijft 1 of meer lijst boeken
    # elk boek heeft locatienummer 123, 124, ...
    # elk boek heeft titel 'a', 'b', 'c', ...
    # elk boek heeft schrijver 'Anna', 'Bert', 'Cees', ...

    schrijvertitels = { 'Anna' : ('a', 'b', 'c'),
                        'Bert' : ('k', 'l', 'm'),
                        'Cees' : ('x', 'y', 'z') }
    boeklocaties = { 'a' : 123, 'b' : 124, 'c' : 125,
                     'k' : 131, 'l' : 134, 'm' : 137,
                     'x' : 131, 'y' : 123, 'z' : 139 }

    def waar_staat_boek(titel):
        return boeklocaties[titel]

    def schrijver_titels(schrijver):
        return schrijvertitels[schrijver]

    def schrijvers():
        return schrijvertitels.keys()

    for schrijver in schrijvers():
        print( 'Schrijver', schrijver )
        for titel in schrijver_titels(schrijver):
            print( ' titel', titel,
                   'locatie', boeklocaties[titel] )

#=============================================================#
# Opgave 14.1

if 0:
    markeer( 'Opgave 14.1 X' )

    # exercise1401.py
    alles = { "Socrates", "Plato", "Eratosthenes", "Zeus", "Hera",
              "Athene", "Acropolis", "Kat", "Hond" }
    mensen = { "Socrates", "Plato", "Eratosthenes" }
    sterfelijken = { "Socrates", "Plato", "Eratosthenes", "Kat",
        "Hond" }

    print('(a) zijn alle mensen sterfelijk?')
    print('mensen subset sterfelijken', mensen.issubset(sterfelijken))
    # print('sterfelijken subset mensen', sterfelijken.issubset(mensen))
    # print('mensen < sterfelijken', mensen < sterfelijken)
    # print('mensen <= sterfelijken', mensen <= sterfelijken)
    # print('mensen == sterfelijken', mensen == sterfelijken)

    print('(b) is Socrates een mens?')
    print('Socrates in mensen', 'Socrates' in mensen)

    print('(c) is Socrates sterfelijk?')
    print('Socrates in sterfelijken', 'Socrates' in sterfelijken)

    print('(d) zijn er sterfelijke dingen die geen mens zijn?')
    print('sterfelijken zonder mensen', sterfelijken - mensen)

    print('(e) zijn er dingen die niet sterfelijk zijn?')
    print('alles zonder sterfelijken', alles - sterfelijken)

#=============================================================#
# Opgave 14.2

if 0:
    markeer( 'Opgave 14.2 X' )

    # end = 100
    # end = 250 # kleinste 3 * 7 * 11 = 231
    end = 1000
    # end = 1001
    setAll = set(range(1, end))

    # met set comprehensions
    set3 = { i for i in setAll if i % 3 == 0 }
    # set3 = { i for i in range(1, end) if i % 3 == 0 }
    if 1:
        print('Deelbaar door 3', set3)
        # print('Deelbaar door 3', sorted(set3))

    set7 = { i for i in setAll if i % 7 == 0 }
    if 0:
        print('Deelbaar door 7', set7)
        # print('Deelbaar door 7', sorted(set7))

    set11 = { i for i in setAll if i % 11 == 0 }
    if 0:
        print('Deelbaar door 11', set11)
        # print('Deelbaar door 11', sorted(set11))

    if 0:
        set3_7_11 = set3 & set7 & set11
        print('Deelbaar door 3, 7 en 11', set3_7_11)
        # print('Deelbaar door 3, 7 en 11', sorted(set3_7_11))

    if 0:
        set3_7not11 = (set3 & set7) - set11
        print('Deelbaar door 3 en 7 maar niet door 11', set3_7not11)
        # print('Deelbaar door 3 en 7 maar niet door 11', sorted(set3_7not11))

    if 0:
        setnot3_7_11A = setAll - set3 - set7 - set11
        # print('Deelbaar niet door 3, niet door 7 en niet door 11',
        #       sorted(setnot3_7_11A))
        if 0:
            setnot3_7_11B = setAll - (set3 | set7 | set11)
            assert setnot3_7_11A == setnot3_7_11B
            # print('Deelbaar niet door 3, niet door 7 en niet door 11',
            #   setnot3_7_11A)

        # extra controles
        # setnot3_7_11A/B doorsnede met set3 geeft lege set
        #   zo ook met set7 en set11
        if 0:
            assert setnot3_7_11A & set3
            assert setnot3_7_11A & set7
            assert setnot3_7_11A & set11
        if 0:
            assert setnot3_7_11B & set3
            assert setnot3_7_11B & set7
            assert setnot3_7_11B & set11

#=============================================================#
# Opgave 15.1

if 0:
    markeer( 'Opgave 15.1 X' )

    import os  # dan ook os.path bruikbaar

    curdir = os.getcwd()
    for name in os.listdir():
        print(curdir + '\\' + name) # \ alleen op windows
        # print(curdir + os.path.sep + name) # portable
        # print(os.path.join(curdir, name))  # beter

        if 0:
            full = os.path.join(curdir, name)
            if os.path.isfile(full):
                print('file')
            elif os.path.isdir(full):
                print('directory')
            else:
                print('special')

#=============================================================#
# Opgave 16.1

if 0:
    markeer( 'Opgave 16.1 X' )

    with open('pc_woodchuck.txt') as fp:
        txt = fp.read()
    # print(txt)

    woorden = [ ]
    inwoord = False
    for ch in txt:
        if not ch.isalpha():
            inwoord = False
        # elif inwoord and len(woorden) != 0:
        elif inwoord:  # zodra inwoord True dan ook woorden gevuld
            woorden[-1] += ch
        else:
            inwoord = True
            woorden.append(ch)
    # print(woorden)

    aantallen = { }
    for woord in woorden:
        woord = woord.lower()
        if woord in aantallen:
            aantallen[woord] += 1
        else:
            aantallen[woord] = 1
    for woord in sorted(aantallen):
        print(woord, aantallen[woord])
    # for woord, aantal in sorted(aantallen.items()):
    #     print(woord, aantal)

#=============================================================#
# Opgave 16.2

if 0:
    markeer( 'Opgave 16.2 A X' )

    # minder goed dan 16.2 B X

    woorden = [ ] # gebruikt veel geheugen bij groot bestand

    with open('pc_woodchuck.txt') as fp:
        for line in fp:
            # print(line, end='')

            inwoord = False
            for ch in line:
                if not ch.isalpha():
                    inwoord = False
                elif inwoord and len(woorden) > 0:
                # elif inwoord:  # zodra inwoord True dan ook woorden gevuld
                    woorden[-1] += ch
                else:
                    inwoord = True
                    woorden.append(ch)
    # print()
    # print(woorden)

    aantallen = { }
    for woord in woorden:
        woord = woord.lower()
        if woord in aantallen:
            aantallen[woord] += 1
        else:
            aantallen[woord] = 1
    for woord in sorted(aantallen):
        print(woord, aantallen[woord])
    # for woord, aantal in sorted(aantallen.items()):
    #     print(woord, aantal)

if 0:
    markeer( 'Opgave 16.2 B X' )

    aantallen = { } # voor alle regels

    with open('pc_woodchuck.txt') as fp:
        for line in fp:
            # print(line, end='')

            woorden = [ ] # voor 1 regel
            inwoord = False
            for ch in line:
                if not ch.isalpha():
                    inwoord = False
                elif inwoord and len(woorden) > 0:
                # elif inwoord:  # zodra inwoord True dan ook woorden gevuld
                    woorden[-1] += ch
                else:
                    inwoord = True
                    woorden.append(ch)
            # print()
            # print(woorden)

            for woord in woorden:
                woord = woord.lower()
                if woord in aantallen:
                    aantallen[woord] += 1
                else:
                    aantallen[woord] = 1

    for woord in sorted(aantallen):
        print(woord, aantallen[woord])
    # for woord, aantal in sorted(aantallen.items()):
    #     print(woord, aantal)

#=============================================================#
# Opgave 16.3

if 0:
    markeer( 'Opgave 16.3 X' )

    gelezen = 0
    geschreven = 0

    with open('pc_woodchuck.tmp', 'w') as fpw:
        with open('pc_woodchuck.txt') as fp:
        # fp = open('pc_woodchuck.txt')
        # if True:
            # x = fp.read(20)
            # print('20 uit bestand', x)
            # fp.seek(0)  # zie 18.4 Positioneren van de pointer
            for i, line in enumerate(fp):
                print(i, line, end='')
                gelezen += len(line)
                line = ''.join(
                    [ch for ch in line if not ch.lower() in 'aeiou'])
                fpw.write(line)
                geschreven += len(line)
        # print()
        # fp.close()

    print('gelezen', gelezen, 'geschreven', geschreven)
    # lengtes kloppen niet met bestand groottes vanwege \r\n -> \n

    if 0:
        with open('pc_woodchuck.tmp') as fp:
            tmp = fp.read()
            tmplen = len(tmp)
            print(tmp)

        print('gelezen tmp', tmplen)

#=============================================================#
# Opgave 16.4

if 0:
    markeer( 'Opgave 16.4 X' )

    def mindrie(bestandsnaam):
        with open(bestandsnaam) as fp:
            txt = fp.read()
        woorden = [ ]
        inwoord = False
        for ch in txt:
            if not ch.isalpha():
                inwoord = False
            elif inwoord and len(woorden) > 0:
            # elif inwoord:
                woorden[-1] += ch
            else:
                inwoord = True
                woorden.append(ch)
        # print(woorden)
        drieletterwoorden = [ woord.lower()
                              for woord in woorden if len(woord) >= 3 ]
        # print(drieletterwoorden)

        return set(drieletterwoorden)

    print(mindrie('pc_woodchuck.txt') &
          mindrie('pc_jabberwocky.txt') &
          mindrie('pc_rose.txt'))

#=============================================================#
# Opgave 16.5

if 0:
    markeer( 'Opgave 16.5 X' )

    def fracties(bestandsnaam):
        with open(bestandsnaam) as fp:
            txt = fp.read()
        aantallen = [ 0 ] * 26
        totaal = 0
        txt = txt.lower()  # sneller als buiten for loop
        for ch in txt:
            # ch = ch.lower()  # minder snel als binnen for loop
            if ch.isalpha():
                totaal += 1
                aantallen[ord(ch) - ord('a')] += 1
        fracties = [ f'{aantal / totaal:.5f}'
                     for aantal in aantallen ]
        # print(aantallen, fracties)
        return fracties

    frw = fracties('pc_woodchuck.txt')
    frj = fracties('pc_jabberwocky.txt')
    frp = fracties('pc_rose.txt')

    with open('willekeurig.csv', 'w') as fpo:
        for i in range(26):
            print('"' + chr(i + ord('a')) + '"',
                  frw[i], frj[i], frp[i], sep=',',
                  file=fpo)

    with open('willekeurig.csv') as fp:
        print(fp.read(), end='')

    # e niet hoogste voor pc_woodchuck.txt
    # want k is hoger
    # Spronck bedoelt hoogste over alle bestanden bij elkaar

    # volgorde in oplossing van Spronck anders
    # gebruikt ook list van totalen totaallist

#=============================================================#
# Opgave 17.1

if 0:
    markeer( 'Opgave 17.1 A X' )

    # exercise1701.py
    numlist = [ 100, 101, 0, "103", 104 ]
    i1 = int( input( "Geef een index: " ) )
    print( "100 /", numlist[i1], "=", 100 / numlist[i1] )

if 0:
    markeer( 'Opgave 17.1 B X' )

    try:
        numlist = [ 100, 101, 0, "103", 104 ]
        i1 = int( input( "Geef een index: " ) )
        print( "100 /", numlist[i1], "=", 100 / numlist[i1] )
    except IndexError:
        print( 'Onjuiste index' )
    except ValueError:
        print( 'Geen getal' )
    except ZeroDivisionError:
        print( 'Index 2 of -3 gebruikt' )
    except TypeError:
        print( 'Index 3 of -2 gebruikt' )

#=============================================================#
# Opgave 18.1

if 0:
    markeer( 'Opgave 18.1 X' )

    import os

    inf = 'pc_rose.txt'
    testf = 'test18.tmp'

    if not os.path.isfile(testf):
        with open(inf) as fp:
            txt = fp.read()

        with open(testf, 'w') as fp:
            fp.write(txt)

    with open(testf, 'r+b') as fp:
        while True:
            pos = fp.tell()
            byt = fp.read(1)
            if len(byt) == 0:
                break
            val = byt[0]
            if 1:
                if val < 128:
                    val += 128
                else:
                    val -= 128
            if 0:
                val = (val + 128) % 256
            byt = bytes( (val,) )
            fp.seek( pos )
            fp.write( byt )
            
    with open( testf, encoding='latin-1' ) as fp:
    # with open( testf) as fp:   # UnicodeDecodeError
    # with open( testf, errors='ignore' ) as fp: # utf-8
        print( fp.read() )

#=============================================================#
# Opgave 18.2

if 0:
    markeer( 'Opgave 18.2 X' )

    eta = 'etaoinshrdlcum '
    # eta = 'etanirodslgvhk '

    def compress(txt):
        halfs = [ ]
        for ch in txt:
            code = eta.find(ch)
            if code != -1:
                halfs.append(code + 1)
            else:
                code = ord(ch)
                # assert code <= 255
                if 1:
                    halfs.append(0)
                    halfs.append(code // 16)
                    halfs.append(code % 16)
                else:
                    halfs.extend((0, code // 16, code % 16))
                    # halfs.extend([0, code // 16, code % 16])

        if len(halfs) % 2 != 0:
            halfs.append(0)
        vals = [ ]
        for i in range(0, len(halfs), 2):
            vals.append(halfs[i] * 16 + halfs[i + 1])
        return bytes(vals)

    # if __name__ == '__main__':
    if 1:
        txt = 'Hello, world!'
        # txt = 'áéíóúàèìòù'
        # txt = '€'
        # txt = 'PP'
        cmpr = compress(txt)
        print(cmpr)
        with open('cmpr.bin', 'wb') as fp:
            fp.write(cmpr)

#=============================================================#
# Opgave 18.3

if 0:
    markeer( 'Opgave 18.3 X' )

    eta = 'etaoinshrdlcum '
    # eta = 'etanirodslgvhk '

    def decompress(byts):
        halfs = [ ]
        for val in byts:
            halfs.append(val // 16)
            halfs.append(val % 16)

        i = 0
        txt = ''
        while i < len(halfs):
            half = halfs[i]
            i += 1
            if half != 0:
                txt += eta[half - 1]
            else:
                if i + 1 < len(halfs):
                    txt += chr(halfs[i] * 16 + halfs[i + 1])
                i += 2
        return txt

    # if __name__ == '__main__':
    if 1:
        if 1:
            byts = b'\x04\x81\xbb@,\xf0wI\xba\x02\x10'
            # byts = b'\x05\x00P' # ongecodeerd 'PP'
            txt = decompress(byts)
            print(txt)
        else:    
            with open('cmpr.bin', 'rb') as fp:
                cmpr = fp.read()
                txt = decompress(cmpr)
                print(txt)

#=============================================================#
# Opgave 18.4

if 0:
    markeer( 'Opgave 18.4 X' )

    try:
        compress
    except NameError:
        print( 'if 1: bij opgave 18.2 hierboven nodig' )
    try:
        decompress
    except NameError:
        print( 'if 1: bij opgave 18.3 hierboven nodig' )

    import os

    def main():
        while True:
            naamin = input('Geef naam invoerbestand, leeg voor stop: ')
            if naamin == '':
                return
            if os.path.isfile(naamin):
                break
            print('Invoerbestand bestaat niet')
        while True:
            naamuit = input('Geef naam uitvoerbestand, leeg voor stop: ')
            if naamuit == '':
                return
            if not os.path.isfile(naamuit):
                break
            print('Uitvoerbestand bestaat al')
        while True:
            keuze = input('Letter (c)omprimeren of (d)ecomprimeren, leeg voor stop: ')
            if keuze == '':
                return
            keuze = keuze[0].lower()
            if keuze in 'cd':
                break
            print('Keuze moet beginnen met c of d')
        with open(naamin, 'rb') as fpr:
            byts = fpr.read()
        # latin-1 geldig voor alle byte waarden 0-255
        if keuze == 'c':
            result = compress(byts.decode('latin-1'))
        else:
            result = decompress(byts).encode('latin-1')
        with open(naamuit, 'wb') as fpw:
            fpw.write(result)

        print( result )

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 19.1

if 0:
    markeer( 'Opgave 19.1 X' )

    txt = "Monty Python's Flying Circus"
    mask = 0b_0010_1010

    print('Originele string:', txt)

    versleuteld = ''
    for ch in txt:
        versleuteld += chr(ord(ch) ^ mask)

    print('Versleutelde string:', versleuteld)

    ontsleuteld = ''
    for ch in versleuteld:
        ontsleuteld += chr(ord(ch) ^ mask)

    print('Ontsleutelde string:', ontsleuteld)

#=============================================================#
# Opgave 19.2

if 0:
    markeer( 'Opgave 19.2 X' )

    def setbool(intstore, boolval, getalindex):
        bit = 1 << getalindex
        if boolval:
            intstore |= bit
        else:
            intstore &= ~bit
        return intstore

    def getbool(intstore, getalindex):
        bit = 1 << getalindex
        return (intstore & bit) != 0

    def printbool(intstore):
        for getalindex in range(intstore.bit_length()):
        # for getalindex in range(intstore.bit_length() - 1, -1, -1):
            print(int(getbool(intstore, getalindex)), end=' ')
            # print(int(getbool(intstore, getalindex)), end='')
        print()

    intstore = 0
    for index in range(37):
        boolval = (index % 3 == 0)
        # boolval = index % 3 == 0
        # boolval = index != 0 and index % 3 == 0
        # boolval = index != 3 and index % 3 == 0
        intstore = setbool(intstore, boolval, index)
        # print('  ', end='')
        printbool(intstore)
        # print(bin( intstore))

#=============================================================#
# Opgave 20.1

if 0:
    markeer( 'Opgave 20.1 A X' )

    class Punt:
        def __init__( self, x=0.0, y=0.0 ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return "({}, {})".format( self.x, self.y )

    class Rechthoek:
        def __init__( self, punt, breedte, hoogte ):
            self.punt = Punt( punt.x, punt.y ) # kopie, geen alias
            if breedte < 0.0:
                raise ValueError('Ongeldige breedte')
            self.breedte = breedte
            if hoogte < 0.0:
                raise ValueError('Ongeldige hoogte')
            self.hoogte = hoogte
        def __repr__( self ):
            return "[{},w={},h={}]".format( self.punt, self.breedte,
                self.hoogte )
        def rechteronderhoek( self ):
            return Punt( self.punt.x + self.breedte,
                         self.punt.y + self.hoogte )
        def overlap( self, ander ):
            # l links van r rechts, mogelijke volgordes
            # sl--sr   al--ar  geen overlap
            # sl- al- -sr -ar  overlap al-sr
            # al- sl--sr  -ar  overlap sl-sr
            # sl-  al--ar -sr  overlap al-ar
            # al- sl- -ar -sr  overlap sl-ar
            # al--ar   sl--sr  geen overlap
            sl, sr = self.punt.x, self.punt.x + self.breedte
            al, ar = ander.punt.x, ander.punt.x + ander.breedte
            overlapl, overlapr = max( sl, al ), min( sr, ar )
            overlapbreedte = overlapr - overlapl
            # print( sl, sr, al, ar, overlapl, overlapr, overlapbreedte )
            # b boven, o onder, als l r
            sb, so = self.punt.y, self.punt.y + self.hoogte
            ab, ao = ander.punt.y, ander.punt.y + ander.hoogte
            overlapb, overlapo = max( sb, ab ), min( so, ao )
            overlaphoogte = overlapo - overlapb
            # print( sb, so, ab, ao, overlapb, overlapo, overlaphoogte )
            # als geen if dan exception
            if overlapbreedte < 0 or overlaphoogte < 0:
                return None
            return Rechthoek( Punt(overlapl, overlapb),
                              overlapbreedte, overlaphoogte )

    if __name__ == '__main__':
        try:
            Rechthoek( Punt( -1.0, 2.0), -0.4, 1.9 )
        except ValueError as exc:
            print( exc )

        r = Rechthoek( Punt( -1.0, 2.0), 0.4, 1.9 )
        print( r.rechteronderhoek() )

        r1 = Rechthoek( Punt( 0.0, 0.0), 4.0, 4.0 )
        print( r1.overlap(r1) )
        r2 = Rechthoek( Punt( -2.0, 0.0), 4.0, 4.0 )
        print( r1.overlap(r2) )
        r3 = Rechthoek( Punt( -4.0, 0.0), 4.0, 4.0 )
        print( r1.overlap(r3) )
        r4 = Rechthoek( Punt( -6.0, 0.0), 4.0, 4.0 )
        print( r1.overlap(r4) )
        r5 = Rechthoek( Punt( 3.0, 0.0), 2.0, 2.0 )
        print( r1.overlap(r5) )

if 0:
    markeer( 'Opgave 20.1 B X' )

    # Oplossing van Hans Klaver

    from copy import copy

    class Punt:
        
        def __init__ ( self, x=0, y=0 ):
            self.x = x
            self.y = y

        def __repr__ ( self ):
            return "({}, {})".format( self.x, self.y )
        

    class Rechthoek:
        
        def __init__ ( self, lb, breedte, hoogte ):
            '''argumenten: linkerbovenhoek (Punt), breedte en hoogte (int)'''
            # zorg voor positieve breedte en hoogte
            self.lb = copy(lb)
            self.breedte = abs(breedte)
            self.hoogte = abs(hoogte)
            self.ro = Punt(self.lb.x + self.breedte, self.lb.y + self.hoogte)

        def __repr__ ( self ):
            return "[{}, b = {}, h = {}]".format( self.lb, self.breedte, self.hoogte)

        def oppervlakte ( self ):
            return self.breedte * self.hoogte
        
        def omtrek ( self ):
            return 2 * (self.breedte + self.hoogte)

        def get_ro ( self ):
            # Retourneert het punt van de rechteronderhoek
            return Punt(self.ro.x, self.ro.y)

        def get_lb ( self ):
            # Retourneert het punt van de linkerbovenhoek,
            # nodig om de overlap met een tweede rechthoek te kunnen bepalen.
            return Punt(self.lb.x, self.lb.y)

        def overlap ( self, rect ):
            # algoritme voor overlap() uit:
            # https://www.geeksforgeeks.org/dsa/intersecting-rectangle-when-bottom-left-and-top-right-corners-of-two-rectangles-are-given/

            # Retourneert de Rechthoek gevormd door de overlap van self en rect
            A = self;  B = rect
            roOv = Punt();  lbOv = Punt()
            roA = A.get_ro();  roB = B.get_ro()
            lbA = A.get_lb();  lbB = B.get_lb()

            lbOv.x = max(lbA.x, lbB.x)
            lbOv.y = max(lbA.y, lbB.y)
            roOv.x = min(roA.x, roB.x)
            roOv.y = min(roA.y, roB.y)

            if lbOv.x > roOv.x or lbOv.y > roOv.y:
                return None                         # no overlap
            else:
                return Rechthoek(lbOv, roOv.x - lbOv.x, roOv.y - lbOv.y)

if 0:
    markeer( 'Opgave 20.1 C X' )

    # test oplossingen 20.1 hierboven met tkinter GUI
    # wijzig if 0: naar if 1: hierboven
    #   bij Opgave 20.1 F of 20.1 A X of 20.1 B X
    # de grafische voorbeelden laten zien
    #   dat de overlap methode van Opgave 20.1 F
    #   soms niet in orde is
    # oorspronkelijke code voor
    #   Python modus van Processing
    #     https://py.processing.org/
    #   aangepast voor tkinter
    try:
        Rechthoek
    except NameError:
        import sys
        sys.exit( 'if 1: bij Opgave 20.1 F of 20.1 A X of 20.1 B X nodig' )

    def tekenRH(rh):
        try:
            x = rh.punt.x  # Opgave 20.1 F en 20.1 A X gebruiken .punt
            y = rh.punt.y
        except AttributeError:
            x = rh.lb.x  # Opgave 20.1 B X gebruikt .lb
            y = rh.lb.y
        breedte = rh.breedte
        hoogte = rh.hoogte
        rect(x, y, breedte, hoogte)
            
    def setup():
        size(640, 650)
        background(grey)
        
    def draw():
        r1 = Rechthoek( Punt( 10, 10 ), 80, 50 )
        r2 = Rechthoek( Punt( 20, 30 ), 90, 20 )
        noFill()
        tekenRH(r1)
        tekenRH(r2)
        overl1 = r1.overlap(r2)
        fill(white)
        if overl1: tekenRH(overl1)

        r3 = Rechthoek( Punt( 150, 10 ), 80, 50 )
        r4 = Rechthoek( Punt( 130, 40 ), 40, 30 )
        noFill()
        tekenRH(r3)
        tekenRH(r4)
        overl2 = r3.overlap(r4)
        fill(white)
        if overl2: tekenRH(overl2)
            
        r5 = Rechthoek( Punt( 300, 10 ), 80, 50 )
        r6 = Rechthoek( Punt( 250, 40 ), 60, 15 )
        noFill()
        tekenRH(r5)
        tekenRH(r6)
        overl3 = r5.overlap(r6)
        fill(white)
        if overl3: tekenRH(overl3)
        
        r7 = Rechthoek( Punt( 400, 10 ), 80, 50 )
        r8 = Rechthoek( Punt( 420, 30 ), 30, 15 )
        noFill()
        tekenRH(r7)
        tekenRH(r8)
        overl4 = r7.overlap(r8)
        fill(white)
        if overl4: tekenRH(overl4)   
        
        r9 = Rechthoek( Punt( 500, 10 ), 80, 50 )
        r10 = Rechthoek( Punt( 510, 2 ), 40, 20 )
        noFill()
        tekenRH(r9)
        tekenRH(r10)
        overl5 = r9.overlap(r10)
        fill(white)
        if overl5: tekenRH(overl5)
        
        r11 = Rechthoek( Punt( 10, 100 ), 80, 50 )
        r12 = Rechthoek( Punt( 60, 90 ), 50, 20 )
        noFill()
        tekenRH(r11)
        tekenRH(r12)
        overl6 = r11.overlap(r12)
        fill(white)
        if overl6: tekenRH(overl6)
        
        r13 = Rechthoek( Punt( 150, 100 ), 80, 50 )
        r14 = Rechthoek( Punt( 190, 130 ), 70, 30 )
        noFill()
        tekenRH(r13)
        tekenRH(r14)
        overl7 = r13.overlap(r14)
        fill(white)
        if overl7: tekenRH(overl7)
            
        r15 = Rechthoek( Punt( 300, 100 ), 80, 50 )
        r16 = Rechthoek( Punt( 320, 130 ), 50, 30 )
        noFill()
        tekenRH(r15)
        tekenRH(r16)
        overl8 = r15.overlap(r16)
        fill(white)
        if overl8: tekenRH(overl8)
        
        r17 = Rechthoek( Punt( 400, 100 ), 80, 50 )
        r18 = Rechthoek( Punt( 390, 110 ), 10, 30 )
        noFill()
        tekenRH(r17)
        tekenRH(r18)
        overl9 = r17.overlap(r18)
        fill(white)
        if overl9: tekenRH(overl9)
        
        r19 = Rechthoek( Punt( 500, 100 ), 80, 50 )
        r20 = Rechthoek( Punt( 590, 90 ), 10, 30 )
        noFill()
        tekenRH(r19)
        tekenRH(r20)
        overl10 = r19.overlap(r20)
        fill(white)
        if overl10: tekenRH(overl10)
        
        r21 = Rechthoek( Punt( 20, 190 ), 80, 50 )
        r22 = Rechthoek( Punt( 10, 175 ), 30, 30 )
        noFill()
        tekenRH(r21)
        tekenRH(r22)
        overl11 = r21.overlap(r22)
        fill(white)
        if overl11: tekenRH(overl11)

        r23 = Rechthoek( Punt( 150, 190 ),  80, 50 )
        r24 = Rechthoek( Punt( 130, 210 ), 120, 20 )
        noFill()
        tekenRH(r23)
        tekenRH(r24)
        overl12 = r23.overlap(r24)
        fill(white)
        if overl12: tekenRH(overl12)

        r25 = Rechthoek( Punt( 300, 190 ), 80, 50 )
        r26 = Rechthoek( Punt( 320, 180 ), 50, 70 )
        noFill()
        tekenRH(r25)
        tekenRH(r26)
        overl13 = r25.overlap(r26)
        fill(white)
        if overl13: tekenRH(overl13)

    from tkinter import *
    from tkinter import ttk

    root = Tk()
    frm = ttk.Frame(root, padding=10)
    frm.grid()
    ttk.Label(frm, text="Test rechthoeken").grid(column=0, row=0)
    ttk.Button(frm, text="Quit", command=root.destroy).grid(column=1, row=0)
    canvas = Canvas(frm, width=100, height=100)
    canvas.grid(column=0, row=1)

    globalfill = ''
    def size(x, y):
        canvas.config(width=x+100, height=y)
    grey = 'lightgrey'
    def background(color):
        canvas.config(bg=color)
    def noFill():
        global globalfill
        globalfill = ''
    def rect(x, y, br, ho):
        # print('rect', x, y, br, ho, globalfill)
        canvas.create_rectangle(x, y, x+br, y+ho, fill=globalfill)
    white = 'white'
    def fill(color):
        global globalfill
        globalfill=color
    setup()
    draw()
    root.mainloop()

#=============================================================#
# Opgave 20.2

if 0:
    markeer( 'Opgave 20.2 X' )

    import datetime

    class Student:
        def __init__(self, voornaam, achternaam, geboortedatum,
                     administratienummer):
            self.voornaam = voornaam
            self.achternaam = achternaam
            self.geboortedatum = geboortedatum
            self.administratienummer = administratienummer
        def __repr__(self):
            return ' '.join(('Student', self.voornaam, self.achternaam,
                   str(self.geboortedatum), str(self.leeftijd()),
                   str(self.administratienummer)))
        def leeftijd( self ):
            vandaag = datetime.date.today()
            geboren = self.geboortedatum

            # zie Stack Overflow, Age from birthdate in python
            # https://stackoverflow.com/questions/2217488/age-from-birthdate-in-python

            # onvoldoende nauwkeurig
            #   zoals 4e antwoord waarin 365.2425 wordt gebruikt
            # diff = vandaag - geboren
            # return int(diff.days / 365.25)

            # gebaseerd op geaccepteerde 1e antwoord
            # correctie = vandaag.month, vandaag.day < geboren.month, geboren.day
            # correctie = (vandaag.month, vandaag.day < geboren.month, geboren.day)
            correctie = (vandaag.month, vandaag.day) < (geboren.month, geboren.day)
            return vandaag.year - geboren.year - correctie

    class Cursus:
        def __init__(self, naam, nummer):
            self.naam = naam
            self.nummer = nummer
        def __repr__(self):
            return ' '.join(('Cursus', self.naam, self.nummer))

    def main():
        studentCursussen = { }   # { Student : set(Cursus, Cursus, ..), ... }
        cursusStudenten = { }   # { Cursus : set(Student, Student, ..), ... }

        def StudentVolgtCursus(student, cursus):
            if 1:
                if not student in studentCursussen:
                    studentCursussen[student] = { cursus }  # = set
                else:
                    studentCursussen[student].add(cursus)
                if not cursus in cursusStudenten:
                    cursusStudenten[cursus] = { student }  # = set
                else:
                    cursusStudenten[cursus].add(student)

            # niet in boek
            # beter: gebruik dict.setdefault
            # https://docs.python.org/3/library/stdtypes.html#dict.setdefault
            if 0:
                cursussen = studentCursussen.setdefault(student, set())
                cursussen.add(cursus)
                studenten = cursusStudenten.setdefault(cursus, set())
                studenten.add(student)
            if 0:
                studentCursussen.setdefault(student, set()).add(cursus)
                cursusStudenten.setdefault(cursus, set()).add(student)

        an = Student('An', 'van Arnhem', datetime.date(1980, 10, 7), 133)
        bob = Student('Bob', 'Bennekom', datetime.date(1974, 1, 21), 721)
        celine = Student('Celine', "du Château", datetime.date(2000, 12, 31), 2200)

        wiskunde = Cursus('wiskunde', 'W2')
        informatica = Cursus('Informatica', 'I7')
        solderen = Cursus('Solderen', 'S35')

        StudentVolgtCursus(an, informatica)
        StudentVolgtCursus(an, solderen)
        
        StudentVolgtCursus(bob, wiskunde)

        StudentVolgtCursus(celine, informatica)
        StudentVolgtCursus(celine, solderen)
        StudentVolgtCursus(celine, wiskunde)

        if 0:
            print( 'An', an.leeftijd() )
            print( 'Bob', bob.leeftijd() )
            print( 'Celine', celine.leeftijd() )
            return

        for student in studentCursussen:
            print(student, 'volgt')
            cursussen = studentCursussen[student]
            for cursus in cursussen:
                print('  ', cursus)
        for cursus in cursusStudenten:
            print(cursus, 'wordt gevolgd door')
            studenten = cursusStudenten[cursus]
            for student in studenten:
                print('  ', student)

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 21.1

if 0:
    markeer( 'Opgave 21.1 X' )

    kleuren = "Harten", "Schoppen", "Klaveren", "Ruiten"
    waarden = 2, 3, 4, 5, 6, 7, 8, 9, 10, "Boer", "Vrouw", "Heer", "Aas"

    class Kaart:
        def __init__(self, kleur, waarde):
            if not kleur in kleuren:
                raise ValueError("onbekende kleur " + kleur)
            if not waarde in waarden:
                raise ValueError("onbekende waarde " + waarde)
            self.kleur = kleur
            self.waarde = waarde
        def __eq__(self, other):
            return self.waarde == other.waarde
            # return (self.kleur, self.waarde) == (other.kleur, other.waarde)
        def __lt__(self, other):
            return waarden.index(self.waarde) < waarden.index(other.waarde)
            # return ((kleuren.index(self.kleur), waarden.index(self.waarde)) <
            #     (kleuren.index(other.kleur), waarden.index(other.waarde)))
        def __le__(self, other):
            return self < other or self == other
        def __repr__(self):
            return "Kaart(" + self.kleur + ', ' + str(self.waarde) + ')'

    def main():
        try:
            kaart0 = Kaart("Clubs", 15)
        except ValueError as e:
            print('Kaart0 exception', e)
        kaart1 = Kaart("Harten", 5)
        kaart2 = Kaart("Schoppen", "Boer")
        print(kaart1, kaart2, 'gelijk', kaart1 == kaart2)
        print(kaart1, kaart2, 'ongelijk', kaart1 != kaart2)
        print(kaart1, kaart2, 'kleiner', kaart1 < kaart2)
        print(kaart1, kaart2, 'kleiner of gelijk', kaart1 <= kaart2)
        print(kaart1, kaart2, 'groter', kaart1 > kaart2)
        print(kaart1, kaart2, 'groter of gelijk', kaart1 >= kaart2)

    if __name__ == '__main__':
        main()

#=============================================================#
# Opgave 21.2

if 0:
    markeer( 'Opgave 21.2 X' )

    kleuren = "Harten", "Schoppen", "Klaveren", "Ruiten"
    waarden = 2, 3, 4, 5, 6, 7, 8, 9, 10, "Boer", "Vrouw", "Heer", "Aas"

    class Kaart:
        def __init__(self, kleur, waarde):
            if not kleur in kleuren:
                raise ValueError("onbekende kleur " + kleur)
            if not waarde in waarden:
                raise ValueError("onbekende waarde " + waarde)
            self.kleur = kleur
            self.waarde = waarde
        def __eq__(self, other):
            return self.waarde == other.waarde
            # return (self.kleur, self.waarde) == (other.kleur, other.waarde)
        def __lt__(self, other):
            return waarden.index(self.waarde) < waarden.index(other.waarde)
            # return ((kleuren.index(self.kleur), waarden.index(self.waarde)) <
            #     (kleuren.index(other.kleur), waarden.index(other.waarde)))
        def __le__(self, other):
            return self == other or self < other
        def __repr__(self):
            return "Kaart(" + self.kleur + ', ' + str(self.waarde) + ')'

    def main1():
        try:
            kaart0 = Kaart("Clubs", 15)
        except ValueError as e:
            print('Kaart0 exception', e)
        kaart1 = Kaart("Harten", 5)
        kaart2 = Kaart("Schoppen", "Boer")
        print(kaart1, kaart2, 'gelijk', kaart1 == kaart2)
        print(kaart1, kaart2, 'ongelijk', kaart1 != kaart2)
        print(kaart1, kaart2, 'kleiner', kaart1 < kaart2)
        print(kaart1, kaart2, 'kleiner of gelijk', kaart1 <= kaart2)
        print(kaart1, kaart2, 'groter', kaart1 > kaart2)
        print(kaart1, kaart2, 'groter of gelijk', kaart1 >= kaart2)

    class Trekstapel:
        def __init__(self):
            self.kaarten = [ ]
        def __len__(self):
            return len(self.kaarten)
        def __getitem__(self, index):
            return self.kaarten[index]
        def voegtoe(self, kaart):
            self.kaarten.append(kaart)
        def trek(self):
            return self.kaarten.pop(0)
        def __repr__(self):
            return 'Stapel(' + ', '.join(repr(kaart) for kaart in self.kaarten) + ')'

    def main2():
        stapel = Trekstapel()
        kaart1 = Kaart("Harten", 10)
        kaart2 = Kaart("Schoppen", "Boer")
        kaart3 = Kaart("Ruiten", 10)
        kaart4 = Kaart("Klaveren", 2)
        print(stapel)
        stapel.voegtoe(kaart1)
        stapel.voegtoe(kaart2)
        stapel.voegtoe(kaart3)
        stapel.voegtoe(kaart4)
        print(stapel)
        print(stapel.trek())
        print(stapel.trek())
        print(stapel.trek())
        print(stapel.trek())
        print(stapel)
        # print(stapel.trek()) ## verbeteren

    if __name__ == '__main__':
        # main1()
        main2()

#=============================================================#
# Opgave 21.3

if 0:
    markeer( 'Opgave 21.3 X' )

    # ♡ = '♡'
    # π = '♡'
    # kleuren = π, "Schoppen", "Klaveren", "Ruiten" # Harten"
    kleuren = "Harten", "Schoppen", "Klaveren", "Ruiten"
    waarden = 2, 3, 4, 5, 6, 7, 8, 9, 10, "Boer", "Vrouw", "Heer", "Aas"

    class Kaart:
        def __init__(self, kleur, waarde):
            if not kleur in kleuren:
                raise ValueError("onbekende kleur " + kleur)
            if not waarde in waarden:
                raise ValueError("onbekende waarde " + waarde)
            self.kleur = kleur
            self.waarde = waarde
        def __eq__(self, other):
            return self.waarde == other.waarde
            # return (self.kleur, self.waarde) == (other.kleur, other.waarde)
        def __lt__(self, other):
            return waarden.index(self.waarde) < waarden.index(other.waarde)
            # return ((kleuren.index(self.kleur), waarden.index(self.waarde)) <
            #     (kleuren.index(other.kleur), waarden.index(other.waarde)))
        def __le__(self, other):
            return self == other or self < other
        def __repr__(self):
            return "Kaart(" + self.kleur + ', ' + str(self.waarde) + ')'

    def main1():
        try:
            kaart0 = Kaart("Clubs", 15)
        except ValueError as e:
            print('Kaart0 exception', e)
        kaart1 = Kaart("Harten", 5)
        kaart2 = Kaart("Schoppen", "Boer")
        print(kaart1, kaart2, 'gelijk', kaart1 == kaart2)
        print(kaart1, kaart2, 'ongelijk', kaart1 != kaart2)
        print(kaart1, kaart2, 'kleiner', kaart1 < kaart2)
        print(kaart1, kaart2, 'kleiner of gelijk', kaart1 <= kaart2)
        print(kaart1, kaart2, 'groter', kaart1 > kaart2)
        print(kaart1, kaart2, 'groter of gelijk', kaart1 >= kaart2)

    class Trekstapel:
        def __init__(self):
            self.kaarten = [ ]
        def __len__(self):
            return len(self.kaarten)
        def __getitem__(self, index):
            return self.kaarten[index]
        def voegtoe(self, kaart):
            self.kaarten.append(kaart)
        def trek(self):
            return self.kaarten.pop(0)
        def __repr__(self):
            return 'Stapel(' + ', '.join(repr(kaart) for kaart in self.kaarten) + ')'

    def main2():
        stapel = Trekstapel()
        kaart1 = Kaart("Harten", 10)
        kaart2 = Kaart("Schoppen", "Boer")
        kaart3 = Kaart("Ruiten", 10)
        kaart4 = Kaart("Klaveren", 2)
        stapel.voegtoe(kaart1)
        stapel.voegtoe(kaart2)
        stapel.voegtoe(kaart3)
        stapel.voegtoe(kaart4)
        print(stapel.trek())
        print(stapel.trek())
        print(stapel.trek())
        print(stapel.trek())

    def main3():
        stapel1 = Trekstapel()
        stapel1.voegtoe(Kaart('Ruiten', 2))
        stapel1.voegtoe(Kaart('Harten', 'Heer'))
        stapel1.voegtoe(Kaart('Klaveren', 7))
        stapel2 = Trekstapel()
        stapel2.voegtoe(Kaart('Harten', 4))
        stapel2.voegtoe(Kaart('Harten', 3))
        stapel2.voegtoe(Kaart('Schoppen', 8))

        print('begin')
        print(' stapel1', stapel1)
        print(' stapel2', stapel2)
        ronde = 0
        while len(stapel1) and len(stapel2):
            kaart1 = stapel1.trek()
            kaart2 = stapel2.trek()
            if kaart1 > kaart2:
                stapel1.voegtoe(kaart1)
                stapel1.voegtoe(kaart2)
            else:
                stapel2.voegtoe(kaart2)
                stapel2.voegtoe(kaart1)
            ronde += 1
            print('ronde', ronde)
            print(' stapel1', stapel1)
            print(' stapel2', stapel2)
        ## print winnaar
        
    if __name__ == '__main__':
        # main1()
        # main2()
        main3()

#=============================================================#
# Opgave 21.4 X

# zie ook Opgave 21.4 F

if 0:
    markeer( 'Opgave 21.4 X' )

    class Fruitmand:
        def __init__(self):
            self.fruits = { }  # { 'fruitnaam' : aantal }
        def __add__(self, fruit):
            # mag self niet wijzigen
            result = Fruitmand()
            result.fruits = self.fruits.copy()
            # result.fruits = dict(self.fruits)
            result += fruit
            return result
        def __iadd__(self, fruit):
            # wijzigt self
            self.fruits[fruit] = self.fruits.get(fruit, 0) + 1
            # if fruit in self.fruits:
            #     self.fruits[fruit] += 1
            # else:
            #     self.fruits[fruit] = 1
            return self
        def __radd__(self, fruit):
            return self + fruit
        def __sub__(self, fruit):
            # mag self niet wijzigen
            result = Fruitmand()
            result.fruits = self.fruits.copy()
            result -= fruit
            return result
        def __isub__(self, fruit):
            # wijzigt self
            if fruit in self:
                aantal = self.fruits[fruit] - 1
                if aantal > 0:
                    self.fruits[fruit] = aantal
                else:
                    del self.fruits[fruit]
            return self
        def __contains__(self, fruit):
            return fruit in self.fruits
        def __getitem__(self, fruit):
            return self.fruits[fruit]
        def __setitem__(self, fruit, aantal):
            if aantal <= 0:
                raise(ValueError('ongeldig aantal ' + str(aantal)))
            self.fruits[fruit] = aantal
        def __len__(self):
            return len(self.fruits)
        def __repr__(self):
            return ('Fruitmand(' +
                ', '.join(key + '=' + str(value)
                    for (key, value) in self.fruits.items())
                + ')')

    if __name__ == '__main__':
        main21_4( 'X' )  # code main21_4 aan einde Opgave 21.4 F

#=============================================================#
# Opgave 22.1

if 0:
    markeer( 'Opgave 22.1 A X' )

    # exercise2201.py
    class Rechthoek:
        def __init__( self, x, y, b, h ):
            self.x = x
            self.y = y
            self.b = b
            self.h = h
        def __repr__( self ):
            return "[({},{}),b={},h={}]".format( self.x, self.y,
                self.b, self.h )
        def oppervlakte( self ):
            return self.b * self.h
        def omtrek( self ):
            return 2*(self.b + self.h)

if 0:
    markeer( 'Opgave 22.1 B X' )

    class Rechthoek:
        def __init__( self, x, y, b, h ):
            self.x = x
            self.y = y
            self.b = b
            self.h = h
        def __repr__( self ):
            return "[({},{}),b={},h={}]".format( self.x, self.y,
                self.b, self.h )
        def oppervlakte( self ):
            return self.b * self.h
        def omtrek( self ):
            return 2*(self.b + self.h)

    class Vierkant( Rechthoek ):
        def __init__( self, x, y, b ):
            super().__init__( x, y, b, b )

    r = Rechthoek( 1, 2, 10, 8 )
    print( r, 'oppervlakte', r.oppervlakte(), 'omtrek', r.omtrek() )
    v = Vierkant( -12, -12, 9 )
    print( v, 'oppervlakte', v.oppervlakte(), 'omtrek', v.omtrek() )

#=============================================================#
# Opgave 22.2

if 0:
    markeer( 'Opgave 22.2 X' )

    import math

    class Vorm:
        def __init__( self, x, y ):
            self.x = x
            self.y = y
        def __repr__( self ):
            return NotImplemented
        def oppervlakte( self ):
            return NotImplemented
        def omtrek( self ):
            return NotImplemented

    class Rechthoek( Vorm ):
        def __init__( self, x, y, b, h ):
            super().__init__( x, y )
            self.b = b
            self.h = h
        def __repr__( self ):
            return "[({},{}),b={},h={}]".format( self.x, self.y,
                self.b, self.h )
        def oppervlakte( self ):
            return self.b * self.h
        def omtrek( self ):
            return 2*( self.b + self.h)

    class Vierkant( Rechthoek ):
        def __init__( self, x, y, b ):
            super().__init__( x, y, b, b )

    class Cirkel( Vorm ):
        def __init__( self, x, y, r ):
            super().__init__( x, y )
            self.r = r
        def __repr__( self ):
            return "[({},{}),r={}]".format( self.x, self.y,
                self.r )
        def oppervlakte( self ):
            return math.pi * pow( self.r, 2 )
        def omtrek( self ):
            return 2 * math.pi * self.r

    r = Rechthoek( 1, 2, 10, 8 )
    print( r, 'oppervlakte', r.oppervlakte(), 'omtrek', r.omtrek() )
    v = Vierkant( -12, -12, 9 )
    print( v, 'oppervlakte', v.oppervlakte(), 'omtrek', v.omtrek() )
    c = Cirkel( -6, 3, 1 )
    print( c, 'oppervlakte', c.oppervlakte(), 'omtrek', c.omtrek() )

#=============================================================#
# Opgave 22.3

if 0:
    markeer( 'Opgave 22.3 A X' )

    # exercise2203.py
    COOPERATIE = 'C'
    DEFECTIE = 'D'
    RONDES = 100

    class Strategie:
        def __init__( self, name="" ):
            self.name = name
            self.score = 0
        def keuze( self ):
            # Moet COOPERATIE of DEFECTIE retourneren
            return NotImplemented
        def laatstezet( self, mijnzet , opponentzet ):
            # Krijgt de laatste zet die gemaakt is, na keuze()
            pass
        def plusscore( self, n ):
            self.score += n

    strategie1 = Strategie()
    strategie2 = Strategie()
    for i in range( RONDES ):
        c1 = strategie1.keuze()
        c2 = strategie2.keuze()
        if c1 == c2:
            strategie1.plusscore( 3 if c1 == COOPERATIE else 1 )
            strategie2.plusscore( 3 if c2 == COOPERATIE else 1 )
        else:
            strategie1.plusscore( 0 if c1 == COOPERATIE else 6 )
            strategie2.plusscore( 0 if c2 == COOPERATIE else 6 )
        strategie1.laatstezet( c1, c2 )
        strategie2.laatstezet( c2, c1 )

    print( "Eind score", strategie1.name, "is", strategie1.score )
    print( "Eind score", strategie2.name, "is", strategie2.score )

if 0:
    markeer( 'Opgave 22.3 B X' )

    import random

    COOPERATIE = 'C'
    DEFECTIE = 'D'
    RONDES = 100

    class Strategie:
        def __init__( self, name="" ):
            self.name = name
            self.score = 0
        def keuze( self ):
            # Moet COOPERATIE of DEFECTIE retourneren
            return NotImplemented
        def laatstezet( self, mijnzet , opponentzet ):
            # Krijgt de laatste zet die gemaakt is, na keuze()
            pass
        def plusscore( self, n ):
            self.score += n

    class Random( Strategie ):
        def __init__( self ):
            super().__init__( 'Random' )
        def keuze( self ):
            return COOPERATIE if random.randint(0, 1) else DEFECTIE

    class AltijdD( Strategie ):
        def __init__( self ):
            super().__init__( 'AltijdD' )
        def keuze( self ):
            return DEFECTIE

    class OogOmOog( Strategie ):
        def __init__( self ):
            super().__init__( 'OogOmOog' )
            self.laatste = COOPERATIE
        def keuze( self ):
            return self.laatste
        def laatstezet( self, mijnzet, opponentzet ):
            self.laatste = opponentzet

    class OogOmTweeOgen( Strategie ):
        def __init__( self ):
            super().__init__( 'OogOmTweeOgen' )
            self.laatste = [ COOPERATIE, COOPERATIE ]
        def keuze( self ):
            return COOPERATIE if COOPERATIE in self.laatste else DEFECTIE
        def laatstezet( self, mijnzet, opponentzet ):
            self.laatste.pop( 0 )
            self.laatste.append( opponentzet )

    class Meerderheid( Strategie ):
        def __init__( self ):
            super().__init__( 'Meerderheid' )
            self.laatste = [ ]
        def keuze( self ):
            countc = self.laatste.count(COOPERATIE)
            countd = self.laatste.count(DEFECTIE)
            if countc < countd:
                return DEFECTIE
            # if countc > countd:
            #     return COOPERATIE
            return COOPERATIE
        def laatstezet( self, mijnzet, opponentzet ):
            self.laatste.append( opponentzet )

    # strategie1 = Strategie()
    # strategie1 = Random()
    strategie1 = AltijdD()
    # strategie2 = Strategie()
    strategie2 = Random()
    # strategie2 = AltijdD()
    for i in range( RONDES ):
        c1 = strategie1.keuze()
        c2 = strategie2.keuze()
        if c1 == c2:
            strategie1.plusscore( 3 if c1 == COOPERATIE else 1 )
            strategie2.plusscore( 3 if c2 == COOPERATIE else 1 )
        else:
            strategie1.plusscore( 0 if c1 == COOPERATIE else 6 )
            strategie2.plusscore( 0 if c2 == COOPERATIE else 6 )
        strategie1.laatstezet( c1, c2 )
        strategie2.laatstezet( c2, c1 )

    print( "Eind score", strategie1.name, "is", strategie1.score )
    print( "Eind score", strategie2.name, "is", strategie2.score )

    if 0:
        print()
        classes = ( Random, AltijdD, OogOmOog,
                    OogOmTweeOgen, Meerderheid )
        for class1 in classes:
            for class2 in classes:
                strategie1 = class1()
                strategie2 = class2()
                for i in range( RONDES ):
                    c1 = strategie1.keuze()
                    c2 = strategie2.keuze()
                    if c1 == c2:
                        strategie1.plusscore( 3 if c1 == COOPERATIE else 1 )
                        strategie2.plusscore( 3 if c2 == COOPERATIE else 1 )
                    else:
                        strategie1.plusscore( 0 if c1 == COOPERATIE else 6 )
                        strategie2.plusscore( 0 if c2 == COOPERATIE else 6 )
                    strategie1.laatstezet( c1, c2 )
                    strategie2.laatstezet( c2, c1 )

                print( "Eind score", strategie1.name, "is", strategie1.score )
                print( "     score", strategie2.name, "is", strategie2.score )

if 0:
    markeer( 'Opgave 22.3 C X' )

    # zie ook https://nl.wikipedia.org/wiki/Prisoner%27s_dilemma

    # cPy : exercise 22.3 programmeursleerling Pieter Spronk
    # provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-01-11  initial release

    import  os;             os.system('clear')
    from    random  import  randint

    COOPERATIE  = 'C'
    DEFECTIE    = 'D'
    RONDES      = 100

    # ================================== Super Abstract class Strategie ==============================

    class Strategie :

        def __init__(self, name =""):
            
            self.name         = name
            self.score        = 0
            self.mijn_zet     = None
            self.opponent_zet = None


        def keuze (self):
            return NotImplemented       # Moet COOPERATIE of DEFECTIE retourneren


        # Krijgt de laatste zet die gemaakt is, na keuze ()
        def laatsteZet (self, mijnzet, opponentzet):
            self.mijn_zet     = mijnzet
            self.opponent_zet = opponentzet


        def plusScore (self, n):
            self.score += n

    # ================================ StratRandom ==================================

    # Random speelt COOPERATIE of DEFECTIE per toeval
    class   StratRandom(Strategie):

        def __init__(self):
            super().__init__('Random')


        def keuze(self):
            return COOPERATIE if randint(0, 1) else DEFECTIE
        
    # ================================ StratAltijdD ==================================

    # AltijdD speelt altijd DEFECTIE
    class   StratAltijdD(Strategie):

        def __init__(self):
            super().__init__('AltijdD')

            
        def keuze(self):
            return DEFECTIE
        
    # ================================ StratOogOmOog ==================================

    # OogOmOog start met COOPERATIE, en speelt daarna wat de tegenstander in de vorige
    # ronde speelde (de laatstezet() methode krijgt altijd te zien wat de tegenstander
    # speelde nadat een keuze gemaakt is)
    class   StratOogOmOog(Strategie):

        def __init__(self):
            super().__init__('OogOmOog')


        def keuze(self):
            if not self.mijn_zet: return COOPERATIE
            return self.opponent_zet
        
    # ================================ StratOogOmTweeOgen ==================================

    # OogOmTweeOgen start met twee keer COOPERATIE, en speelt dan DEFECTIE als de
    # opponent DEFECTIE speelde in de twee voorgaande rondes, en anders COOPERATIE
    class   StratOogOmTweeOgen(Strategie):

        def __init__(self):
            super().__init__('OogOmTweeOgen')


        def keuze(self):
            if self.mijn_zet == None:
                self.mijn_zet     = 0
                self.opponent_zet = 0
            if self.mijn_zet < 2:
                self.mijn_zet += 1
                return COOPERATIE
            if self.opponent_zet >= 2: return DEFECTIE
            return COOPERATIE


        def laatsteZet (self, mijnzet, opponentzet):
            if opponentzet == DEFECTIE: self.opponent_zet += 1
            else:                       self.opponent_zet  = 0

    # ================================ StratMeerderheid ==================================

    # Meerderheid start met COOPERATIE, en speelt dan wat de tegenstander speelde in
    # de meerderheid van de voorgaande rondes
    class   StratMeerderheid(Strategie):

        def __init__(self):
            super().__init__('Meerderheid')


        def keuze(self):
            if self.opponent_zet == None:
                self.opponent_zet = [0, 0]          # N cooperatie, n defectie
                return COOPERATIE
            if self.opponent_zet [0] >= self.opponent_zet[1]:  return COOPERATIE
            return DEFECTIE


        def laatsteZet (self, mijnzet, opponentzet):
            if opponentzet == COOPERATIE: self.opponent_zet[0] += 1
            else:                         self.opponent_zet[1] += 1

    # ============================== Update score =======================================

    # Als beide strategieën C spelen, krijgen ze allebei 3
    # punten. Als ze beide D spelen, krijgen ze allebei 1 punt. Als één strategie C speelt en één
    # strategie D, dan krijgt de strategie die C speelt 0 punten, en de strategie die D speelt 6 punten.
    def updateScore(speler_1, zet_1, speler_2, zet_2):
        if zet_1 == COOPERATIE:
            if zet_2 == COOPERATIE:
                speler_1.plusScore(3); speler_2.plusScore(3)        # C, C   3, 3
            else:
                speler_2.plusScore(6)                               # C, D   0, 6
        else:
            if zet_2 == COOPERATIE:
                speler_1.plusScore(6)                               # D, C   6, 0
            else:
                speler_1.plusScore(1); speler_2.plusScore(1)        # D, D   1, 1

    # ================================== Game ===========================================
                
    guard    = StratAltijdD()
    prisoner = StratMeerderheid()

    for i in range(RONDES):
        c1 = guard.keuze()
        c2 = prisoner.keuze()
        updateScore(guard, c1, prisoner, c2)
        guard.laatsteZet(c1, c2)
        prisoner.laatsteZet(c2, c1)

    print(f'eind score guard    met {guard.name:13} is {guard.score}')
    print(f'eind score prisoner met {prisoner.name:13} is {prisoner.score}')

#=============================================================#
# Opgave 23.1

# Script: Expeditie-door-H23.py (1)
#         Uittreksel uit en bewerking van
#         ProgrammeursleerlingExtra.py, 2024-01-04 15:11 (2)
# Datum : 2024-01-04, Theo van Haandel
#         2024-01-18, Meindert Meindertsma
#         2024-01-24  (1) in (2) hoofdstuk 23 en appendix X 23

if 0:
    markeer( 'Opgave 23.1 X' )

    vals = [ ]
    # vals = [ 2, 3, 7 ]
    if not vals:
        while True:
            txt = input( 'Geef positieve integer, 0 voor einde: ' )
            try:
                val = int(txt)
            except ValueError:
                print( 'Geen geldige integer' )
            else:
                if val < 0:
                    print( 'Integer mag niet negatief zijn' )
                elif val == 0:
                    break
                vals.append(val)

    class NietDeelbaar:
        def __init__( self, vals, deelmin=1, deelmax=100 ):
            self.vals = vals
            self.deelmin = deelmin
            self.deelmax = deelmax
        def __iter__( self ):
            return NietDeelbaarIter( self )

    class NietDeelbaarIter:
        def __init__( self, nietDeelbaar ):
            self.nietDeelbaar = nietDeelbaar
            self.deel = nietDeelbaar.deelmin
        def __iter__( self ):
            return self
        def __next__( self ):
            while True:
                if self.deel > self.nietDeelbaar.deelmax:
                    raise StopIteration()
                for val in self.nietDeelbaar.vals:
                    if self.deel % val == 0:
                        break
                else:
                    deel = self.deel
                    self.deel += 1
                    return deel
                self.deel += 1

    # print(vals)
    nd = NietDeelbaar( vals )
    for x in nd:
        print( x, end=' ' )

#=============================================================#
# Opgave 23.2

if 0:
    markeer( 'Opgave 23.2 X' )

    def faculteit( einde=10 ):
        mul = 1
        fac = 1        
        while mul <= einde:
            yield fac
            mul += 1
            fac *= mul

    for x in faculteit():
        print( x, end=' ' )

#=============================================================#
# Opgave 23.3

if 0:
    markeer( 'Opgave 23.3 X' )

    import itertools

    woord = input( 'geef woord: ' )
    # woord = 'gen'
    for i, wrd in enumerate( itertools.permutations( woord ) ):
        print( i, ''.join( wrd ) )

#=============================================================#
# Opgave 23.4

if 0:
    markeer( 'Opgave 23.4 X' )

    import itertools

    woord = input( 'geef woord: ' )
    # woord = 'aap'
    for i, wrd in enumerate( set( itertools.permutations( woord ) ) ):
        print( i, ''.join( wrd ) )

#=============================================================#
# Opgave 23.5

if 0:
    markeer( 'Opgave 23.5 X' )

    # zie ook Opgave 12.8

    # zie ook https://en.wikipedia.org/wiki/Subset_sum_problem

    # zie ook combinations functie in Python documentatie
    #   met equivalente niet recursieve code
    # https://docs.python.org/3/library/itertools.html#itertools.combinations

    import itertools

    def subsetsom(lst):
        print('Subset som van', lst)
        count = 0
        for size in range( 1, len( lst ) + 1 ): # exclusief lege set
        # for size in range( len( lst ) + 1 ):  # inclusief lege set
            for x in itertools.combinations( lst, size ):
                if sum( x ) == 0:
                    print( 'Gevonden (', *x, ')' )
                    count += 1
        if count == 0:
            print('Geen gevonden')
        else:
            print('Aantal gevonden', count)
        print()

    def main():
        subsetsom( [1, 4, -3, -5, 7] )
        subsetsom( [1, 4, -3, 7] )

        subsetsom( [-7, -3, -2, 9000, 5, 8] )

        # zie ook Opgave 12.8 X
        # lege lijst heeft
        #   bij gebruik range vanaf 1 geen subset
        #   bij gebruik range vanaf 0 wel lege subset
        # lege set is ook een subset met sum 0
        #   kan gezien worden als geldig antwoord
        # sum( set() ) = sum( {} ) = 0
       
        subsetsom( [ ] ) 

        # omdat een list wordt gebruikt
        #   zijn meerdere met dezelfde waarde mogelijk
        #   is dan geen verzameling zoals nodig volgens de opgave
        # bij Python set geen dubbele mogelijk
        #   zie hoofdstuk 14 Sets
        subsetsom( [1, 1, -2] )

    main()

#=============================================================#
# Opgave 23.6

if 0:
    markeer( 'Opgave 23.6 X' )

    import itertools
    import pprint

    pp = pprint.PrettyPrinter(indent=4)

    def subdicts( dct ):
        lst = [ ]
        for size in range( len( dct ) + 1 ):
            for k in itertools.combinations( dct.keys(), size ):
                lst.append( { key : dct[key] for key in k } )
#        print( 'Sub-dictionaries van', dct )
#        print( '  ', lst )
        print( '\nSub-dictionaries van', dct )
        pp.pprint( lst )

    subdicts( { "a" : 1, "b" : 2 } )
    subdicts( { "x" : 10, "y" : '11', 'z' : () } )
    
#=============================================================#
# Opgave 23.7

if 0:
    markeer( 'Opgave 23.7 X' )

    # zie ook https://en.wikipedia.org/wiki/Eight_queens_puzzle

    import itertools

    def queens():
        n = 8
        count = 0
        for perm in itertools.permutations(range(n)):
            diags1 = set() # /
            diags2 = set() # \
            for x, y in enumerate(perm):
                diag1 = x + y
                if diag1 in diags1:
                    break
                diags1.add(diag1)
                diag2 = (n - x) + y
                if diag2 in diags2:
                    break
                diags2.add(diag2)
            else:
                count += 1
                print(count, perm)
                if 0:
                    for x in perm:
                        print( '.' * x, '+', '.' * (n - x), sep='' )

    queens()

#=============================================================#
# Opgave 24.1

if 0:
    markeer( 'Opgave 24.1 X' )

    # roept zichzelf recursief aan via subprocess
    import sys

    arglst = sys.argv[1:]
    # print( 'Argumenten:', *arglst )
    som = 0.0
    for ix, arg in enumerate( arglst ):
        try:
            som += float( arg )
        except ValueError:
            print( 'Geen numerieke waarde:', arg, 'op positie:', ix + 1 )
    print( 'Som:', som )

    vals = input( 'Geef numerieke waarden voor som, leeg voor stop: ' )
    if vals:
        import subprocess

        if sys.platform.startswith( 'win' ):
            pyexe = 'py.exe'
        else:
            pyexe = 'python3'
        # print( pyexe )
        pymodule = sys.argv[0]
        # print( pymodule )
        if 1:
            args = [ pyexe, pymodule ]
            args.extend( vals.split() )
        else:
            args = pyexe, pymodule, *vals.split()
        # print( args )
        subprocess.run( args )

#=============================================================#
# Opgave 25.1

if 0:
    markeer( 'Opgave 25.1 X' )

    import re

    tekst = """\
Opgave 25.1 Neem aan dat een woord alleen kan bestaan uit letters van het alfabet
(hoofd- of kleine letters). Schrijf code die een reguliere expressie gebruikt om alle woorden
van een tekst in een list te zetten."""
    rex = r'\b[A-Za-z]+\b'
    woorden = re.findall( rex, tekst )
    # print( woorden )
    for woord in woorden:
        print( woord, end=' ' )
    print()

#=============================================================#
# Opgave 25.2

if 0:
    markeer( 'Opgave 25.2 X' )

    import re

    tekst = """\
Opgave 25.2 Gebruik een reguliere expressie en de findall() methode om een list te
maken van alle instanties van het woord "de" in een zin. Druk het aantal instanties af.
Je code mag geen onderscheid maken tussen hoofd- en kleine letters. Zorg ervoor dat je
de combinatie "de" niet telt als het een onderdeel is van een ander woord (bijvoorbeeld
"onderdeel," "delicaat", of "woede").
Deze regel met dE, DE, ED, De en 'de' er ook nog bij..."""
    rex = r'\b[Dd][Ee]\b'
    des = re.findall( rex, tekst )
    # print( des )
    print( 'Aantal instanties van "de":', len( des ) )

#=============================================================#
# Opgave 25.3

if 0:
    markeer( 'Opgave 25.3 X' )

    import re

    tekst = """\
Opgave 25.3 De volledige naam van een persoon bestaat uit twee woorden, die naast
elkaar staan, en die alleen letters van het alfabet bevatten, die allemaal kleine letters zijn
behalve de eerste, die een hoofdletter moet zijn. Tussen de woorden mogen alleen spaties
staan. De woorden beginnen en eindigen bij een woordscheider. Bijvoorbeeld, volgens
deze specificatie is Kardinaal Richelieu een naam, maar Charles d'Artagnan niet, noch
Gilbert duPrez, Joe DiMaggio, of Unit X1138. Maak een reguliere expressie die een list
maakt van alle twee-woorden combinaties in een zin die (waarschijnlijk) de namen van
personen zijn onder deze aanname.
Test Example Erbij gezet Of Niet..."""
    # rex = r'\b[A-Z][a-z]*\b \b[A-Z][a-z]*\b'  # fout, meer spaties mogelijk
    rex = r'\b[A-Z][a-z]*\b +\b[A-Z][a-z]*\b'
    namen = re.findall( rex, tekst )
    # print( namen )
    for naam in namen:
        print( naam )
    
#=============================================================#
# Opgave 25.4

if 0:
    markeer( 'Opgave 25.4 X' )

    import re

    tekst = """\
Een Matig lange zin met af en toe iets wat lijkt op Een Naam Ertussen en DIE
nog veel meer woOrden Bevat Om Te Kijken of die invloed hebben of nieT."""
    rex = r'(\b[A-Z][a-z]*\b( \b[A-Z][a-z]*\b)+)'
    namen = re.findall( rex, tekst )
    # print( namen )
    for naam in namen:
        # print( naam )
        print( naam[0] )

#=============================================================#
# Opgave 25.5

if 0:
    markeer( 'Opgave 25.5 X' )

    import re

    tekst = """\
Opgave 25.5 Als in een stuk tekst een persoon spreekt, wordt dit meestal weergegeven
door de gesproken tekst tussen dubbele aanhalingstekens ("") te plaatsen. Schrijf een regu-
liere expressie die alle delen van een tekst die tussen dubbele aanhalingstekens staan eruit
haalt. Hint: Gebruik groepen, en bedenk dat reguliere expressies "gulzig" zijn.
Erbij de persoon zegt: "Dit staat tussen aanhalingstekens."."""
    rex = r'("[^"]*")'
    quotes = re.findall( rex, tekst )
    # print( quotes )
    for quote in quotes:
        print( quote )

#=============================================================#
# Opgave 25.6

if 0:
    markeer( 'Opgave 25.6 X' )

    # exercise2506.py
    import re

    tekst = "<html><head><title>Lijst van personen met ids</title >\
</head><body>\
<p><id>123123123</id><naam>Groucho Marx</naam>\
<p><id>123123124</id><naam>Harpo Marx</naam>\
<p><id>123123125</id><naam>Chico Marx</naam>\
<randomcrap>Etaoin<id>Shrdlu</id>qwerty</naam></randomcrap>\
<nocrap><p><id>123123126</id><naam>Zeppo Marx</naam></nocrap>\
<address>Chicago</address>\
<morerandomcrap><id>999999999</id>geennaamtezien!\
</morerandomcrap>\
<p><id>123123127</id><naam>Gummo Marx</naam>\
<note>Zoek hem op via <a href=\"http://www.google.com\">\
Google.</a></note>\
</body></html>"

# In F:    r"<id>([^<]+)</id><naam>([^<]+)</naam>"
#    rex = r'<id>[0-9]{9}</id><naam>((\w|\s)+)</naam>'
#    rex = r'(<id>([0-9]{9})</id><naam>((?:\w|\s)+)</naam>)'
    rex = r'(<id>(\d{9})</id><naam>((?:\w|\s)+)</naam>)'
    ids = re.findall( rex, tekst )
    # print( ids )
    for id in ids:
        print( id )
        print( id[0] )
        print( id[1], id[2] )
        print()

#=============================================================#
# Opgave 26.1

if 0:
    markeer( 'Opgave 26.1 X A' )

    import csv

    with open( 'pc_inventory.csv', 'r', newline = '' ) as fpr:
        reader = csv.reader( fpr )
        with open( 'pc_inventory2.csv', 'w', newline = '' ) as fpw:
            writer = csv.writer( fpw, delimiter=' ',
                                 quotechar="'", quoting=csv.QUOTE_ALL )
            for regel in reader:
                print( regel )
                writer.writerow( regel )
    print()

    with open( 'pc_inventory2.csv', 'r' ) as fp:
        for regel in fp:
            print( regel, end='' )

if 0:
    markeer( 'Opgave 26.1 X B' )

    # niet in boek
    # oef26_1.py

    # Pieter Spronck - Hoofdstuk 26 - opgave 1
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-03-07  initial release

    import  os;     os.system('clear')
    import  csv

    # ============================ Local ==================================

    # Convert csv fn_src to space separated values file f_dst
    def convertCsv(fn_src, fn_dst):
        with open(fn_src, newline='') as f_src:
            with open(fn_dst, 'w', newline='') as f_dst:
                reader_src = csv.reader(f_src)
                writer_dst = csv.writer(f_dst, delimiter=' ', quotechar="'")
                for line in reader_src:
                    writer_dst.writerow(line)


    # Show space separated value file fn
    def showCsv(fn):
        with open(fn) as f:
            csv_reader = csv.reader(f, delimiter=' ', quotechar="'")
            for line in csv_reader:
                print(''.join(f'{x:>15}' for x in line))

    # ================================ Main ==============================
                
    def main():
        convertCsv('pc_inventory.csv', 'temp.csv')
        showCsv('temp.csv')


    main()

    # ================================= End ==============================

#=============================================================#
# Opgave 26.2

if 0:
    markeer( 'Opgave 26.2 X A' )

    import csv
    import json

    regels = []
    with open( 'pc_inventory.csv', 'r', newline = '' ) as fpr:
        reader = csv.reader( fpr )
        for regel in reader:
            regels.append(regel)

    with open( "pc_inventory.json", "w" ) as fpw:
        # json.dump( regels , fpw )
        json.dump( regels , fpw, indent=2 )

    with open( "pc_inventory.json", "r" ) as fpr:
        print( fpr.read() )
        # for regel in fpr:
        #     print( regel, end='' )
    print()

if 0:
    markeer( 'Opgave 26.2 X B' )

    # niet in boek
    # oef26_2.py

    # Pieter Spronck - Hoofdstuk 26 - opgave 2
    # Provided by Anthony Vogelaar, Perth UK
    # rev 0.10  2024-03-07  initial release

    import  os;     os.system('clear')
    import  json
    import  csv

    # ================================= Local ==========================

    # Read a csv file and convert it to a list of lists object
    def getCsvAsList(fn):
        with open(fn) as f:
            csv_reader = csv.reader(f)
            res        = []
            for line in csv_reader:
                res.append(line)
        return res


    # Save object obj as formatted json file fn
    def saveAsJson(fn, obj):
        with open(fn, 'w') as f:
            # json.dump(obj, f, indent=2)
            json.dump(obj, f)

    # =============================== Main =============================
            
    def main():
        lst = getCsvAsList('pc_inventory.csv')
        saveAsJson('test.json', lst)
        print('done')


    main()

    # ================================= End ============================

#=============================================================#
# Opgave 27.1

if 0:
    markeer( 'Opgave 27.1 X' )

    import collections

    tekst = '''
Opgave 27.1 Gebruik de Counter class om de vijf meest voorkomende letters in een
tekst te tonen, inclusief hun tellingen.
'''
    print( tekst )

    letters = [ ch for ch in tekst if ch.isalpha() ]
    # print( letters )

    cntr = collections.Counter(letters)
    for ch, aantal in cntr.most_common( 5 ):
        print( ch, aantal )

#=============================================================#
# Opgave 27.2

if 0:
    markeer( 'Opgave 27.2 X' )

    import collections
    import statistics

    vals = []
    # vals = [ 7.2, -5.3, 7.2, 8.9, -328, 1, 2, 8.9 ]
    if not vals:
        while True:
            num = input('Geef getal, 0 voor einde:')
            try:
                val = float( num )
            except ValueError:
                print( 'Ongeldig getal', num )
            else:
                if val == 0:
                    break
                vals.append( val )

    print( *vals )
    # print( vals )
    
    print( 'gemiddelde', statistics.mean( vals ) )
    print( 'mediaan', statistics.median( vals ) )

    cntr = collections.Counter( vals )
    most = cntr.most_common()
    modus = [ ]
    if len( most ) == 0:
        # print( 'modus', 'geen' )
        pass
    else:
        mostval, mostaantal = most[0]
        modus.append( mostval )
        # print( 'modus', mostval, mostaantal )
        for val, aantal in most[1:]:
            if aantal < mostaantal:
                break
            modus.append( val )
            # print( 'modus', val, aantal )
    print( 'modus', *modus )
