Programmeren in COBOL/Relatieve bestanden met hash

Programmeren in COBOL

Inhoudsopgave

  1. Sequentiële bestanden Zeer goed ontwikkeld. Revisiedatum: 21 oktober 2007 (Oef Redelijk ontwikkeld. Revisiedatum: 21 oktober 2007 )
  2. Relatieve bestanden zonder hash Zeer goed ontwikkeld. Revisiedatum: 20 november 2007 (Oef Nog vrijwel niets. Revisiedatum: 20 november 2007 )
  3. Relatieve bestanden met hash Goed ontwikkeld. Revisiedatum: 21 oktober 2007 (Oef Nog vrijwel niets. Revisiedatum: 23 november 2007 )
  4. Index-sequentiële bestanden Nog vrijwel niets. Revisiedatum: 21 oktober 2007 (Oef Nog vrijwel niets. Revisiedatum: 20 november 2007 )




Relatieve bestanden kunnen via een sleutel makkelijk een record terugvinden. Dit kan via een cijfer (van 1 tot n). Maar een cijfer is niet altijd handig, en vaak weinig informatief. Een naam of een beschrijvend woord zouden makkelijker zijn. COBOL laat het gebruik van namen toe via een omweg: een hash-functie zet de naam om in een cijfer waarmee je makkelijk het record kunt terugvinden. Het grote nadeel van een relatief bestand met hash is dat je op voorhand moet weten hoeveel records je maximaal zult hebben. Als je eerst 10 records wou hebben en je maakt dat bestand aan, moet je wanneer je plotseling 20 records wilt opslaan een nieuw relatief bestand aanmaken.

Relatief bestand met hash aanmaken

bewerken

Zoals een gewoon relatief bestand moeten we vertrekken vanuit een sequentieel bestand. We lezen dus eerst een sequentieel bestand in, en maken daarvan een relatief bestand met hash.

COBOL-code: Het sequentiële bestand.

Wikipedia   De vrije encyclopedie
Wiktionary  Vrij woordenboek met definities en uitleg
Wikimedia   Website over de projecten van Wikimedia
Wikiquote   Verzameling citaten
Wikibooks   Handleidingen en vrije boeken
Wikinews    Vrije nieuwsbron
Wikisource  Documenten vanuit publiek domein
Wikispecies Catalogus van alle soorten
Commons     Vrije mediabestanden

Dit bestand gaan we omzetten in een relatief bestand. Het bevat de namen van wiki's met hun omschrijvingen. De namen van de wiki's zullen we gebruiken als sleutel. In het relatief bestand zal zowel de naam als de omschrijving van de wiki staan. Hieronder is een voorbeeld van een programma dat dit voor ons zal doen.

COBOL-code: Het programma dat het relatief bestand zal aanmaken.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. Wiki.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT wikiseq ASSIGN TO "wikiseq.txt"
             ORGANIZATION LINE SEQUENTIAL
             FILE STATUS seqstatus.

           SELECT wikirel ASSIGN TO "wikirel.txt"
             ORGANIZATION RELATIVE
             ACCESS RANDOM
             RELATIVE KEY nummer
             FILE STATUS relstatus.

       DATA DIVISION.
       FILE SECTION.
       FD wikiseq.
       01 seqwiki.
        02 seqnaam PIC X(11).
        02 PIC X.
        02 seqomschr PIC X(50).
       
       FD wikirel.
       01 relwiki.
        02 relnaam PIC X(11).
        02 relomschr PIC X(50).

       WORKING-STORAGE SECTION.
       01 seqstatus PIC XX.
        88 eofseq VALUE '10'.
       01 relstatus PIC XX.
        88 eofrel VALUE '10'.
        88 slechtesleutel VALUE '23'.

       01 nummer PIC 99.
       01 wikinaam PIC X(11).
       01 wikinummer PIC 9(11).
       01 kleineletters PIC X(27) VALUE 'abcdefghijklmnopqrstuvwxyz '.
       01 hoofdletters PIC X(27) VALUE 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '.
       01 cijfers PIC X(27) VALUE '012345678901234567890123456'.
       01 teller PIC 99.

       PROCEDURE DIVISION.
       HOOFD.
           OPEN OUTPUT wikirel
           CLOSE wikirel
           OPEN INPUT wikiseq I-O wikirel
           READ wikiseq NEXT AT END CONTINUE END-READ
           PERFORM omzetten UNTIL eofseq
           CLOSE wikiseq wikirel
           STOP RUN.

       OMZETTEN.
           PERFORM zoeken
           IF slechtesleutel 
             THEN 
                  MOVE seqnaam TO relnaam
                  MOVE seqomschr TO relomschr
                  WRITE relwiki
           END-IF
           READ wikiseq NEXT AT END CONTINUE END-READ.

       ZOEKEN.
           PERFORM hash
           READ wikirel INVALID KEY CONTINUE END-READ
           PERFORM VARYING teller FROM 1 BY 1 UNTIL slechtesleutel
           OR seqnaam = relnaam OR teller > 9
             PERFORM botsing
             READ wikirel INVALID KEY CONTINUE END-READ
           END-PERFORM.

       HASH.
           MOVE seqnaam TO wikinaam
           INSPECT wikinaam CONVERTING kleineletters to hoofdletters
           INSPECT wikinaam CONVERTING hoofdletters TO cijfers
           DIVIDE wikinummer BY 9 GIVING wikinummer REMAINDER nummer
           ADD 1 TO nummer.

       BOTSING.
           ADD 1 TO nummer.
           IF nummer = 10
             MOVE 1 TO nummer
           END-IF.

