12.6 An Example--Dynamic Records and Files

Dynamic memory is useful when working with records initially stored in a file because the number of records in the file is not known by the program ahead of time. Therefore, it may be worthwhile to allocate the memory for the items in the file as they are read, instead of declaring ahead of time an array of records large enough to hold all the data. The example below is a simple illustration of this idea. As in the sorting example of a previous section, the dynamic data is tracked by a collection of pointers to the data items held in an array and each item is sorted to its correct place in the list by manipulating the pointers in the array, not by reassigning any of the data elements themselves. As in that example, this is more efficient than keeping an array of the entire records, because memory reshuffling is limited to pointers, which take far fewer storage locations and therefore less time to swap or otherwise reassign.

Here, a small record consisting of people's names, initial, and age is kept in a file on the disk. For simplicity, it is supposed that the maximum number of these records is 101. The purpose this simple module serves is to read the items into memory, simultaneously sorting their pointers by the name in the record, and then printing out the entire list. The module also illustrates some elementary interactions between files and dynamic records, though this is by far the last word on the subject.

First a very simple module was written to create a data file. It has no dynamic data or pointers of any kind.

MODULE MakeRecords;

(* very crude data entry
to produce a file for the program GetPrintRecords 
by R. Sutcliffe -- modified 1995 05 12 *)

FROM STextIO IMPORT
  WriteString, ReadString, ReadChar, WriteLn, SkipLine;
FROM SWholeIO IMPORT
  ReadCard, WriteCard;
FROM RndFile IMPORT
  ChanId, OpenClean, OpenResults, Close, raw;
FROM RawIO IMPORT
  Write;
FROM SIOResult IMPORT
  ReadResult, ReadResults;

CONST
  max = 100;

TYPE 
  ShortAry = ARRAY [0..15] OF CHAR;
  RecordData =
    RECORD
      last, first: ShortAry;
      initial: CHAR;
      age: CARDINAL;
    END;

VAR
  person: RecordData;
  numActive : CARDINAL;
  outfile: ChanId;
  res : OpenResults;

BEGIN   (* main program *)
  OpenClean (outfile, "data", raw, res);
  IF res = opened
    THEN
      numActive := 0;
      LOOP
        WriteString ("reading record number");
        WriteCard (numActive +1, 5);
        WriteLn;
        WriteString ("last name?");
        ReadString (person.last);
        IF ReadResult () # allRight
          THEN
            EXIT
          END;   (* if *)
        SkipLine; WriteLn;
        WriteString ("first name?");
        ReadString (person.first);
        IF ReadResult () # allRight
          THEN
            EXIT
          END;   (* if *)
        SkipLine; WriteLn;
        WriteString ("initial?");
        ReadChar (person.initial);
        IF ReadResult () # allRight
          THEN
            EXIT
          END;   (* if *)
        SkipLine; WriteLn;
        WriteString ("age?");
        ReadCard (person.age);;
        IF ReadResult () # allRight
          THEN
            EXIT
          END;   (* if *)
        SkipLine;
        Write (outfile, person);
        INC (numActive);
      END; (* loop *)
      Close (outfile);
    ELSE
      WriteString ("could not open file");
    END (* initial if res *)
  END MakeRecords.

Next, the main module was produced to read, sort and print this data. To keep things simple so that the focus can be on the dynamic nature of the memory allocation error checking is primitive or non-existent. The sorting routine should be considered carefully, however. This particular one is called an insert sort, and will be discussed in detail in chapter 13, but something like it will be used in other places in the chapter. It works by examining each item in a sorted list and then inserting the new one in its appropriate place.

MODULE GetPrintRecords;

(* By R. Sutcliffe
to illustrate dynamic records
modified 1995 05 12 *)

FROM STextIO IMPORT
  WriteChar, WriteString, WriteLn;
FROM SWholeIO IMPORT
  WriteCard;
FROM RndFile IMPORT
  ChanId, OpenOld, OpenResults, Close, raw;
FROM RawIO IMPORT
  Read;
FROM IOResult IMPORT
  ReadResult, ReadResults;
FROM Storage IMPORT
  ALLOCATE;
FROM Strings IMPORT
  Compare, CompareResults;

CONST
  max = 100; (* maximum number of records *)

TYPE 
  ShortAry = ARRAY [0..15] OF CHAR;
  RecordData =
    RECORD
      last, first: ShortAry;
      initial: CHAR;
      age: CARDINAL;
    END;
  DataPoint = POINTER TO RecordData;
  ListType = ARRAY [1..max] OF DataPoint; (* pointers to records *)
VAR
  person: RecordData; (* one item for I/O *)
  list: ListType;
  count, numActive : CARDINAL;
  infile: ChanId;
  res : OpenResults;
  dataArrayFull : BOOLEAN;

PROCEDURE InitPointers;  (* Set all pointers in the list to NIL *)
VAR
  count: CARDINAL;
BEGIN
  FOR count := 1 TO max
    DO
      list [count] := NIL
    END
END InitPointers;

PROCEDURE SortOneIn (name: RecordData);
VAR
  moveCount, count: CARDINAL;
BEGIN
  IF numActive = 0   (* perhaps this is the first one *)
    THEN
      INC (numActive);  (* get it all started *)
      NEW (list [1]); (* get memory *)
      list [1]^ := name; (* and occupy it *)
    ELSIF numActive = max THEN (* no room *)
      dataArrayFull := TRUE;
      RETURN;
    ELSE (* add this one to the array *)
      count := 0;
      REPEAT
        INC (count);
      UNTIL (count > numActive)
          OR NOT (Compare (name.last, list [count]^.last) = greater);
      (* Now move old ones forward in list to insert *)
      FOR moveCount := numActive + 1 TO count + 1 BY - 1
        DO
          list [moveCount] := list [moveCount - 1]
        END;
      NEW (list [count]); (* new value at this spot *)
      list [count]^ := name; (* occupy the memory *)
      INC (numActive)
    END;
  END SortOneIn;

BEGIN   (* main program *)
  dataArrayFull := FALSE;
  InitPointers;
  OpenOld (infile, "data", raw, res);
  IF res = opened
    THEN
      numActive := 0;
      LOOP
        Read (infile, person);
        IF ReadResult (infile) # allRight
          THEN
            EXIT
          END;   (* if *)
        SortOneIn (person);
        IF dataArrayFull
          THEN
            WriteString ("Could not read entire file");
            WriteLn;
            EXIT;
          END;    (* if *)
      END; (* loop *)
    Close (infile); 
  (* now ready to print it out *)
    FOR count := 1 TO numActive
      DO
        WITH list [count]^
          DO
            WriteString (first);
            WriteString (' ');
            WriteChar (initial);
            WriteString ('. ');
            WriteString (last);
            WriteString (' is ');
            WriteCard (age, 3);
            WriteString (' years old.');
            WriteLn;
          END (* with *)
      END; (* for *)
    END (* if res *)
END GetPrintRecords.

Examples of this general nature will be revisited and improved on substantially from time to time in later sections, but for now, the reader will be invited to incorporate at least some cosmetic improvements in the exercises. One set of output from this latter module, after entering some data for a file with the first one, was:

jim t. babchuk is  56 years old.
ernest y. borgnine is  89 years old.
catherine m. davis is  15 years old.
dawn t. moore is  47 years old.
garth r. moorehead is  34 years old.
tom r. rowan is  26 years old.
william r. shakespeare is 390 years old.
joel r. sutcliffe is  15 years old.
nathan p. sutcliffe is  15 years old.

Contents