Programmeren in REXX/Voorbeeld1
Nu hebben we genoeg elementen om een eerste werkend REXX programma te bestuderen. Het zal de priemgetallen tussen 1 en 100 opzoeken.
Eigenlijk moeten we voor elk getal tussen 1 en 100 nagaan of het deelbaar is door een van de priemgetallen kleiner dan zichzelf. Maar er zijn een aantal verbeteringen mogelijk aan deze werkwijze, waardoor het programma ettelijke malen sneller zal draaien.
- Priemgetallen zijn onpare getallen, met slechts één uitzondering, het getal 2. Pare getallen onderzoeken is dus al nodeloos. We gaan ervan uit dat we intuïtief al weten dat 2 een priemgetal is en dat testen we dus ook niet meer.
- Stel dat we moeten onderzoeken of 23 een priemgetal is. Priemgetallen kleiner dan 23 zijn (2,) 3, 5, 7, 11, 13 en 17. Ons getal 23 is geen veelvoud van 3. Maar moeten we nog testen of het een veelvoud van 5 is ? Een decimale deling door 5 geeft: 4.6. Dus, 23 is alvast niet geheel deelbaar door 5, en het resultaat is zelfs al kleiner dan 5. Er zal dus zeker geen ander nog groter priemgetal te vinden zijn dat een deler is van 23. Kortom, eens het kwadraat van een priemgetal groter is dan het te onderzoeken getal, weten we dat er geen delers meer zullen gevonden worden. Ons getal is dan zelf een priemgetal en de lus kan worden gestopt.
Laten we nu ons programma bestuderen.
Code
bewerken1 | /* Berekenen priemgetallen tussen 1 en 100 */ 2 | start=3 /* De start van onze zoektocht */ 3 | einde=100 /* Het einde van onze zoektocht*/ 4 | priem.0=1 /* Definitie van stem en teller*/ 5 | priem.1=2 /* Ons eerste priemgetal kennen we */ 6 | z=1 /* onze priemgetallenteller */ 7 | say priem.1 /* We tonen ons eerste priemgetal */ 8 | do i=start to einde by 2 /* lus van start tot einde, per 2 */ 9 | do j=2 to z /* lus over al gevonden priemgetallen */ 10 | if i<priem.j**2 then leave j /* groter dan priem**2 ? ==> verlaat lus j */ 11 | if i//priem.j=0 then iterate i /* als rest i/priem nul is, volgende zoeken */ 12 | end j /* einde van lus j */ 13 | z=z+1 /* 1 bij aan priemgetallenteller */ 14 | priem.z=i /* toevoegen aan reeks */ 15 | priem.0=z /* Element nul van stem aanpassen */ 16 | say i /* we tonen het gevonden priemgetal */ 17 | end i /* einde van lus i */ 18 | say 'We hebben' z 'priemgetallen gevonden tussen' start 'en' einde 19 | exit /* einde van het programma */
In de eerste kolom hebben we lijnnummers gezet om naar te kunnen verwijzen. Ze maken geen deel uit van het programma.
Bespreking
bewerkenLijn 1: alhoewel een REXX programma slechts op sommige platformen met een commentaar moet starten is het toch een goede gewoonte hier op z'n minst te schrijven wat het programma doet. Er kan ook een auteur, creatiedatum en zelfs historiek aan toegevoegd worden.
Lijn 2: we definiëren de variabele start en geven ze de waarde 3 (onze effectieve start na het "gekende" priemgetal 2).
Lijn 3: we definiëren de variabele einde en geven ze de waarde 100, omdat we ons nu willen beperken tot de priemgetallen tot 100.
Lijn 4: in één enkele operatie creëren we een stem, priem. genaamd, en geven aan het element priem.0 de waarde 1. De conventie wil namelijk dat we in het "nulde" element van een stem (array) bijhouden hoeveel elementen er in de stem zitten, en op
Lijn 5 vullen we nu net dat eerste element van de stem op, namelijk ons eerste priemgetal 2.
Lijn 6: we definiëren de variabele z die zal dienen om de index van onze stem te verhogen (en tevens het aantal gevonden priemgetallen).
Lijn 7: we mogen niet vergeten ons eerste priemgetal 2 ook op het scherm te tonen!
Lijn 8: nu starten we een lus. De variabele die als teller gebruikt wordt is i. De naam van deze variabele kan vrij gekozen worden, maar het is ietwat historisch dat variabelen i, j, k, l als eerste worden gekozen omdat in oorspronkelijke programmeertalen deze variabelen impliciet gehele getallen voorstelden. Een langere naam zou ook alleen meer schrijfwerk betekenen.
We gaan in de lus blijven vanaf onze startwaarde (3) tot onze eindwaarde (100). En om enkel de onpare getallen te onderzoeken maken we de stap 2 groot.
Lijn 9: binnen de eerste lus beginnen we nu een tweede lus. We gaan (dank zij lusteller j) alle reeds gekende priemgetallen af. Vermits we enkel onpare getallen onderzoeken moeten we echter ons allereerste priemgetal 2 niet meenemen.
Merk hier op dat we de eerste maal een lus-definitie zullen hebben die resulteert in do j=2 to 1. Dit is helemaal OK voor REXX, en maakt dat de lus niet zal worden uitgevoerd.
Lijn 10: als nu blijkt dat het getal dat we onderzoeken (en dat in variabele i staat) kleiner is dan het kwadraat van het j-de priemgetal, dan kunnen we de j-lus verlaten, en hebben we een nieuw priemgetal gevonden.
Lijn 11: hier testen we of het te onderzoeken getal (i) deelbaar is door het j-de priemgetal. We testen dit door na te gaan of de restdeling op nul uitkomt. Indien zo, dan hebben we geen priemgetal en kunnen we onmiddellijk naar het volgende te onderzoeken getal gaan, dus de rest van de i-lus overslaan en terug naar lijn 8 springen.
Lijn 12 geeft het einde van de binnenste lus aan. Alhoewel niet nodig, is het een goede gewoonte de lusteller (j) als parameter mee te geven. Zeker bij heel lange lussen maakt het het foutonderzoek gemakkelijker.
Lijn 13: als we hier zijn terechtgekomen, dan hebben we een priemgetal gevonden. We verhogen onze teller, zodat we op
lijn 14 ons nieuw priemgetal kunnen toevoegen aan het z-de element van onze stem.
Lijn 15: en vergeten ook niet ons element 0 aan te passen. We kunnen hiermee zelfs beter wachten tot na de i-lus, zo winnen we aan snelheid. Maar lees ook de
opmerkingen hieronder.
Lijn 16: we tonen het gevonden priemgetal op het scherm en kunnen het volgende getal gaan onderzoeken.
Lijn 18: we hebben nu alle getallen van 1 tot 100 onderzocht. Zowel variabele z als stem.0 bevatten het aantal gevonden priemgetallen, dus een kleine moeite om dat aan de gebruiker te melden.
Lijn 19: we beëindigen het programma. We geven geen returncode mee, dus die zal steeds 0 zijn. In dit eenvoudig programmaatje is die exit eigenlijk zelfs niet écht nodig, maar het blijft toch een goede gewoonte er een te schrijven. Op een PC zal die returncode trouwens enkel zin hebben indien ons programma door een ander programma is opgeroepen, in welk geval de returncode kan getest worden. Windows zelf doet niets met
de returncode van het programma.
Extra opmerkingen
bewerkenDe variabele start hebben we niet echt nodig. We kunnen op lijn 8 ook do i=3 to einde by 2 schrijven. Maar we gaan dit programma later nog uitbreiden en dan zal die variabele van pas komen.
Het stem-element priem.0 moeten we niet opvullen, zodat lijnen 4 en 15 ook mogen wegvallen. Het is echter een conventie van het aantal elementen van de stem hierin bij te houden. Sommige functies of programma's vullen deze variabele ook automatisch in.
Merk op dat we lijn 14 niet mogen schrijven als:
priem.priem.0=i
want REXX gaat dan op zoek naar... priem.PRIEM.0 (het element PRIEM.0 van de stem priem.) en dat bestaat niet. Vandaar dat we variabele z als tussenstap gebruiken. Het gevolg is wel dat zowel z als priem.0 dezelfde waarden zullen bevatten.
De Object REXX en Reginald REXX implementaties laten echter wel toe het op volgende manier op te lossen:
priem.[priem.0]=i
Tussen de vierkante haakjes kunnen een complexe variabele of zelfs bewerkingen geschreven worden. Deze vorm is echter niet in alle REXX implementaties gekend, noch in de officiële ANSI J18-199X definities. We zullen deze oplossing verder vermijden om algemener te blijven.
Als eerste voorbeeld van de kracht van de PARSE instructie kunnen we melden dat de regels 2 tot 6 kunnen vervangen worden door
parse value 3 100 1 2 1 with start einde priem.0 priem.1 z
Vermits we hier een reeks tokens willen parsen mogen we het sleutelwoord with niet vergeten, anders is onduidelijk waar het sjabloon begint.
Het sjabloon is hier in zijn meest eenvoudige vorm, namelijk een reeks tokens, die variabelen zullen worden. De eerste token van het sjabloon (start) zal het eerste woord van de bron (3) ontvangen. En zo verder. Dus, met één regel kennen we een initiële waarde aan 5 variabelen toe.
En, het is niet alleen een kortere schrijfwijze, het kan de uitvoeringssnelheid ook ten goede komen! Het aantal interpretatiestappen wordt hierdoor namelijk met 4 verminderd, al is de interpretatie van deze ene lijn wel complexer en dus iets meer tijdrovend.
Desgevallend kan de leesbaarheid als volgt verbeterd worden:
parse value 3 100 1 2 1, with start einde priem.0 priem.1 z
Of er nu één of meerdere blanco karakters tussen de woorden van de bron staan doet er in dit geval niet toe. Op deze manier wordt duidelijk welke waarde naar welke variabele gaat. Vergeet hierbij niet de vervolg-komma te schrijven op de eerste lijn, want het moet één instructie blijven.
Op dezelfde manier zouden we lijnen 13 tot 15 kunnen schrijven als:
parse value z+1 i with z . 1 priem.0 priem.z
doch dit is voer voor later, als we de parse instructie nog wat meer hebben bestudeerd.