Een relatief bestand aanmaken met hash kan zeer moeilijk zijn om te doen en te begrijpen. Nu gaan we stap voor stap voor stap uitleggen wat dit programma doet.

COBOL-code: De bestanden benaderen.

           SELECT wikiseq ASSIGN TO "wikiseq.txt"
             ORGANIZATION LINE SEQUENTIAL
             FILE STATUS seqstatus.

           SELECT wikirel ASSIGN TO "wikirel.txt"
             ORGANIZATION RELATIVE
             ACCESS RANDOM
             RELATIVE KEY nummer
             FILE STATUS relstatus.

Als u de twee vorige hoofdstukken hebt gelezen dan zal dit niet moeilijk zijn om te begrijpen. wikiseq is het bestand dat hier helemaal bovenaan werd beschreven en we zullen gebruiken om een relatieve bestand met hashfunctie aan te maken. wikirel zal natuurlijk dat bestand zijn, bij dit bestand is er wel iets waar je voor moet opletten. In het vorige hoofdstuk Relatieve bestanden zonder hash gebruikten we ACCESS SEQUENTIAL maar door de hash-functie moeten we dit bestand met een RANDOM benaderen, hier gaan we later nog op in.

COBOL-code: Hoofd.

       HOOFD.
           OPEN OUTPUT wikirel
           CLOSE wikirel
           OPEN INPUT wikiseq I-O wikirel
           READ wikiseq NEXT AT END CONTINUE END-READ
           PERFORM omzetten UNTIL eofseq

In het begin moeten we natuurlijk de bestanden openen, het relatief bestand wikirel moet als I-O(INPUT-OUTPUT) worden gelezen want er wordt in dit bestand zowel geschreven als gelezen. Waarom wordt ook later uitgelegd. Maar we moeten echter eerst een keer die relatief bestand openen als OUTPUT, dit zorgt ervoor dat het bestand wordt aangemaakt als het nog niet bestaat en voorkomt dat er fouten zijn als we het vervolgens openen met I-O. We lezen vervolgens het eerste lijntje in van het sequentiële bestand. Om vervolgens naar het deelprogramma omzetten te gaan als het op het einde van het sequentiële bestand is zal het deelprogramma stoppen.

Als we in omzetten zijn aangekomen, gaan we meteen naar het deelprogramma zoeken en van daaruit meteen naar de hash-functie.

COBOL-code: Hash-functie

       HASH.
           MOVE seqnaam TO wikinaam
           INSPECT wikinaam CONVERTING kleineletters to hoofdletters
           INSPECT wikinaam CONVERTING hoofdletters TO cijfers
           MOVE wikinaam TO wikinummer
           DIVIDE wikinummer BY 9 GIVING wikinummer REMAINDER nummer
           ADD 1 TO nummer.

