Programmeren in REXX/Functies deel 2: verschil tussen versies

Verwijderde inhoud Toegevoegde inhoud
GuyDC (overleg | bijdragen)
Toevoegen van Translate functie
GuyDC (overleg | bijdragen)
Verhuizen van date en time naar ander hoofdstuk, kleine toevoegingen
Regel 2:
In dit hoofdstuk behandelen we een tweede reeks standaard REXX functies.
 
Het gaat om functies die toelaten getallen te formatteren of om te zetten, envan functieséén dieformaat in een ander. We overlopen ook nog even hoe REXX rekent met datumgetallen en tijdhoe we de nauwkeurigheid van de uitkomsten kunnen werkenbepalen.
 
Zoals steeds zullen resultaten tussen « en » worden getoond. Als er sprake is van een '''pad''' karakter, dan dient dit karakter om de (kortste) string op te vullen. Standaard wordt daar de spatie voor gebruikt.
 
=Hoe rekent REXX ?=
=Beheer van datums en tijd=
Het resultaat van een numerische bewerking is een karakterreeks. De regels die daarbij spelen zijn:
Omdat datums en tijden geen gebruik maken van het tientallig stelsel is het werken ermee niet altijd eenvoudig en vraagt het grote aandacht. Fouten zijn snel gemaakt.
*Het resultaat is berekend tot een maximaal aantal significante cijfers. Standaard is de nauwkeurigheid beperkt tot 9 cijfers, maar dit aantal kan aangepast worden zoals we verder zullen leren. Indien het resultaat meer cijfers nodig heeft wordt het afgerond, dus 2/3=0.666666667;
==TIME==
*Behalve bij deling en machtsverheffing worden de nullen aan eind van de decimalen behouden, dus 2.40+2=2.40;
'''time'''([optie])
*Als het resultaat nul is dan wordt het steeds als één enkel cijfer 0 voorgesteld;
Met deze functie kan men de huidige tijd opvragen. Deze kan in verschillende formaten worden verkregen (enkel eerste karakter is daarbij nodig):
*Exponentiële notatie wordt pas gebruikt als het aantal significante cijfers voor het decimaal punt groter wordt dan de nauwkeurigheid (standaardwaarde=9). Ook indien het aantal decimalen meer dan 2 maal de precisie overschrijdt, schakelt REXX over op exponentiële notatie, bijvoorbeeld:
*Time('''N'''ormal) «15:57:48» (dit is de standaard-optie)
1e6 *Time('''C'''ivil) 1e6 «3:57pm1E+12» (bijen onsniet weinig gebruikt)«1000000000000»
1 / 3E10 «3.33333333E-11» en niet «0.0000000000333333333»
*Time('''L'''ong) «15:57:48.315000» (microseconden)
*Alvorens een berekening aan te vatten zal REXX de spaties wegnemen. Noteer dat ".92" omgezet wordt tot "0.92". Het getal wordt dan beperkt tot het aantal significante cijfers plus één. De bewerking zelf gebeurt met het dubbele van de opgegeven nauwkeurigheid (standaard dus 2*9 cijfers);
*Time('''H'''ours) «15» (uren sinds het begin v/d dag)
*Machtsverheffing kan enkel met een exponent die een '''geheel''' positief of negatief getal is.
*Time('''M'''inutes) «957» (minuten sinds het begin v/d dag)
*Time('''S'''econds) «57468» (seconden sinds het begin v/d dag)
En nog twee speciale opties:
*Time('''E'''lapsed) «0.45455» (misschien)
*Time('''R'''eset) «10.03100» (misschien)
De eerste reeks behoeft weinig meer uitleg, behalve dat bij het '''L'''ong formaat, men op PC systemen geen grotere nauwkeurigheid dan milliseconden moet verwachten.
 
