Hoofdstuk 13 Programmeren (2) Programmeren in Mathematica behelst meer dan alleen weten wat een Module is en het kunnen toepassen van sturingscommando's, zoals For of If. Om goed te programmeren in Mathematica is er meer inzicht nodig in de syntax die door Mathematica wordt gebruikt. Dit hoofdstuk probeert een inzicht te geven in die syntax en in de verschillende wijzen waarop in Mathematica geprogrammeerd kan worden. N.B. Een goed opgezet programma (stukje code): - beï nvloedt de omgeving niet; - wordt niet beï nvloed door de omgeving; - is goed leesbaar (zichzelf documenterend); - is algemeen te gebruiken; - is gemakkelijk te onderhouden; - is zo eenvoudig mogelijk; - is zo efficië nt mogelijk. In dit hoofdstuk komen een aantal nieuwe begrippen aan bod zoals: slots, default-waarde van argumenten, underscores en pattern matching, wildcards. De belangrijke begrippen als Map en Apply komen in het volgende hoofdstuk pas uitgebreid aan bod.
13.1 Functionele constructies In het algemeen is een functie een bewerking op data. Om een functie uit te voeren moet ten eerste de bewerking worden gespecificeerd en ten tweede moet je data hebben waarop de bewerking dient te worden worden uitgevoerd. In de Mathematica syntax kan een functie op meerdere manieren worden geï mplementeerd. Slechts één van die manieren wordt ook daadwerkelijk een functie (Function) genoemd. In de volgende paragraaf zien we dat de manier waarop we tot nu toe een functie in Mathematica termen definieerden een zogenaamde patroonvervangingsregel is. In deze paragraaf richten we ons op wat in Mathematica wordt verstaan onder een Function. Een Function is n.l. een constructie waarbij expliciet wordt opgeven dat we hier te maken hebben met een bewerking op een data-element. In het nu volgende worden verschillende soorten functies besproken. 1) Zuivere functies zonder een naam met gebonden variabelen
In een zuivere functie (pure function) wordt expliciet vermeld dat het om Function gaat, wat de bewerking is, en wat de parameters zijn. De parameter en de bewerking op die parameter worden achter elkaar geschreven, gescheiden door een komma. Deze expressie krijgt als Head mee: Function. Mathematica weet dan dat, als er achter dit geheel een waarde tussen blokhaken wordt gezet, deze waarde voor de parameter moet worden ingevuld. Dit klinkt moeilijker dan het is. Een eenvoudig voorbeeld maakt alles in één klap duidelijk: Function@x, x + 2D @2D 4
De variabele x die in de functie wordt gebruikt, bestaat alleen lokaal binnen de functie. Een globale variabele x wordt niet beï nvloedt door de functie. Je kunt meerdere variabelen meegeven in een lijst. De lengte van de lijst moet dan wel gelijk zijn aan het aantal argumenten van de functie. 2) Zuivere functies met een naam en met gebonden variabelen
Zuivere functies kunnen net als de 'gewone' functies een naam krijgen. Deze opzet lijkt veel op de definitie van de functie die je al kende, bijv. f[x_]:= x+2, maar hij is het niet. Het resultaat is wel gelijk. Voorbeeld:
CP-cursus, 2007-2008
118
Hoofdstuk13.nb
f = Function@x, x + 2D; f@2D 4
De functienaam f die voor de functie wordt gebruikt zal een eventueel bestaande globale variabele f ontoegankelijk maken. Let dus op bij het kiezen van een functienaam. 3) Zuivere functies zonder naam en zonder gebonden variabelen
Hier wordt gebruik gemaakt van zogenaamde slots (#). Slots worden vaak gebruikt in het programmeren in Mathematica. De eerste variabele wordt volgens afspraak aangeduid met # (of #1), de tweede met #2, etc. Naast de volledige notatie is ook een verkorte notatie toegestaan. Het ‘ &’ -teken geeft hier aan dat het om een functie gaat: Function[body] betekent precies hetzelfde als body&. Voorbeeld: Function@ð + 2D @2D ð + 2 & @2D 4 4
Een pure functie met 2 argumenten is bijvoorbeeld Function@ð1 + ð2D@ 3, 4D ð1 + ð2 & @3, 4D 7 7
De functionele constructie van een naamloze functies zonder variabelen (maar met Slots) komt vaker voor dan je zou denken. Het wordt vooral toegepast wanneer een functie slechts op één plaats in het programma wordt uitgevoerd en je er dus liever geen naam aan wilt verbinden. Een voorbeeld is het Select commando wat je in het vorige hoofdstuk al bent tegengekomen. Hierin wordt een pure functie zonder naam als tweede argument gebruikt. lijst = 81, 2, 3, 4, 5, 6<; Select@lijst, ð £ 3 &D 81, 2, 3<
4) Expressies
Ook een expressie werkt meestal als een functie zonder dat daarbij is aangegeven dat het om een Function gaat. Voorbeeld: SolveAx2 == 2, 8x<E ::x ® -
2 >, :x ®
2 >>
Dit is eigenlijk gewoon een functie aanroep. De naam van de functie is Solve, en er zijn 2 argumenten, n.l. x^22 en {x}. De output van deze functie verkrijg je door de functie te evalueren.
13.2 Regelgestuurde constructies Een tweede manier om een functie te implementeren in Mathematica is met behulp van patroonvervangingsregels. Dit wordt ook wel een regelgestuurde constructie genoemd. Deze manier om een functie te implementeren sluit beter aan op wat we in het begin van de cursus onder een functie verstonden. In Mathematica heet het echter een patroonvervanging ofwel Pattern Matching. Programmeren met patroonvervangingsregels is één van de meest krachtige manieren van programmeren in Mathematica.
CP-cursus, 2007-2008
Hoofdstuk13.nb
119
Remove@"Global`*"D f@x_D = x2 ;
De parameter x in de definitie krijgt bij de uitvoering van de functie f een waarde (wat er op de plaats van x_ staat) toegekend, waarna de functie als output het kwadraat daarvan retourneert. De functie bestaat hier uit de meest algemene patroonvervangingsregel: x ® f[x], in dit geval is dat x^2, ongeacht de "waarde" van x. De waarde van het algemene patroon kan worden opgegeven bij de executie van de functie. Remove@"Global`*"D f@x_D = x2 ; f@yD f@8a, b
Deze vorm van functie definitie is dus eigenlijk een patroonvervangingsregel. Ingewikkelder pattern matching is ook mogelijk. We bespreken een aantal veel voorkomende typen. Om de functie alleen te laten uitvoeren als de input van een bepaald type is, bijvoorbeeld alleen integer getallen, moet het datatype worden gespecificeerd. Er zijn meer datatypes, zoals Integer, Real en String. Om een functie alleen op een invoer van een bepaald type te laten uitvoeren, wordt er achter de underscore (_) het datatype vermeld. Remove@"Global`*"D f@x_IntegerD = x2 ; f@x_RealD = - x3 ; 8f@aD, f@2D,
[email protected]< 8f@aD, 4, - 8.<
Er kan ook worden opgelegd dat de functie alleen mag worden uitgevoerd als aan een bepaalde voorwaarde is voldaan. Men kan dit aangeven met een vraagteken (?) of een conditie specificeren achter een ‘ /;’teken. Achter een vraagteken (?) wordt een functie geplaatst die de waarde True of False retourneert. De functies IntegerQ, NumericQ, enzovoorts zijn dergelijke functies. Ze testen of het argument aan bepaalde criteria voldoet. Een overzicht van alle bestaande Q functies in Mathematica die beginnen met een N krijg je door evaluatie van de vraagteken-operator. Let op de wildcard *. ? N*Q System`
NameQ
NumberQ
NumericQ
Remove@"Global`*"D f@x_ ; x > 0D =
x;
f@x_ ; x £ 0D = - - x ; Plot@f@xD, 8x, - 1, 1
0.5
-1.0
0.5
-0.5
1.0
-0.5
-1.0
Voor het testen of iets positief is of negatief, bestaan de functies Positive en Negative, zonder Q.
CP-cursus, 2007-2008
120
Hoofdstuk13.nb
Voor het testen of iets positief is of negatief, bestaan de functies Positive en Negative, zonder Q. Remove@fD; f@x_ ? PositiveD =
x;
f@x_ ? NegativeD = - - x ; Plot@f@xD, 8x, - 1, 1
0.5
-1.0
0.5
-0.5
1.0
-0.5
-1.0
Patronen die op deze manier zijn gedefinieerd zijn dwingend: als er niet aan de voorwaarde wordt voldaan, dan kan en zal de vervangingsregel niet worden toegepast. Een wat minder streng patroon is als er een default waarde wordt gedefinieerd. Als de input niet wordt opgegeven of niet voldoet aan de condities wordt de default waarde gebruikt. Een default waarde wordt opgegeven door achter de blank (‘ _’ ) een ‘ :’teken te plaatsen, met daarachter de default waarde voor die parameter. Dit kun je op deze wijze alleen doen voor het laatste argument in een functie. Remove@"Global`*"D f@x_, y_ : 2D = Hx + yL2 ; 8f@xD, f@x, 3D< 9H2 + xL2 , H3 + xL2 =
Meer informatie over het gebruik van Pattern Matching als programmeerstijl vind je bijvoorbeeld bij Introduction to Patterns en Putting Constraints on Patterns. Alle patroondefinities in deze paragraaf kunnen worden aangewend om ervoor te zorgen dat een bepaalde definitie alleen in hele specifieke gevallen wordt uitgevoerd, of dat een functie juist heel robuust is tegen onjuiste of onverwachte invoer. Er zijn zo dus meerdere patroondefinities voor één en dezelfde functie mogelijk die elkaar voor specifieke waarden van x kunnen tegenspreken, bijv. Remove@"Global`*"D f@x_D = 1; f@x_ ; - 1 < x < 1D = Abs@xD; Plot@f@xD, 8x, - 2, 2
0.8
0.6
0.4
0.2
-2
-1
1
2
Hoewel de meest algemene definitie eerst werd gegeven, wordt bij de evaluatie van f eerst gecontroleerd of de meer specifieke patroondefinitie wellicht van toepassing is. Dit kun je makkelijk nagaan door met de ? operator (Information) informatie over f op te vragen. Meestal is het overduidelijk welke patroondefinitie het meest specifiek is en dus het eerst wordt CP-cursus, 2007-2008 geprobeerd.
Hoofdstuk13.nb
121
Hoewel de meest algemene definitie eerst werd gegeven, wordt bij de evaluatie van f eerst gecontroleerd of de meer specifieke patroondefinitie wellicht van toepassing is. Dit kun je makkelijk nagaan door met de ? operator (Information) informatie over f op te vragen. Meestal is het overduidelijk welke patroondefinitie het meest specifiek is en dus het eerst wordt geprobeerd. ?f Global`f f@x_ ; - 1 < x < 1D = Abs@xD f@x_D = 1
13.3 Modulair / procedureel programmeren In Mathematica bestaan meerdere manieren om programma onderdelen af te schermen van de rest van het programma. In deze afgeschermde programma onderdelen kunnen variabelen, constanten en functies lokaal worden gedefinieerd. Dat wil zeggen, de waarde die er aan wordt toegekend bestaat alleen binnen dat onderdeel. Je kent al de Module. Alle constructies zijn bedoeld om robuuster, overzichtelijker en compacter te programmeren. Vooral in lange programma’ smet herhalingsloops en dergelijke is dat erg belangrijk. Met een Module kunnen lokale variabelen en functies worden gedefinieerd die alleen binnen de module bestaan. Binnen modules kunnen de lokale variabelen al dan niet van waarde veranderd worden. Modules kunnen conditioneel (als aan een bepaalde voorwaarde wordt voldaan) worden uitgevoerd door de hele Module in een If-statement te zetten. Als alternatief kun je in Mathematica beter de Conditon operator of de verkorte notatie daarvan te gebruiken (‘ /;’ .) Zie regelgestuurd programmeren.
13.4 Voorbeelden bij Programmeren Mathematica biedt de mogelijkheid om vrij makkelijk met lijsten te kunnen rekenen, zonder daarvoor steeds weer een stukje "programma" te hoeven schrijven. Als uitgangspunt voor de volgende voorbeelden nemen we een 10 bij 10 matrix met de getallen 1 tot en met 100, waarop we bepaalde bewerkingen willen uitvoeren. Alle onderstaande bewerkingen zijn vrij makkelijk in een one-liner te programmeren. matrix = Partition@Table@i, 8i, 100
Overzicht: 1 De som van alle elementen in een geneste lijst matrix // Flatten // Total
2
De som van de elementen in elke kolom
3
Dot-product van de elementen in twee rijen i en j van een matrix
4
Dot-product van de elementen in twee kolommen i en j
5
Een 3 bij 3 matrix met de eerst 3 rijen van de eerste 3 kolommen
6
De even elementen van de derde rij
7
De som van de elementen in elke rij
Total[matrix] matrix[[i]] . matrix[[j]] matrix[[All,i]] . matrix[[All,j]] matrix[[1;;3, 1;;3]] Select[matrix[[3]], EvenQ] Total /@ matrix
Het gebruik van de /@ operator in het laatste voorbeeld ken je waarschijnlijk nog niet, dit komt in het volgende hoofdstuk aan bod. In de nu volgende cellen wordt geverifieerd dat de makkelijk zelf na te rekenen resultaten correct zijn. 13.5.1 Berekenen van de som van alle elementen in een geneste lijst
CP-cursus, 2007-2008
122
Hoofdstuk13.nb
13.5.1 Berekenen van de som van alle elementen in een geneste lijst matrix Flatten Total 5050
13.5.2 Berekenen van de som van de elementen in elke kolom Total@matrixD 8460, 470, 480, 490, 500, 510, 520, 530, 540, 550<
13.5.4 Dot-product van de elementen in twee rijen van een matrix
Het dot-product van de 3e en de 4e rij is: matrix@@3DD.matrix@@4DD 9135
Ter controle: dit is gelijk aan 21*31 + 22*32 +... 30*40, ofwel Table@H20 + iL * H30 + iL, 8 i, 10
13.5.5 Dot-product van de elementen in twee kolommen matrix@@All, 3DD. matrix@@All, 4DD 31 770
Ter controle: dit is 3*4 + 13*14+ ... + 93 *94, ofwel Table@i * Hi + 1L, 8i, 3, 93, 10
13.5.6 Een 3 bij 3 matrix met de eerst 3 rijen van de eerste 3 kolommen matrix@@1 ;; 3, 1 ;; 3DD matrix@@Range@1, 3D, Range@1, 3DDD; H* Herriner dat dit hetzelfde is als voorgaand commando *L 881, 2, 3<, 811, 12, 13<, 821, 22, 23<<
13.5.6 De even elementen van de derde rij Select@matrix@@3DD, EvenQD 822, 24, 26, 28, 30<
13.5.7 Berekenen van de som van de elementen in elke rij Total matrix 855, 155, 255, 355, 455, 555, 655, 755, 855, 955<
De functie Map ofwel /@ doet in dit geval eigenlijk niets anders dan de functie Total toepassen op alle elementen (=rijen) van matrix.
13.6 Samenvatting CP-cursus, 2007-2008
Hoofdstuk13.nb
123
13.6 Samenvatting Remove@"Global`*"D
Je kunt functies ook maken zonder namen aan variabelen of de functie zelf te geven. Dit hete pure functies en kunnen handig zijn als je even snel een functie wilt hebben zonder dat je die later nog een keer nodig hebt. Een pure functie voor een kwadraat gaat als volgt: ð ^ 2 &@4D 16
Verder kun je voor functies ook nog testen wat voor invoer ze krijgen, en aan de hand daarvan het gedrag van de functie aan te passen. Je kunt bijvoorbeeld een functie maken die voor hele getallen wat anders teruggeeft als voor reë le getallen: g@x_IntegerD := x ^ 3 g@x_RealD := x ^ 4 8g@3D,
[email protected]< 827, 81.<
Of je kunt functies maken die anders reageren op positieve en negatieve getallen: h@x_ ; x >= 0D := Sqrt@xD h@x_ ; x < 0D := Sqrt@- xD 8h@10D, h@- 10D< :
10 ,
10 >
Dit kan ook op een andere manier, en hetzelfde resultaat. Voor x=0 is de volgende functie echter ongedefinieerd: i@x_ ? PositiveD := 1 i@x_ ? NegativeD := - 1 i@0D i@0D
Verder kun je ook nog zogenaamde default-waarden aan functies geven, die ze toekennen aan een variabele als er geen input is gegeven: j@x_, y_: 10D := x + y 8j@1, 0D, j@1D< 81, 11<
13.7 Opgaven bij Programmeren Û Tip: Kopieer elke opgave eerst naar een eigen opgaven-notebook, en begin dan pas met de uitwerking ervan.
CP-cursus, 2007-2008
124
Hoofdstuk13.nb
Opgave 13.1 Radio uitzendingen van Amsterdam naar Tokio Radioamateurs gebruiken vaak de breking- en spiegelende eigenschappen van de dampkring om over lange afstanden te kunnen zenden. Normaal gesproken zou de afstand die maximaal kan worden overbrugd met een radiogolf vanwege de aardkromming gelijk zijn aan de vrijzicht afstand over de aarde. Een deel van de dampkring, de f1-laag van de ionosfeer in dit geval, reflecteert echter radiogolven van de juiste frequentie terug naar de aarde; men spreekt dan van een hop. Voor nog langere afstanden worden multi-hop zendingen gebruikt. De radiogolven kaatsen dan meerdere malen tussen de aarde en de f1-laag. Op deze manier kunnen grotere afstanden worden gehaald, tot zelfs 'rond de wereld signalen'. In deze opdracht is de vraag hoeveel hops er minimaal nodig zijn om van Amsterdam naar Tokio te zenden. Neem voor de hoogte van de f1-laag 300 km aan; de omtrek van de aarde is 40.000 km en neem aan dat de aarde is een echte bol is. Coö rdinaten van een aantal plaatsen zijn bekend en de afstanden kunnen tussen 2 punten op aarde kan makkelijk worden berekend met Mathematica. Kijk hiervoor naar items met de naam CityData en Geodesy Package. Neem aan dat de aarde een bol is. 1. Wat is de afstand Amsterdam - Tokio (Tokyo) in kilometer, gemeten langs het oppervlak van de aarde met bovenstaande aannames? 2. Wat is de hoek tussen Amsterdam en Tokio in radialen; gezien vanuit het middelpunt van de aarde? 3. Hoeveel hops zijn minimaal nodig om met een radiogolf die kaatst tussen de f1-laag in de ionosfeer en de aarde van Amsterdam naar Tokio te zenden? 4. Schrijf een module Hops die niet alleen een antwoord geeft voor de afstand Tokio - Amsterdam, maar ook voor elk tweetal "bekende" plaatsen. De aanroep is dus van de vorm Hops["naam1", "naam2"]. Als de namen niet (of nog niet) bekend zijn binnen Mathematica mag er van alles gebeuren. 5. Nodificeer de module zodat het tweede argument niet langer verplicht is. Als het ontbreekt wordt de default waarde "Amsterdam" gebruikt. Kijk hiervoor bijvoorbeeld bij Optional. Als de argumenten x en y in een aanroep Hops[x,y] niet van het type String zijn mag de definitie niet worden toegepast. 6. Maak in een 2D-vlak (je beeldscherm) een plot van de doorsnede van de aarde door Amsterdam, Tokio en het middelpunt van de aarde, de f1-laag en het 'pad' van de radiogolf tussen Amsterdam en Tokio. Geef de verschillende onderdelen in de figuur een leuk kleurtje, zie o.a. Line, Disk en Point. Opgave 13.2 Afstanden tot Amsterdam Bereken de afstand tussen Amsterdam en alle hoofdsteden die minder dan 1500 kilometer van Amsterdam liggen. De opgave is makkelijker geformuleerd dan uitgevoerd. Gebruik hiervoor het commando CountryData[] en SphericalDistance[]. Voor het laatste commando moet je het Geodesy package laden. Het resultaat moet uiteindelijk een tabel met Stad - Land - Afstand zijn, zoals hieronder (alleen de eerste tien rijen zijn weergegeven): AndorraLaVella Vienna Brussels Sarajevo Zagreb Copenhagen Tallinn Torshavn Paris Berlin
Andorra Austria Belgium BosniaHerzegovina Croatia Denmark Estonia FaroeIslands France Germany
1128 937 177 1375 1087 620 1455 1277 431 576
Voor de liefhebber: maak een plot van het aantal steden binnen straal r als functie van die straal voor 0 km < r < 20 000 km. Wat voor verdeling zou je verwachten als de hoofdsteden homogeen over de aardbol waren verdeeld? Klopt dit?
CP-cursus, 2007-2008
Hoofdstuk13.nb
125
Stappenplan
1. Laadt het benodigde package. 2. Zoek uit hoe je met CountryData[] de plaats en naam van de hoofdstad van een land kunt bepalen. 3. Bepaal hoe je een lijst van alle landen kunt krijgen met CountryData[]. 4. Gebruik nu het Map[] commando om voor alle hoofdsteden van alle landen de afstand in hele kilometers tot Amsterdam te bepalen. Hint: maak een lijstje met paren van {Land, Afstand tot Amsterdam}. 5. Gebruik nu Select[] om alleen de landen eruit te filteren waarvan de hoofdstad verder dan 1500 kilometer van Amsterdam ligt. 6. Gebruik nu weer Map[] om voor alle landen die je over hebt de naam van de hoofdstad te krijgen en de afstand tot Amsterdam. 7. Probeer je commando nu in te korten door overbodige dingen weg te halen, en plaats dit in 1 cel.
CP-cursus, 2007-2008