Dit is waar alles om draait, dit klein stukje code zorgt ervoor dat je via een woord of een naam een record zult kunnen plaatsen of vinden. We slaan eerst de naam van de wiki op in een hulpveld. Met dit hulpveld zullen we de hash-functie gebruiken. Eerst zorgen we ervoor dat alles in hoofdletters staat via de INSPECT(zie Werken met tekst). Als dat gebeurd is moeten we alles omzetten in een cijfer; dit doen we ook via INSPECT. Wikinaam is nog steeds een alfanumeriek veld en hiermee kan je niet rekenen. Dit moeten we eerst in een numeriek veld overzetten. Het getal dat we nu hebben is 11 tekens lang, goed voor 100 miljard mogelijke combinaties. Dit is natuurlijk teveel van het goede we hebben maar 9 records die we willen opslaan. De oplossing is het getal delen door het aantal records dat we willen opslaan. De uitkomst ervan interesseert ons niet want dat is nog steeds meer dan 10 miljard. Wat ons wel interesseert is de restwaarde, dat zal een getal zijn van 0 tot 8. Dat zijn 9 verschillende combinaties. Maar aangezien COBOL niet zero-based is in vergelijking met bijvoorbeeld Java moeten we er één bij optellen. Dit geeft ons dan een getal van 1 tot 9 en slaan we op in het veld dat dient als RELATIVE KEY, in dit geval nummer. Vervolgens gaan we terug naar het deelprogramma zoeken.

Opmerking: Hoe je precies aan het eindgetal komt maakt eigenlijk niet uit zolang je maar aan een getal komt dat gebaseerd is op de naam of het woord en dat genoeg kans loopt om te verschillen met de getallen van andere namen of woorden.

COBOL-code: Zoeken.

       ZOEKEN.
           PERFORM hash
           READ wikirel INVALID KEY CONTINUE END-READ
           PERFORM VARYING teller FROM 1 BY 1 UNTIL slechtesleutel
           OR seqnaam = relnaam OR teller > 9
             PERFORM botsing
             READ wikirel INVALID KEY CONTINUE END-READ
           END-PERFORM.

De hash functie is juist gebeurd. We hebben dus een sleutel en we lezen vervolgens het relatief bestand. Dit is meteen de reden waarom we het geopend hebben als I-O: we moeten het bestand ook lezen omdat we moeten zien of er niet al een record op die plaats is. Als er geen record is op die plaats dan krijgen we een INVALID KEY, hier slechtesleutel genaamd. INVALID KEY betekent hier ironisch genoeg dat het goed is want op de plaats van onze sleutel is er geen record dus kunnen we daar een record wegschrijven. Maar wat als er wel een record is? Hiervoor zijn drie mogelijke oorzaken: ofwel heeft toevallig een ander record dezelfde sleutel gekregen van de hashfunctie of hebben we het record al een keer weggeschreven of we hebben al 9 records weggeschreven. Aan de twee laatste kunnen we niet veel doen als het record al weggeschreven is maakt het niet veel uit en als we al 9 records hebben weggeschreven moeten we het programma aanpassen zodat we toch meer dan 9 records kunnen gebruiken. Als er toevallig een ander record dezelfde sleutel heeft gekregen noemen we dat een botsing of collision. Dit is niet erg en het gebeurt vrijwel altijd. Het simpelste wat we kunnen doen om het op te lossen is gewoon één bijtellen bij de sleutel en het opnieuw te proberen, dit gebeurt in het deelprogramma botsing. Het is bijna gedaan want nu gaan we al terug naar het deelprogramma omzetten.

COBOL-code: Omzetten.

       OMZETTEN.
           PERFORM zoeken
           IF slechtesleutel 
             THEN 
                  MOVE seqnaam TO relnaam
                  MOVE seqomschr TO relomschr
                  WRITE relwiki
           END-IF
           READ wikiseq NEXT AT END CONTINUE END-READ.

Onthoud INVALID KEY betekende in dit geval goed en daardoor is het veld slechtesleutel geactiveerd. Vervolgens gebeurt net hetzelfde als bij een gewoon relatief bestand. Schrijf alles weg en lees het volgende record van het sequentiële bestand. Als alle records zijn gelezen sluit de twee bestanden en het programma heeft zijn werk gedaan.

Relatief bestand met hash lezen

bewerken

Als je een relatief bestand met hash aanmaken onder de knie hebt moet dit niet zo moeilijk meer zijn. Jammer genoeg als je zeker wilt weten of je zo'n bestand perfect hebt kunnen maken moet je eerst ze ook kunnen lezen. Dit volgend programma leest dat bestand van het vorige stukje in. De gebruiker geeft de naam van de wiki en het programma geeft zijn omschrijving.