Als de functie '''time''' meermaals in hetzelfde statement wordt opgeroepen dan zal ze steeds dezelfde waarde teruggeven. Het is alsof de tijd dan even stilstaat. Bovenstaande resultaten zijn allemaal samen in één statement uitgevoerd, dus slaan de uitkomsten op dezelfde tijd.
 
De twee laatste opties vragen wel wat meer uitleg. Ze laten toe de tijd te meten die een taak erover heeft gedaan. Het is dus een soort chronometer.
<br>Bekijk deze volgorde:
call time 'R' /* zet de chronometer op 0, Reset dus */
/* ...een eerste reeks bewerkingen... */
say 'Reeks 1 nam' time('E') 'seconden in beslag.'
/* ...een tweede reeks bewerkingen... */
say 'Reeksen 1 en 2 namen samen' time('R') 'seconden in beslag.'
/* ...een derde reeks bewerkingen... */
say 'Reeks 3 duurde' time('E') 'seconden.'
In het eerste statement doen we een '''Reset''' van de chronometer. We zetten hem op nul. Vermits we niet geïnteresseerd zijn in de tijd die de chronometer op dat moment had gebruiken we een '''call''' bevel.
<br>Na de eerste reeks bewerkingen vragen we de '''Elapsed''' (verlopen) tijd op. De chronometer wordt daarbij niet terug op nul gezet, en zal dus blijven voortlopen.
<br>Na de tweede reeks bewerkingen vragen we weer de verlopen tijd op, maar tegelijkertijd zetten we de chronometer terug op nul.
<br>Uiteindelijk vragen we hoeveel tijd de derde reeks bewerkingen in beslag namen.
 
Noteer ook dat REXX slechts één chronometer bezit. Als hij wordt gestart in een subroutine, dan zal hij zijn waarde verliezen (en terug op nul vallen) bij het verlaten van de subroutine.
 
Hier volgen een aantal voorbeelden van tijdsrekening:
parse value time() time('S') with TijdNu DagSeconden
parse var TijdNu uu ':' mm ':' ss /* splits tijd in uren, minuten en seconden */
AantalSeconden=uu*3600 + mm*60 + ss /* hoeveel is dat in seconden */
say AantalSeconden=DagSeconden /* &#171;1&#187; want anders maakten we een fout */
Plus4min= AantalSeconden + 4*60 /* doe er 4 minuten bij */
/* Nu terug omzetten naar uu:mm:ss formaat... */
uu = Plus4min % 3600 /* Gehele deling */
if uu>23 then uu=uu-24 /* Misschien middernacht overschreden bij optelling */
rest = Plus4min // 3600 /* Rest na deling door 3600 (= aantal sec in 1 uur */
mm = rest % 60 /* Omzetten naar minuten via gehele deling */
ss = Plus4min // 60 /* Seconden zijn rest na deling door 60 */
Say 'Binnen 4 minuten zal het' right(uu,2,0)':'right(mm,2,0)':'right(ss,2,0) 'zijn.'
Het laatste deel is zeker een functie waard als men deze omzetting vele malen moet uitvoeren. Ze kan er dan in een zeer compacte vorm als volgt uitzien:
S2T: Procedure /* Zet een aantal Seconden om in normale Tijdformaat */
parse arg secs .
if secs > 86400 then secs=secs-86400 /* Meer dan 24 uur ? */
return right(secs % 3600,2,0) &#124;&#124; ':' &#124;&#124;,
right(secs // 3600 % 60,2,0) &#124;&#124; ':' &#124;&#124;,
right(secs // 60,2,0)
Bestudeer deze functie nog eens aandachtig zodat alle aspecten ervan duidelijk worden. Ze lijkt misschien nogal cryptisch, maar ze is alvast performant omdat er geen werkvariabelen worden aangemaakt.
 
==DATE==
'''date'''([optie])
Het oorspronkelijk formaat, met slechts één mogelijke parameter ''optie'', is nogal rudimentair. Men kan ermee de huidige datum in verschillende formaten opvragen. De ''optie'' (eerste letter is daarbij voldoende) bepaalt het formaat. We vernoemen enkel de voornaamste die men in alle REXX implementaties zal terugvinden:
*'''N'''ormal - dit formaat is standaard als geen optie wordt meegegeven. We krijgen de datum in de vorm '''dd mnd jjjj''', bv. 16 mar 2011 (maand in 3 karakters, Engelse namen);
*'''B'''asedate - het aantal dagen sinds 1 januari van het jaar 1. Er wordt hier geen rekening gehouden met de aanpassingen aan de kalender zoals o.a. Paus Gregorius ze doorvoerde in 1582. Voor meer informatie over
tijdrekening, zie [[w:Jaartelling|dit artikel]]. Indien we een restdeling door 7 op een basisdatum uitvoeren, dan bekomen we de dag in de week, waarbij maandag=0 en zondag=6. Anders gezegd, 1 januari 0001 was een dinsdag (want 1//7 = 1).
*'''D'''ays - het aantal dagen sinds het begin van dit jaar;
*'''E'''uropean - de datum in verkort Europees formaat (dd/mm/jj);
*'''M'''onth - de maand in dit jaar, niet afgekorte Engelse naam, bv. "May";
*'''O'''rdered - de datum in formaat jj/mm/dd. Kan dienen om te sorteren, maar het jaartal heeft slechts 2 posities;
*'''S'''orted - vandaar dat dit formaat beter aangepast is om te sorteren: jjjjmmdd;
*'''U'''sa - in Amerikaans formaat: dd/mm/jj;
*'''W'''eekday - de dag van de week in niet afgekorte Engelse tekst, bv. "Saturday".
OORexx biedt ook nog het '''L'''anguage formaat aan, waarbij de datum in lokaal formaat wordt gegeven, bv. 17 januari 2011. Het lokaal formaat wordt in de systeeminstellingen bepaald. Een REXX programma mag geen veronderstellingen maken omtrent het resultaat omdat het programma op een ander systeem in de prak kan lopen als er een andere land-instellingen is gemaakt.
 
Kortom, de meest zinvolle formaten zijn N, B, S, D, M en W. Sorteren kan enkel zinvol gebeuren met het S of B formaat. Het B-formaat is dan weer ideaal om met datums te rekenen zoals we verder zullen leren.
 
Bij vorige eeuwwisseling werd de functie drastisch uitgebreid om de jaar-2000 problematiek te kunnen aanpakken. De datum kan nu variabel zijn (dus niet enkel die van vandaag). Meer nog, de datum kan via de functie worden omgezet van één formaat in een ander waarbij ook scheidingstekens kunnen gekozen worden.
'''date'''(outputformaat,datum,inputformaat[,outputscheiding][,inputscheiding])
De input- en outputformaten zijn zoals we ze hierboven al bestudeerden.
 
Nu zal de ''datum'' die als 2de parameter is meegegeven, en wiens formaat is bepaald door de derde parameter, in het ''outputformaat'' worden teruggegeven. Daarbij kan het scheidingsteken van de input omgezet worden naar het scheidingsteken dat als output gevraagd is. De datum kan in het verleden of de toekomst liggen.
 
Een moeilijke uitleg, daarom snel over naar voorbeelden om het te begrijpen:
Gisteren = date('Normal',date('B')-1,'B')
In de veronderstelling dat het vandaag 19 november 2011 is, dan mogen we terecht verwachten dat de functie aan ''Gisteren'' de waarde &#171;18 Nov 2011&#187; zal toekennen. Want zo werkt het:
*de eerste parameter zegt dat we de datum in het '''N'''ormaal formaat willen terugkrijgen;
*de tweede parameter is de datum die moet worden omgevormd. In dit geval gebruiken we '''date('B')''' om de basisdagen te krijgen. Daar kunnen we dan gemakkelijk één van aftrekken om naar gisteren over te gaan;
*de derde parameter maakt aan de functie duidelijk in welk formaat de tweede parameter is opgegeven, hier dus het B-formaat.
De functie zal dus de uitkomst van de tweede parameter intern omzetten naar het N-formaat.
Het voorbeeld onderstreept nogmaals het belang van het B-formaat als er moet gerekend worden. Dagen toevoegen of aftrekken van andere formaten kan niet altijd foutloos of is zelfs onmogelijk. '''date('N')-1''' zal bijvoorbeeld een ''bad arithmetic operation'' geven, want date('N') is geen getal.
 
Uiteraard kan de om te vormen datum uit een variabele gehaald worden. Laten we nog een voorbeeld bekijken:
sdate=19530422 /* Dit is een Sorted datum */
say 'Kris is geboren op' date('W',sdate,'S')',' date(,sdate,'S')
Het antwoord dat we hier mogen verwachten is &#171;Kris is geboren op Wednesday, 22 Apr 1953&#187;. Mooi is dat echter niet. Willen we het volledig in het Nederlands vertalen, dan moeten we wat meer moeite doen, bijvoorbeeld als volgt:
sdate=19530422 /* Dit is een Sorted datum */
dagen='maandag dinsdag woensdag donderdag vrijdag zaterdag zondag'
maanden='januari februari maart april mei juni',
'juli augustus september oktober november december'
basisdatum=date('B',sdate,'S') /* omzetten van sdate naar basisdatum */
/* Restdeling van basisdatum door 7 geeft een cijfer van 0 tot 6 */
/* waarbij 0 staat voor maandag en 6 voor een zondag */
dagnr=(basisdatum // 7)
dag = word(dagen,dagnr + 1) /* index mag niet nul zijn */
parse var sdate jjjj 5 mm 7 dd /* opsplitsen van sdate */
say 'Kris is geboren op' dag',' dd word(maanden,mm) jjjj
Nu wordt het resultaat &#171;Kris is geboren op vrijdag, 22 april 1953&#187;. We kunnen dat nog wat inkorten:
dag=word(dagen,date('B',sdate,'S')//7+1)
say 'Kris is geboren op' dag',' substr(sdate,7) word(maanden,substr(sdate,5,2)) left(sdate,4)
Om te besluiten geven we nog een aantal voorbeelden waarbij scheidingstekens worden gebruikt. De vierde parameter van de functie bepaalt welk teken in het resultaat moet verschijnen, en de vijfde parameter bepaalt welk teken er nu staat. Stel dat het nog steeds 19 november 2011 is, dan geven:
IsoDate = date('S',,,'-') &#171;2011-11-19&#187; (toevoegen scheidingsteken)
bdate = date('B',IsoDate,'S',,'-') &#171;734459&#187; (weglaten scheidingsteken)
mindate = date(,,,'-') &#171;19-Nov-2011&#187;(toevoegen scheidingsteken)
In het eerste geval voegen we mintekens toe aan een Sorted datum (jjjjmmdd). REXX weet waar het moet&nbsp;!
<br>In het tweede geval zetten we die IsoDate weer om naar een Basedate, waarbij we duidelijk maken dat de mintekens die in IsoDate zitten nu moeten verdwijnen (vierde parameter is nullstring).
<br>In het derde geval vragen we mintekens (vierde parameter) toe te voegen in de Normale datum, en weer weet REXX hoe het moet.
<!----------------------------------------------------------------------------------------------->
=TRANSLATE - karakters vertalen=
'''translate'''(string[,outputtabel][,inputtabel][,pad])
Translate is een zeer nuttige functie, maar wel ietwat moeilijk in gebruik, zeker voor beginnelingen. Toch willen we deze moeilijkheid niet uit de weg gaan. Dat we ze hier bespreken zal trouwens ook snel duidelijk worden.
 
In de eenvoudige vorm, waarbij enkel een ''string'' als parameter wordt meegegeven is het nog eenvoudig: de string wordt naar hoofdletters vertaald.
say translate('Zaterdag 17/12 is het feest') &#171;ZATERDAG 17/12 IS HET FEEST&#187;
say translate('Maandag fèteren we met paté') &#171;MAANDAG FèTEREN WE MET PATé&#187;
Uit deze voorbeelden volgt duidelijk dat enkel de normale karakters a tot z naar hoofdletters worden vertaald.
 
Willen we in het laatste voorbeeld ook de geäccentueerde karakters vertalen, dan moeten we dit schrijven:
say translate('Ik had een déjà vu ervaring','AE','àé') &#171;Ik had een dEjA vu ervaring&#187;
Als er een karakter uit de reeks die als derde parameter in de string voorkomt, dan moeten we hem vertalen naar het karakter dat op de overeenkomende plaats in de tweede reeks staat.
Voor elk karakter in de string gaat REXX na of het ook voorkomt in de derde parameter. Zo ja, dan wordt het corresponderende karakter uit de tweede parameter in de plaats gezet. Zoniet blijft het karakter onveranderd.
Dit verklaart waarom nu niet alles naar hoofdletters meer wordt vertaald.
 
De tweede en derde parameter kunnen dus als vertaaltabellen gezien worden. De kortste tabel wordt even lang gemaakt als de langste door er spaties aan toe te voegen.
 
Nog een voorbeeld:
zin="Dit is een volzin"
say translate(zin,'ABCDEFGHI_','abcdefghi ') &#171;DIt_Is_EEn_volzIn&#187;
Nu worden enkel de karakters van a tot i naar hoofdletters vertaald.
 
<p id="XRANGE">Willen we toch het hele alfabet vertalen, dan zouden we het ook volledig moeten uitschrijven. Het kan echter eenvoudiger door middel van de '''xrange''' functie. Deze functie retourneert alle karakters binnen een bereik.</p>
 
Dus nu kunnen we schrijven:
say translate('Ik had een déjà vu ervaring',xrange('A','Z')&#124;&#124;'AE',xrange('a','z')&#124;&#124;'àé')
om toch &#171;IK HAD EEN DEJA VU ERVARING&#187; te bekomen.
 
We zijn echter nog niet uitgepraat over de '''translate''' functie. Ze kan namelijk ook dienen om de karakters van de string op een andere plaats te brengen. Het wordt nu wel nog wat moeilijker om te begrijpen, dus zullen we beginnen met een eenvoudig voorbeeld:
translate('7890','ABCD','9870') &#171;CBAD&#187;
We bewerkstelligen dus het omkeren van de eerste drie karakters.
In tegenstelling tot het "gewone" gebruik van de '''translate''' functie, staat de string die we willen bewerken nu in parameter 2 i.p.v. 1. Maar de functie werkt nog steeds op de manier die we hoger hebben uitgelegd. We nemen het eerste karakter uit de eerste parameter, dus 7. Staat dit in de tabel van parameter 3&nbsp;? Ja, en wel op de derde plaats. Dus, we moeten die 7 vertalen in het derde karakter van de tweede parameter, dus C.
 
We doen hetzelfde voor elk van de karakters, dus 8 wordt B, 9 wordt A en 0 wordt D. Het resultaat is dus CBAD.
 
Dit voorbeeld was goed om het principe uit te leggen, maar van weinig praktisch nut. Laten we nu overgaan tot een meer zinvol gebruik van deze techniek. Daarbij gaan we datums van één formaat in een ander omzetten.
say translate('12345678','16-11-1949','12-34-5678') &#171;16111949&#187;
We hebben dus bereikt dat de mintekens uit de datum zijn verdwenen. Het volgende geeft exact hetzelfde resultaat, maar is véél minder leesbaar geworden:
say translate('abcdefgh','16-11-1949','ab-cd-efgh')
Als we toch met datums bezig zijn, dan wordt de nu volgende schrijfwijze nóg veel intuïtiever:
say translate('DdMmCcJj','16-11-1949','Dd-Mm-EeJj')
Door de karakters in de eerste en derde parameter zorgvuldig te kiezen verhogen we de leesbaarheid en zullen latere voorbeelden veel eenvoudiger te gebruiken zijn. De posities voor de dag stellen we voor d.m.v. "Dd", die voor maand door "Mm" en die voor het jaartal door "EeJj" (E voor eeuw). Niet "dd" want dan treedt er dubbelzinnigheid op zoals we hier kunnen zien:
say translate('ddmmccjj','16-11-1949','dd-mm-ccjj') &#171;11111144&#187;
say translate('ddmmccjj','16-11-1949','Dd-Mm-CcJj') &#171;66119999&#187;
say translate('DdMmCcJj','16-11-1949','dd-mm-ccjj') &#171;D1M1C1J4&#187;
Bij een zuivere permutatie, moeten de tekens van de eerste parameter uniek zijn, anders worden tekens uit tweede parameter meermaals herhaald.
 
In voorgaande voorbeelden haalden we tekens weg. Maar het is ook mogelijk tekens toe te voegen of te veranderen. In volgend voorbeeld maken we een ISO-datum (zie [[w:ISO_8601|ISO 8601]] voor meer informatie):
say translate('CcJj-Mm-Dd','16/11/1949','Dd/Mm/CcJj') &#171;1949-11-16&#187;
Hier is het belangrijk dat de toegevoegde tekens niet in het derde argument voorkomen.
 
Laten we de werking bij permutaties nog eens duidelijk formuleren:
*Parameter 1 bevat het gewenste formaat van het resultaat;
*Parameter 2 bevat de te permuteren gegevens;
*Parameter 3 beschrijft het formaat van de te permuteren gegevens.
Om te eindigen geven we een nog iets langer voorbeeld:
say translate('CcJjMmDd.HhNnSs','16/11/1949 12:14:03','Dd/Mm/CcJj Hh:Nn:Ss') &#171;19491116.121403&#187;
Daar we al "Mm" gebruiken moeten we voor de minuten van het uur spijtig genoeg iets anders kiezen om niet tot dubbelzinnigheid te leiden. Het werd dan maar "Nn".
 
Dit deeltje is zeker niet het gemakkelijkste uit het boek. Het is daarom goed om het nog eens snel te overlopen en na te gaan of alles is begrepen. Deze functie onder de knie krijgen zal dikwijls anders ellenlange logica kunnen vermijden.
 
Hiermee sluiten we het deel over tijdrekening af. We gaan nu verder de functies bestuderen waarmee we getallen kunnen bewerken.
<!----------------------------------------------------------------------------------------------->
=NUMERIC - nauwkeurigheid van bewerkingen bepalen=
Regel 195 ⟶ 28:
<br id=DIGITS>Noteer dat met de functie '''digits()''' de actuele cijfergrootte kan worden opgevraagd.</br>
 
Met '''numeric fuzz''' kunnen we bepalen tot op hoeveel decimalen de berekeningen moeten worden uitgevoerd. We bepalen hiermee m.a.w. de nauwkeurigheid van de berekeningen. De nauwkeurigheid moet kleiner zijn dan digits().
<br id=FUZZ>De actuele decimale nauwkeurigheid kan worden opgevraagd met de functie '''fuzz'''. De "numeric fuzz" kan lokaal voor een subroutine worden veranderd.
De fuzz-waardeen zal dus ook invloed hebben op eventuele afrondingen van getallen.</br>
 
Met '''numeric form''' tenslotte controleren we de vorm van de exponentiële notatie van getallen. We kunnen kiezen2 voorformaten het ''formaat''kiezen:
*'''scientific''', waarbij slechts één significant cijfer voor de komma geplaatst wordt. Dit is de standaard;
*'''engeneering''', waarbij in het exponentieel deel de macht steeds een veelvoud van 3 is.
Informatie afkomstig van https://nl.wikibooks.org Wikibooks NL.
Wikibooks NL is onderdeel van de wikimediafoundation.