COBOL-code: Een relatief bestand met hash lezen.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. Wiki.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

           SELECT wikirel ASSIGN TO "wikirel.txt"
             ORGANIZATION RELATIVE
             ACCESS RANDOM
             RELATIVE KEY nummer.

       DATA DIVISION.
       FILE SECTION.  
       FD wikirel.
       01 relwiki.
        02 relnaam PIC X(11).
        02 relomschr PIC X(50).

       WORKING-STORAGE SECTION.
       01 nummer PIC 99.
       01 wikinaam PIC X(11).
       01 hulp PIC x(11).
       01 wikinummer PIC 9(11).
       01 kleineletters PIC X(27) VALUE 'abcdefghijklmnopqrstuvwxyz '.
       01 hoofdletters PIC X(27) VALUE 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '.
       01 cijfers PIC X(27) VALUE '012345678901234567890123456'.
       01 teller PIC 99.

       PROCEDURE DIVISION.
       HOOFD.
           OPEN INPUT wikirel
           ACCEPT wikinaam NO BEEP
           PERFORM zoeken
           CLOSE wikirel
           STOP RUN.

       ZOEKEN.
           PERFORM hash
           PERFORM varying teller FROM 1 BY 1 UNTIL teller > 9
             READ wikirel END-READ
             IF relnaam = wikinaam
               THEN
                 DISPLAY relomschr
                 MOVE 10 TO teller
               ELSE 
                 PERFORM botsing
             END-IF
           END-PERFORM.

       HASH.
           MOVE wikinaam TO hulp
           INSPECT hulp CONVERTING kleineletters to hoofdletters
           INSPECT hulp CONVERTING hoofdletters TO cijfers
           MOVE hulp TO wikinummer
           DIVIDE wikinummer BY 9 GIVING wikinummer REMAINDER nummer
           ADD 1 TO nummer.

       BOTSING.
           ADD 1 TO nummer.
           IF nummer = 10
             MOVE 1 TO nummer
           END-IF.

De hash-functie en botsingzijn in dit programma nogsteeds hetzelfde, dus het enige waar je moet voor opletten is de hoofd en zoeken.

COBOL-code: hoofd.

       HOOFD.
           OPEN INPUT wikirel
           ACCEPT wikinaam NO BEEP
           PERFORM zoeken
           CLOSE wikirel
           STOP RUN.

De hoofd is niet echt moeilijk. Je moet het relatief gewoon open als input en vervolgens lees je de naam van de wiki in waarvan je de omschrijving wilt hebben. Vervolgens voer je zoeken uit.

COBOL-code: zoeken.

       ZOEKEN.
           PERFORM hash
           PERFORM varying teller FROM 1 BY 1 UNTIL teller > 9
             READ wikirel END-READ
             IF relnaam = wikinaam
               THEN
                 DISPLAY relomschr
                 MOVE 10 TO teller
               ELSE 
                 PERFORM botsing
             END-IF
           END-PERFORM.

Zoeken is het enige waar je misschien nog moet opletten. Eers voor je natuurlijk de hash-functie uit en die geeft dan een sleutel. Vervolgens start je met een lus die maximaal het aantal records dat er in het bestand zit zal draaien. Je voert een READ uit met de sleutel die je hebt van de hash-functie. Nu moeten we eerst controleren of het wel het juist record is. Dit doen we door de naam da we hebben gekregen van de gebruiker te verglijken met dat van de record. Als het juist is toont dit programma gewoon de omschrijving van de wiki. Als het fout is moet botsing worden uitgevoerd tot het record is gevonden.

Record herschrijven

bewerken

Een record herschrijven is niet moeilijk en je kunt de code van het vorige stukje gebruiken en gewoon een beetje aanpassen.

COBOL-code: Record herschrijven.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. Wiki.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

           SELECT wikirel ASSIGN TO "wikirel.txt"
             ORGANIZATION RELATIVE
             ACCESS RANDOM
             RELATIVE KEY nummer.

       DATA DIVISION.
       FILE SECTION.  
       FD wikirel.
       01 relwiki.
        02 relnaam PIC X(11).
        02 relomschr PIC X(50).

       WORKING-STORAGE SECTION.
       01 nummer PIC 99.
       01 wikinaam PIC X(11).
       01 hulp PIC x(11).
       01 wikinummer PIC 9(11).
       01 kleineletters PIC X(27) VALUE 'abcdefghijklmnopqrstuvwxyz '.
       01 hoofdletters PIC X(27) VALUE 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '.
       01 cijfers PIC X(27) VALUE '012345678901234567890123456'.
       01 teller PIC 99.

       PROCEDURE DIVISION.
       HOOFD.
           OPEN I-O wikirel
           DISPLAY "Geef de naam van de wiki die je wilt aanpassen:"
           ACCEPT wikinaam NO BEEP
           PERFORM zoeken
           CLOSE wikirel
           STOP RUN.

       ZOEKEN.
           PERFORM hash
           PERFORM varying teller FROM 1 BY 1 UNTIL teller > 9
             READ wikirel END-READ
             IF relnaam = wikinaam
               THEN
                 DISPLAY "Het originele inhoud: " relomschr
                 DISPLAY "Geef de nieuwe inhoud: "
                 ACCEPT relomschr NO BEEP
                 REWRITE relwiki END-REWRITE
                 MOVE 10 TO teller
               ELSE 
                 PERFORM botsing
             END-IF
           END-PERFORM.

       HASH.
           MOVE wikinaam TO hulp
           INSPECT hulp CONVERTING kleineletters to hoofdletters
           INSPECT hulp CONVERTING hoofdletters TO cijfers
           MOVE hulp TO wikinummer
           DIVIDE wikinummer BY 9 GIVING wikinummer REMAINDER nummer
           ADD 1 TO nummer.

       BOTSING.
           ADD 1 TO nummer.
           IF nummer = 10
             MOVE 1 TO nummer
           END-IF.

Niet zoveel verschil dus. Er zijn slechts twee zaken die echt anders zijn. Ten eerste hoe je het bestand moeten openen. Net zoals een gewoon relatief bestand moet je dit openen met I-O(INPUT-OUTPUT). Vervolgens doe je nadat je het record hebt gevonden die je wilt aanpassen een REWRITE net zoals een gewoon relatief bestand. Hieronder is er een schermafdruk van dit programma.

Geef de naam van de wiki die je wilt aanpassen:
Wikipedia
Het originele inhoud: De vrije encyclopedie
Geef de nieuwe inhoud:
VRIJE ENCYCLOPEDIE

Als je dan opnieuw wikipedia opzoekt zal je zien dat er "VRIJ ENCYCLOPEDIE" staat.

Record verwijderen

bewerken

Dit volgende programma is gebaseerd op het vorige maar in de plaats van een record te herschrijven wordt die gedelete.

COBOL-code: Record verwijderen.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. RecordVerwijderen.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT wikirel ASSIGN TO "wikirel.txt"
             ORGANIZATION RELATIVE
             ACCESS RANDOM
             RELATIVE KEY nummer.

       DATA DIVISION.
       FILE SECTION.  
       FD wikirel.
       01 relwiki.
        02 relnaam PIC X(11).
        02 relomschr PIC X(50).

       WORKING-STORAGE SECTION.
       01 nummer PIC 99.
       01 wikinaam PIC X(11).
       01 hulp PIC x(11).
       01 wikinummer PIC 9(11).
       01 kleineletters PIC X(27) VALUE 'abcdefghijklmnopqrstuvwxyz '.
       01 hoofdletters PIC X(27) VALUE 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '.
       01 cijfers PIC X(27) VALUE '012345678901234567890123456'.
       01 teller PIC 99.

       PROCEDURE DIVISION.
       HOOFD.
           OPEN I-O wikirel
           DISPLAY "Geef de naam van de wiki die je wilt verwijderen:"
           ACCEPT wikinaam NO BEEP
           PERFORM zoeken
           CLOSE wikirel
           STOP RUN.

       ZOEKEN.
           PERFORM hash
           PERFORM varying teller FROM 1 BY 1 UNTIL teller > 9
             READ wikirel END-READ
             IF relnaam = wikinaam
               THEN
                 DELETE wikirel
                 DISPLAY "Record verwijderd"
                 MOVE 10 TO teller
               ELSE 
                 PERFORM botsing
             END-IF
           END-PERFORM.

       HASH.
           MOVE wikinaam TO hulp
           INSPECT hulp CONVERTING kleineletters to hoofdletters
           INSPECT hulp CONVERTING hoofdletters TO cijfers
           MOVE hulp TO wikinummer
           DIVIDE wikinummer BY 9 GIVING wikinummer REMAINDER nummer
           ADD 1 TO nummer.

       BOTSING.
           ADD 1 TO nummer.
           IF nummer = 10
             MOVE 1 TO nummer
           END-IF.

Niet zo veel verschil met het vorige. DELETE in de plaats van REWRITE en natuurlijk altijd openen met I-O(INPUT-OUTPUT). Als je een record wilt lezen die je hebt gedelete geeft hij een foutmelding.

Informatie afkomstig van https://nl.wikibooks.org Wikibooks NL.
Wikibooks NL is onderdeel van de wikimediafoundation.