home *** CD-ROM | disk | FTP | other *** search
/ RBBS in a Box Volume 1 #3.1 / RBBSIABOX31.cdr / magn / ltut2.pas < prev    next >
Pascal/Delphi Source File  |  1990-09-29  |  32KB  |  775 lines

  1. {
  2.                       FUNCTIONS AND PROCEDURES
  3.                      FOR CREATING AND ACCESSING
  4.                       LINKED LISTS IN HEAPSPACE
  5.  
  6.                      GARRY J. VASS  [72307,3311]
  7.  
  8.  
  9. This file is a stand-alone TPascal program that attempts to provide
  10. a step-by-step demo of the creation and management of a linked list
  11. in heapspace.  Comments, revisions, corrections, questions, and
  12. suggestions should be passed on to my SIG at the POLICE STATION BSS
  13. (201-866-7902).
  14.  
  15.  
  16.  
  17. INTRODUCTION
  18. ------------
  19.  
  20.   Why bother with linked lists?  Why not just stick everything in an
  21.    array and keep track it that way?  Link lists are undoubtedly one
  22.    of the most arcane things Turbo has to offer; consider the problems
  23.    in learning linked lists:
  24.  
  25.                1.  The variables do not really have names in any
  26.                    familar context;
  27.                2.  Lists involve odd notation;
  28.                3.  Lists reside in an unfamilar place, heapspace.
  29.                4.  Lists need pointer variables, and those look
  30.                    cumbersome.
  31.                5.  Lists require odd procedures like GETMEM, NEW,
  32.                    and MEMAVAIL.
  33.                6.  The TURBO documentation on this subject seems
  34.                    vague and inconsistent.  It does not provide
  35.                    an adequate conceptual foundation.
  36.  
  37.     On the other hand, consider some of the advantages of lists:
  38.  
  39.                1.  The total data area available to your application
  40.                    is limited only by the amount of RAM available
  41.                    when your program starts.  With arrays, your
  42.                    application is limited by the size of the
  43.                    data segment.
  44.                2.  The sort time is reduced to that required for a
  45.                    single pass of the data (no nested FOR constructs,
  46.                    or bubble sorts, etc).
  47.                3.  List members can be deleted much more easily than
  48.                    array elements.  Imagine trying to delete the
  49.                    70th element of an array with 300 plus elements.
  50.                4.  The device drivers, memory control blocks, etc.,
  51.                    used by DOS are chained together using linked list-like
  52.                    constructs and are easier to understand and process
  53.                    in a linked list context.
  54.  
  55.  
  56.     So, on balance, lists have a useful place in any programmer's
  57.     bag of trick,s and, with an equitable foundation, they are actually
  58.     quite simple.
  59.  
  60.     A pointer, for example, is roughly analogous to an array index.  It
  61.     has a value in its own right, but actually refers to an address
  62.     (or offset) in memory of what is really needed.  An array index,
  63.     however, contains only a part of what is needed to access the
  64.     data (we rely upon the compiler to fill in the segment and base
  65.     address of the array).  A pointer variable contains a full
  66.     absolute address (segment and offset) in two words.  A pointer
  67.     is declared in a way that seems to violate Turbo's syntax rules in
  68.     that a reference is made to something not yet declared.  For example,
  69.  
  70.                       TYPE
  71.                           LIST_POINTER = ^LISTREC;
  72.                           (... more type declarations ...)
  73.                       VAR
  74.                           MYPOINTER : LIST_POINTER;
  75.  
  76.      When the compiler reaches this statement, it trusts that something
  77.      called LISTREC will be defined in the next statement.  The english
  78.      translation for this example is "Variables of type LIST_POINTER
  79.      will contain two word, segment/offset addresses corresponding
  80.      to the locations of variables of the type LISTREC.  Variables
  81.      of the type LISTREC will not reside in the data segment like
  82.      static variables.  Rather, they will reside in heapspace.  MYPOINTER
  83.      is a variable that can contain any segment/offset address, but will
  84.      contain the address of a specific member of a list (of type LISTREC)."
  85.  
  86.     To get the next element in an array, we usually just add one to the
  87.     index.  To get the next element in a linked list, we need to know
  88.     its address.  To accomplish this then, each member of a linked list
  89.     must have some address information, namely the address of the
  90.     next member.  For this reason, a RECORD structure is frequently used
  91.     to define list members, such as:
  92.  
  93.                  TYPE
  94.                      ANYSTRING    =  STRING[255];
  95.                      LIST_POINTER = ^LISTREC;
  96.                      LISTREC = RECORD
  97.                                      POINTER_TO_NEXT_MEMBER: LIST_POINTER;
  98.                                      MEMBER_DATA           : ANYSTRING;
  99.                                END;
  100.                  VAR
  101.                     MYPOINTER : LIST_POINTER;
  102.                     ROOT      : LIST_POINTER;
  103.                     LIST      : LISTREC;
  104.  
  105.     This translates into, "each member of my linked list will occupy
  106.     260 bytes of memory:
  107.  
  108.               POINTER_TO_NEXT_MEMBER      4 bytes
  109.               MEMBER_DATA
  110.                   (Turbo's length byte)   1 byte
  111.                   (String part)         255 bytes
  112.  
  113.     Further, each member will reside in contiguous heapspace, and each
  114.     member will contain the address of the next member.  The variable,
  115.     ROOT, will be used to hold the address of the first member added to
  116.     the list ('list entry point').  With the value of ROOT, the first
  117.     list member can be accessed.  Once the first record is accessed, the
  118.     pointer to the next record can be obtained.  When that record is
  119.     obtained, the pointer to the third record can be found, and so on".
  120.  
  121.     ROOT, then, corresponds roughly to the first element of an array.
  122.     To learn the last element in an array, we frequently use a counter
  123.     in a context like "FOR I := 1 TO NAME_COUNT DO ...".  In linked
  124.     lists, the last member is signified by all zeros in the
  125.     POINTER_TO_NEXT_MEMBER element.  Turbo uses a pre-defined constant
  126.     called, "NIL" for this.  Using the above example, compare these
  127.     constructs:
  128.  
  129.       last element of array               last member of a list
  130.  
  131.       I := I + 1;                         MYPOINTER := MYPOINTER^.POINTER_TO_NEXT_MEMBER;
  132.       IF I = NAME_COUNT THEN              IF MYPOINTER = NIL THEN
  133.          BEGIN                               BEGIN
  134.              (last element is reached)           (last member is reached)
  135.  
  136.     The translation for the statements on the right is "MYPOINTER is currently
  137.     pointing to a list member.  Take the value of the POINTER_TO_NEXT_MEMBER element
  138.     from this record and put it in MYPOINTER.  If this is NIL, then..."
  139.  
  140.     With these concepts, we can now transverse a linked list in a line-by-line
  141.     analogy to array processing:
  142.  
  143.         array logic                            linked list logic
  144.  
  145.         I := 1;                                MYPOINTER := ROOT;
  146.         REPEAT                                 REPEAT
  147.             WRITELN(NAMES[I]);                      WRITELN(MYPOINTER^.MEMBER_DATA);
  148.             I := SUCC(I);                           MYPOINTER := MYPOINTER^.POINTER TO NEXT MEMBER;
  149.         UNTIL I > NAME_COUNT;                  UNTIL MYPOINTER = NIL;
  150.  
  151.     To add an element/member:
  152.  
  153.         array logic                   linked list logic
  154.  
  155.   NAME_COUNT := SUCC(NAME_COUNT);     NEW(MYPOINTER);
  156.   NAMES[NAME_COUNT] := 'Mr. Kahn';    MYPOINTER^.MEMBER_DATA := 'Mr. Kahn';
  157.                                       MYPOINTER^.POINTER_TO_NEXT_MEMBER := NIL;
  158.                                       (logic for setting pointer
  159.                                        in previous record goes here).
  160.  
  161.   Beyond these examples, the analogies tend to break down.  The logic for
  162.   sorting an array tends to involve a physical rearrangement of the data
  163.   through an iterative process.  A linked list is usually sorted as
  164.   it is created by reassigning the pointers.  The upper bound of an
  165.   array is bound to a constant.  The capacity of a list is tied to
  166.   available heapspace.
  167.  
  168.   To maximize the value of this program, I suggest that it be run
  169.   in its native form (i.e., as you found it - which is hopefully
  170.   not altered) several times and observe the dynamics/interaction
  171.   of the generic routines.  Then copy this program into your own
  172.   file and modify the list building routines to create various
  173.   situations, such as a stack/heap collisions, duplicate records,
  174.   multiple lists, different sort key types, and so on.  It is also
  175.   important to note that several particularly elegant uses of
  176.   pointer variables that are not list related can be found on "GO BOR"
  177.   (try MAPMEM.PAS by TurboPower for a flagship example).
  178.  
  179. TREATMENT
  180. ---------
  181.  
  182.    The linked list used here is composed of three elements.  The first
  183.    is an index (or sort key).  The values in this element determine
  184.    how the list is to be sorted.  I have used an integer type here,
  185.    but this element can just as easily be redefined as a string type.
  186.    In addition to being the sort key for the list, the index element
  187.    is also used as a search argument in the lookup function.  The
  188.    next element is the so-called "data part", which contains all the
  189.    relevant information associated with the index value.  Naturally, this
  190.    element can also be redefined to meet specific applications.  The last
  191.    element is a pointer variable that can hold one of two values:
  192.  
  193.            1.  the NIL value, a special value indicating that
  194.                this record is the last item in the list.  NIL
  195.                is represented internally by four zero filled
  196.                bytes; or
  197.            2.  a pointer to the location of the next item in the
  198.                list.  These four bytes are represented internally
  199.                (as are all pointers) as:
  200.  
  201.                       -  low order offset;
  202.                       -  high order offset;
  203.                       -  low order segment;
  204.                       -  high order segment;
  205.  
  206.                It is IMPORTANT to note that the physical location
  207.                of a pointer or list member has NOTHING to do with
  208.                its sorted (or logical) location in the list.
  209.  
  210.                Physical addresses are assigned at the next available
  211.                heapspace location in the order that you ask for them.
  212.                This means that the root record (or logical entry point)
  213.                of a list could be located at a higher physical address than
  214.                any of the other members in the same list.
  215.  
  216.    Most of the procedures and functions given below deal with the
  217.    assignment of these pointers.  Filling the index and data parts
  218.    is up to you.  To build a list of names and ages
  219.    with age as the index, for example, the following code could be
  220.    used:
  221.               list.index_part := 28;   (* age *)
  222.               list.data_part  := 'Sally Maxwell';
  223.               add_member(list);
  224.               list.index_part := 27;   (* age *)
  225.               list.data_part  := 'Jane Robinson';
  226.               add_member(list);
  227.               etc, etc, etc....
  228.  
  229.    The add_member procedure finds the appropriate place in the
  230.    list, plugs your record there, and figures out to modify the
  231.    pointers.
  232.  
  233.    Once the list has been constucted, several things can be
  234.    done:
  235.  
  236.          1.  Look up a member of the list based upon the
  237.              the index value (procedure lookup_member).
  238.          2.  Delete a member of the list based upon
  239.              the index value (procedure delete_member).
  240.          3.  Process the entire list in sorted order
  241.              (procedure process_list).
  242.          4.  Dispose of the entire list (procedure dispose_list).
  243.  
  244.   When reading through linked list code, one encounters odd notation
  245.   such as the carat, ^.  I use the following translations:
  246.  
  247.  
  248.       P^              That which is pointed to by P.
  249.       P^ := X         That which is pointed to by P is X.
  250.       P^.A            The A element of the record pointed
  251.                       to by P.
  252.       P^ := P^.M^     P now points to same thing as the M pointer
  253.                       element of the record being pointed to by P.
  254.       NEW(W)          Create a new record in heapspace.  Assignments
  255.                       for the record elements to go in this area will
  256.                       soon follow, and will be issued in the form...
  257.  
  258.                                W^.INDEX :=
  259.                                W^.DATA  :=
  260. }
  261. PROGRAM LINKED_LISTS;
  262. CONST
  263.      FOREVER:BOOLEAN = FALSE;  { FOR CREATING STACK/HEAP COLLISIONS }
  264. TYPE
  265.     ANYSTRING = STRING[255];
  266.     LISTPTR =  ^LISTREC;
  267.     LISTREC =   RECORD
  268.                       NEXT_POINTER : LISTPTR;
  269.                       INDEX_PART   : INTEGER;
  270.                       DATA_PART    : ANYSTRING;
  271.                 END;
  272. VAR
  273. {  Define a global variable to hold the root address,
  274.    or entry point of the list.  The concept of a root
  275.    variable means "this variable holds the logical
  276.    entry point into the list".}
  277.    ROOT: LISTPTR;
  278.  
  279. {  Define a global variable to enable the construction
  280.    of the list.}
  281.    LIST: LISTREC;
  282.  
  283. {  Extra pointer for general use }
  284.    TEMPTR:LISTPTR;
  285.  
  286.    ANYINTEGER:INTEGER;
  287.    ANYCH:CHAR;
  288.  
  289.  
  290. {--------------------- GENERAL SUPPORT ROUTINES  ----------------------}
  291. { These routines are not of immediate interest and are included in compressed format.}
  292. FUNCTION HEXBYTE(N:INTEGER):ANYSTRING;CONST H:ANYSTRING='0123456789ABCDEF';BEGIN HEXBYTE:=H[((LO(N)DIV 16)MOD 16)+1]+
  293. H[(LO(N)MOD 16)+1];END;FUNCTION HEXWORD(N:INTEGER):ANYSTRING;BEGIN HEXWORD:=HEXBYTE(HI(N))+HEXBYTE(LO(N));END;
  294. FUNCTION CONV(I:INTEGER):REAL;BEGIN CONV:=256.0*HI(I)+LO(I);END;PROCEDURE PAUSE;VAR CH:CHAR;BEGIN WRITELN;
  295. WRITELN('-- PAUSING FOR KEYBOARD INPUT...');READ(KBD,CH);WRITELN;END;
  296. {--------------------- END OF GENERAL SUPPORT ROUTINES  ----------------}
  297.  
  298.  
  299. {  Linked lists reside in heapspace, an area of unused memory
  300.    at the low end of the stack/data segment.  The beginning of
  301.    available heapspace changes according to two factors:
  302.  
  303.               1.  how much is on the stack; and
  304.               2.  how much heapspace has already been grabbed.
  305.  
  306.    Our friends at Borland were thoughtful enough to provide
  307.    a pre-defined variable that points to the beginning of heapspace,
  308.    this variable is called, "HEAPPTR", and is configured with four bytes
  309.    just like the pointer layout described above.  You can use the
  310.    VALUEOF(HeapPtr) function at various points in the program to
  311.    examine Turbo's behavior in setting and assigning this rather
  312.    ephermeral item.  Unfortunately the standard variable, StackPtr,
  313.    as described in the documentation, is more elusive and not
  314.    compatable with VALUEOF.
  315.  
  316.    When heapspace is requested (with the NEW procedure), the HEAPPTR
  317.    variable will be reset to the next available location in
  318.    memory so that it will always point to the beginning of
  319.    available heapspace.  If enough heapspace is requested,
  320.    this variable will ultimately be equal to, or greater than
  321.    the stack pointer.  This situation creates a run-time
  322.    "stack/heap collision" and the program dies.  To avoid
  323.    this, the MEMAVAIL function should be used before the
  324.    NEW procedure to assure that enough heapspace is available.
  325. }
  326.  
  327. {-------------------  LINKED LIST SUPPORT  ---------------------
  328.  
  329. These routines help show what is happening as the list is being
  330. constructed.  They are not needed for an application.
  331.                                                                 }
  332. PROCEDURE SNAPMEM(X, Y:INTEGER);     { PUTS AVAILABLE HEAPSPACE }
  333. BEGIN                                { AT COLUMN X, ROW Y       }
  334.      GOTOXY(X, Y);
  335.      CLREOL;
  336.      WRITELN('HEAP SPACE = ',CONV(MEMAVAIL)*16.0:8:0);
  337. END;
  338.  
  339. FUNCTION VALUEOF(P:LISTPTR):ANYSTRING;  { RETURNS A STRING IN   }
  340.                                         { SEGMENT:OFFSET FORMAT }
  341.                                         { WHICH CONTAINS VALUE  }
  342. BEGIN                                   { OF A POINTER VARIABLE }
  343.      VALUEOF := HEXBYTE(MEM[SEG(P):OFS(P) + 3]) +
  344.                 HEXBYTE(MEM[SEG(P):OFS(P) + 2]) +':'+
  345.                 HEXBYTE(MEM[SEG(P):OFS(P) + 1]) +
  346.                 HEXBYTE(MEM[SEG(P):OFS(P) + 0]);
  347. END;
  348.  
  349. PROCEDURE GIVESTATS;   { REPORTS STATISTICS FOR DEMO PROGRAM }
  350. BEGIN
  351.      WRITELN('HEAP SPACE NOW BEGINS AT ',VALUEOF(HEAPPTR),
  352.             ' AND EXTENDS FOR ',CONV(MEMAVAIL) * 16.0:8:0,' BYTES');
  353.      PAUSE;
  354. END;
  355.  
  356. PROCEDURE SNAP_MEMBER(POINTER:LISTPTR);   {  PRINTS THE LIST MEMBER }
  357. BEGIN                                     {  BEING POINTED TO BY "POINTER"}
  358.      IF POINTER = NIL THEN
  359.         BEGIN
  360.              WRITELN('-----------  EMPTY LIST  -------------');
  361.              EXIT;
  362.         END;
  363.     WRITELN('---------------  LIST MEMBER INFORMATION  ------------');
  364.     WRITELN('THE INDEX OF THIS RECORD IS ', POINTER^.INDEX_PART);
  365.     WRITELN('THE DATA IN THIS RECORD IS  ', POINTER^.DATA_PART);
  366.     WRITELN('MEMORY LOCATION OF THIS RECORD IS ',VALUEOF(POINTER));
  367.     WRITELN('THIS RECORD IS POINTING TO A RECORD AT ',VALUEOF(POINTER^.NEXT_POINTER));
  368.     PAUSE;
  369. END;
  370. {---------------------  END OF LINKED LIST SUPPORT  ---------------------}
  371.  
  372.  
  373. {---------------------  GENERIC LINKED LIST ROUTINES  --------------------}
  374.  
  375. {  When adding a member to a linked list, four situations can occur.
  376.  
  377.    1.  The list is empty/uninitialized.
  378.    2.  The new record must be added to a place
  379.        before the existing root record.
  380.    3.  The new record must be inserted somewhere
  381.        in the middle of the list.
  382.    4.  The new record must be inserted
  383.        at the end of the list.
  384.  
  385.   The add_member procedure below recognizes each of these four
  386.   situations and adjusts the list accordingly.
  387.  
  388. }
  389.  
  390. PROCEDURE ADD_MEMBER (TARGET:LISTREC);   { ADDS THE TARGET RECORD TO }
  391. VAR                                      { LINKED LIST               }
  392.      MOVING_POINTER          : LISTPTR;
  393.      POINTER_TO_PRIOR_RECORD : LISTPTR;
  394. BEGIN
  395.  
  396.      {Assure that sufficient memory is available for the target member}
  397.      IF ((CONV(MEMAVAIL) * 16.0) < CONV(SIZEOF(TARGET))) THEN
  398.         BEGIN
  399.              WRITELN('---------- NO MORE MEMORY ---------------');
  400.              EXIT;
  401.         END;
  402.  
  403.      WRITELN;
  404.      WRITELN('-------------  ADDING A RECORD TO THE LIST  ----------');
  405.      WRITELN;
  406.      WRITELN('INDEX VALUE IS ',TARGET.INDEX_PART,' DATA IS ',TARGET.DATA_PART);
  407.  
  408.  
  409. { Logic for adding the first record to a list }
  410.      IF ROOT = NIL THEN
  411.         BEGIN
  412.              { Allocate some memory for the root pointer }
  413.              NEW(ROOT);
  414.              { Assign NIL to the last record in the list }
  415.              TARGET.NEXT_POINTER := NIL;
  416.              { Root points to the record in TARGET }
  417.              ROOT^ := TARGET;
  418.              WRITELN('THIS RECORD INITIALIZED THE LIST AT ',VALUEOF(ROOT));
  419.              GIVESTATS;
  420.              EXIT;
  421.         END;
  422.  
  423.  
  424. { Logic for adding a record "in front of" the current root }
  425.      IF (TARGET.INDEX_PART) <= (ROOT^.INDEX_PART) THEN
  426.         BEGIN
  427.              { Set pointer in new record to point to the root }
  428.              TARGET.NEXT_POINTER := ROOT;
  429.              { Allocate some memory for a new root pointer }
  430.              NEW(ROOT);
  431.              { Root points to the record in TARGET }
  432.              ROOT^ := TARGET;
  433.              WRITELN('THIS RECORD BECAME THE NEW ROOT AT    ',VALUEOF(ROOT));
  434.              WRITELN('THIS RECORD POINTS TO THE OLD ROOT AT ',VALUEOF(ROOT^.NEXT_POINTER));
  435.              GIVESTATS;
  436.              EXIT;
  437.         END;
  438.  
  439. { Logic for adding a record to "the middle of" a list }
  440.      MOVING_POINTER := ROOT;
  441.      REPEAT
  442.            POINTER_TO_PRIOR_RECORD  := MOVING_POINTER;
  443.            MOVING_POINTER  := MOVING_POINTER^.NEXT_POINTER;
  444.            IF (MOVING_POINTER <> NIL) THEN
  445.                IF ((TARGET.INDEX_PART) <= (MOVING_POINTER^.INDEX_PART))
  446.                    THEN
  447.                         BEGIN
  448.                              NEW(POINTER_TO_PRIOR_RECORD^.NEXT_POINTER);
  449.                              TARGET.NEXT_POINTER := MOVING_POINTER;
  450.                              POINTER_TO_PRIOR_RECORD^.NEXT_POINTER^ := TARGET;
  451.                              WRITELN('THIS RECORD WAS INSERTED INTO THE MIDDLE OF THE LIST ',
  452.                                      'AT ',VALUEOF(POINTER_TO_PRIOR_RECORD^.NEXT_POINTER));
  453.                              GIVESTATS;
  454.                              EXIT;
  455.                         END;
  456.      UNTIL MOVING_POINTER = NIL;
  457.  
  458.      {APPEND TO END OF LIST }
  459.      NEW(MOVING_POINTER);
  460.      TARGET.NEXT_POINTER := NIL;
  461.      POINTER_TO_PRIOR_RECORD^.NEXT_POINTER := MOVING_POINTER;
  462.      MOVING_POINTER^ := TARGET;
  463.      WRITELN('THIS RECORD WAS ADDED TO THE END OF THE LIST AT ',VALUEOF(POINTER_TO_PRIOR_RECORD^.NEXT_POINTER));
  464.      GIVESTATS;
  465. END;
  466.  
  467. FUNCTION LAST_MEMBER:LISTPTR;   { RETURNS POINTER TO LAST LIST MEMBER }
  468. VAR P:LISTPTR;
  469. BEGIN
  470.      P := ROOT;
  471.      IF P = NIL THEN
  472.         BEGIN
  473.              LAST_MEMBER := NIL;
  474.              EXIT;
  475.         END;
  476.      REPEAT
  477.            IF P^.NEXT_POINTER <> NIL THEN P := P^.NEXT_POINTER;
  478.      UNTIL P^.NEXT_POINTER = NIL;
  479.      LAST_MEMBER := P;
  480. END;
  481.  
  482. PROCEDURE ROTATE_LIST_LEFT;      { ROTATES ENTIRE LIST LEFT, ROOT BECOMES }
  483. VAR P1:LISTPTR;                  { TAIL, TAIL BECOMES PENULTIMATE, ETC.   }
  484.     P2:LISTPTR;
  485.     TR:LISTPTR;
  486.     R1:LISTREC;
  487. BEGIN
  488.      IF (ROOT = NIL) OR (ROOT^.NEXT_POINTER = NIL) THEN EXIT;
  489.      R1.DATA_PART    := ROOT^.DATA_PART;
  490.      R1.INDEX_PART   := ROOT^.INDEX_PART;
  491.      R1.NEXT_POINTER := NIL;
  492.      TR := ROOT^.NEXT_POINTER;
  493.      DISPOSE(ROOT);
  494.      ROOT := TR;
  495.      NEW (P1);
  496.      P1^ := R1;
  497.      P2 := LAST_MEMBER;
  498.      P2^.NEXT_POINTER := P1;
  499. END;
  500.  
  501. PROCEDURE MAKE_NEW_HEAD(P:LISTPTR);  { FIXES LIST SO THAT RECORD WITH POINTER }
  502. BEGIN                                { "P" IS THE ROOT RECORD.  ASSUMES "P"   }
  503.      IF (ROOT = NIL) OR              { EXISTS.                                }
  504.         (ROOT^.NEXT_POINTER = NIL) THEN EXIT;
  505.      REPEAT
  506.            ROTATE_LIST_LEFT;
  507.      UNTIL ROOT = P;
  508. END;
  509.  
  510. FUNCTION LIST_COUNT:INTEGER;     { RETURNS LIST MEMBER COUNT }
  511. VAR
  512.    P:LISTPTR;
  513.    I:INTEGER;
  514. BEGIN
  515.      IF ROOT = NIL THEN
  516.         BEGIN
  517.              LIST_COUNT := 0;
  518.              EXIT;
  519.         END;
  520.      I := 0;
  521.      P := ROOT;
  522.      REPEAT
  523.            I := SUCC(I);
  524.            P := P^.NEXT_POINTER;
  525.      UNTIL P = NIL;
  526.      LIST_COUNT := I;
  527. END;
  528.  
  529. FUNCTION PENULTIMATE:LISTPTR;           { RETURNS PENULTIMATE LIST MEMBER  }
  530. VAR                                     { OR NIL IF NO PENULTIMATE         }
  531.     P1:LISTPTR;
  532.     P2:LISTPTR;
  533. BEGIN
  534.      IF ROOT = NIL THEN
  535.         BEGIN
  536.              PENULTIMATE := NIL;
  537.              EXIT;
  538.         END;
  539.      IF LIST_COUNT <= 2 THEN
  540.         BEGIN
  541.              PENULTIMATE := ROOT;
  542.              EXIT;
  543.         END;
  544.      P1 := ROOT;
  545.      P2 := ROOT^.NEXT_POINTER;
  546.      REPEAT
  547.            P1 := P2;
  548.            P2 := P2^.NEXT_POINTER;
  549.      UNTIL P2 = LAST_MEMBER;
  550.      PENULTIMATE := P1;
  551. END;
  552.  
  553. PROCEDURE KILL_MEMBER(P:LISTPTR);   { DELETES LIST MEMBER POINTED TO      }
  554. VAR P1:LISTPTR;                     { BY "P".  EXITS IF THE LIST CONTAINS }
  555.     P2:LISTPTR;                     { LESS THAN TWO MEMBERS.              }
  556. BEGIN
  557.      IF ROOT = NIL THEN EXIT;
  558.      IF ROOT^.NEXT_POINTER = NIL THEN EXIT;
  559.      IF P = ROOT THEN
  560.         BEGIN
  561.              P1 := ROOT^.NEXT_POINTER;
  562.              DISPOSE(P);
  563.              ROOT := P1;
  564.              EXIT;
  565.         END;
  566.      IF P = LAST_MEMBER THEN
  567.         BEGIN
  568.              P1 := PENULTIMATE;
  569.              DISPOSE(P);
  570.              P1^.NEXT_POINTER := NIL;
  571.              EXIT;
  572.        END;
  573.      P1 := ROOT;
  574.      REPEAT
  575.            P2 := P1;
  576.            IF P1^.NEXT_POINTER <> NIL THEN P1 := P1^.NEXT_POINTER;
  577.      UNTIL (P1 = NIL) OR
  578.            (P1 = P);
  579.      IF P1 = NIL THEN EXIT;
  580.      P2^.NEXT_POINTER := P1^.NEXT_POINTER;
  581.      DISPOSE(P1);
  582. END;
  583.  
  584. FUNCTION LOOKUP_MEMBER(IND:INTEGER):LISTPTR;  { RETURNS THE POINTER      }
  585. VAR                                           { CONTAINING THE RECORD    }
  586.    MOVING_POINTER:LISTPTR;                    { WHOSE INDEX VALUE IS     }
  587. BEGIN                                         { EQUAL TO "IND".  IF NO   }
  588.      MOVING_POINTER := ROOT;                  { RECORD EXISTS, RETURNS   }
  589.      REPEAT                                   { NIL.                     }
  590.            IF MOVING_POINTER^.INDEX_PART = IND THEN
  591.               BEGIN
  592.                    LOOKUP_MEMBER := MOVING_POINTER;
  593.                    EXIT;
  594.               END;
  595.            MOVING_POINTER := MOVING_POINTER^.NEXT_POINTER;
  596.     UNTIL MOVING_POINTER = NIL;
  597.     LOOKUP_MEMBER := NIL;
  598. END;
  599.  
  600. {------------------  END OF GENERIC LIST PROCEDURES  -------------}
  601.  
  602.  
  603. {---------------  PRESENTATION PROCEDURES AND ENGINE  ----------}
  604.  
  605. PROCEDURE PROCESS_LIST;                  { WALKS THROUGH THE LIST, }
  606.                                          { FROM ROOT TO NIL.       }
  607.  
  608. { IMPORTANT NOTE ABOUT LIST PROCESSING!!!!
  609.  
  610.        Once a list has been created you can still have a stack/heap
  611.        collision where the stack is the culprit!  Remember that each
  612.        call to a function or procedure moves the stack downward
  613.        closer to your list.  For this reason, extra care must be
  614.        taken to assure that the routines for list processing programs
  615.        are sufficiently "shallow" (not nested too deep, not overly
  616.        recursive, etc). }
  617. VAR
  618.    MOVING_POINTER:LISTPTR;
  619. BEGIN
  620.      CLRSCR;
  621.      {                                       }
  622.      { YOUR LIST PROCESSING ROUTINES GO HERE }
  623.      {                                       }
  624.      IF ROOT = NIL THEN
  625.         BEGIN
  626.              WRITELN('--------------  EMPTY LIST  -------------');
  627.              EXIT;
  628.         END;
  629.      WRITELN;
  630.      WRITELN('      ----------------  CURRENT LIST CONTENTS  -------------- ');
  631.      WRITELN;
  632.      WRITELN('INDEX         DATA               LOCATION OF RECORD           LOCATION OF NEXT');
  633.      WRITELN('VALUE        VALUE                  IN MEMORY                RECORD IN MEMORY');
  634.      WRITELN('-----  --------------------         ---------                ----------------');
  635.      MOVING_POINTER := ROOT;
  636.      REPEAT
  637.           {  LIST PROCESSING CODE GOES HERE }
  638.           WRITELN(MOVING_POINTER^.INDEX_PART:5,
  639.                   MOVING_POINTER^.DATA_PART :20,
  640.                   VALUEOF(MOVING_POINTER):20,
  641.                   VALUEOF(MOVING_POINTER^.NEXT_POINTER):30);
  642.           MOVING_POINTER := MOVING_POINTER^.NEXT_POINTER;
  643.      UNTIL MOVING_POINTER = NIL;
  644.      WRITELN;
  645.      WRITELN('THE LIST COUNT IS ',LIST_COUNT);
  646.      IF ROOT = NIL THEN WRITELN('----- EMPTY LIST ----')
  647.                    ELSE WRITELN('THE ROOT MEMBER IS ',ROOT^.DATA_PART);
  648.      MOVING_POINTER := PENULTIMATE;
  649.      IF MOVING_POINTER <> NIL
  650.         THEN WRITELN('THE PENULTIMATE MEMBER IS ',MOVING_POINTER^.DATA_PART)
  651.         ELSE WRITELN('THERE IS NO PENULTIMATE MEMBER');
  652.      MOVING_POINTER := LAST_MEMBER;
  653.      WRITELN('THE LAST MEMBER IS ',MOVING_POINTER^.DATA_PART);
  654.      PAUSE;
  655. END;
  656.  
  657. PROCEDURE BUILD_A_LIST;
  658. BEGIN
  659.      {                                     }
  660.      { YOUR LIST BUILDING LOGIC GOES HERE  }
  661.      {                                     }
  662.      LIST.INDEX_PART :=  25; LIST.DATA_PART  := 'George Wilson  '; ADD_MEMBER(LIST); PROCESS_LIST;
  663.      LIST.INDEX_PART :=  21; LIST.DATA_PART  := 'Walter Thompson'; ADD_MEMBER(LIST); PROCESS_LIST;
  664.      LIST.INDEX_PART :=  28; LIST.DATA_PART  := 'James McCoy    '; ADD_MEMBER(LIST); PROCESS_LIST;
  665.      LIST.INDEX_PART :=  22; LIST.DATA_PART  := 'Robert Henton  '; ADD_MEMBER(LIST); PROCESS_LIST;
  666.      LIST.INDEX_PART :=  31; LIST.DATA_PART  := 'Dennis Campbell'; ADD_MEMBER(LIST); PROCESS_LIST;
  667.      LIST.INDEX_PART :=  20; LIST.DATA_PART  := 'Jack Farrell   '; ADD_MEMBER(LIST); PROCESS_LIST;
  668.      LIST.INDEX_PART :=  24; LIST.DATA_PART  := 'Herbert Walker '; ADD_MEMBER(LIST); PROCESS_LIST;
  669. END;
  670.  
  671. {-----------------  BEGINNING OF MAINLINE  ------------------------}
  672. {------------------------  DEMO  ----------------------------------}
  673. BEGIN
  674.      CLRSCR;
  675.      WRITELN('PROGRAM IS LOADED AT ',HEXWORD(CSEG));
  676.      WRITELN('DATA SEGMENT IS AT ',HEXWORD(DSEG));
  677.      WRITELN('STACK SEGMENT IS AT ',HEXWORD(SSEG));
  678.      WRITELN('HEAP SPACE BEGINS AT ',VALUEOF(HEAPPTR),
  679.              ' AND EXTENDS FOR ',    CONV(MEMAVAIL) * 16.0:8:0,
  680.              ' BYTES');
  681.      WRITELN('LINKED LIST RECORD SIZE IS ',SIZEOF(LIST),' BYTES');
  682.      PAUSE;
  683.      ROOT := NIL;
  684.  
  685.      WRITELN('BEGIN BUILDING A LINKED LIST');
  686.      PAUSE;
  687.      BUILD_A_LIST;
  688.  
  689.      REPEAT
  690.            WRITELN('DO YOU WANT TO ROTATE THE LIST (Y/N)?');
  691.            GOTOXY(50, WHEREY - 1);
  692.            READ(KBD,ANYCH);
  693.            IF UPCASE(ANYCH) = 'Y' THEN
  694.               BEGIN
  695.                    ROTATE_LIST_LEFT;
  696.                    PROCESS_LIST;
  697.               END;
  698.      UNTIL UPCASE(ANYCH) <> 'Y';
  699.      WRITELN;
  700.      WRITELN('NOW SEARCH FOR LIST MEMBERS');
  701.      PAUSE;
  702.      REPEAT
  703.           PROCESS_LIST;
  704.           WRITELN('PLEASE ENTER AN AGE TO SEARCH FOR (OR ZERO TO QUIT):  ');
  705.           GOTOXY(55, WHEREY - 1);
  706.           READLN(ANYINTEGER);
  707.           IF ANYINTEGER <> 0 THEN
  708.                  BEGIN
  709.                       WRITELN;
  710.                       TEMPTR := LOOKUP_MEMBER(ANYINTEGER);
  711.                       IF TEMPTR = NIL THEN
  712.                                           BEGIN
  713.                                                WRITELN('NO ONE OF AGE ',ANYINTEGER);
  714.                                                PAUSE;
  715.                                           END
  716.                                       ELSE SNAP_MEMBER(TEMPTR);
  717.                  END;
  718.      UNTIL ANYINTEGER = 0;
  719.  
  720.      WRITELN('NOW DELETE LIST MEMBERS');
  721.      PAUSE;
  722.      REPEAT
  723.           PROCESS_LIST;
  724.           WRITELN('PLEASE ENTER AN AGE TO DELETE (OR ZERO TO QUIT):  ');
  725.           GOTOXY(55, WHEREY - 1);
  726.           READLN(ANYINTEGER);
  727.           IF ANYINTEGER <> 0 THEN
  728.                  BEGIN
  729.                       WRITELN;
  730.                       TEMPTR := LOOKUP_MEMBER(ANYINTEGER);
  731.                       IF TEMPTR = NIL THEN
  732.                                           BEGIN
  733.                                                WRITELN('NO ONE OF AGE ',ANYINTEGER);
  734.                                                PAUSE;
  735.                                           END
  736.                                       ELSE KILL_MEMBER(LOOKUP_MEMBER(ANYINTEGER));
  737.                  END;
  738.      UNTIL (ANYINTEGER = 0) OR (ROOT = NIL);
  739.      PROCESS_LIST;
  740. END.
  741.  
  742. {-----------------------  END OF MAINLINE  ---------------------------}
  743.  
  744. APPLICATIONS:
  745.  
  746. With no effort at all, you can use these procedures to develop an improved
  747. DOS sort filter.  Consider the following list record:
  748.  
  749.       LISTREC = RECORD
  750.                       INDEX_PART : ANYSTRING;  { Load this according  }
  751.                                                { to your specs        }
  752.  
  753.                       DATA_PART  : ANYSTRING;  { Line from ASCII file }
  754.                       NEXT_PART  : POINTER;
  755.                 END;
  756.  
  757. With the numerous directory procedures available at "GO BOR", you can
  758. develop customized directory sort programs.  A sort by file size, for
  759. example, might have the following records:
  760.  
  761.          DIRREC  = RECORD
  762.                          ATTRIBUTE     : BYTE;
  763.                          NAME          : ANYSTRING;
  764.                          EXTENSION     : ANYSTRING;
  765.                          DATE          : DATEREC;  { or whatever you want }
  766.                          TIME          : TIMEREC;  { "      "     "   "   }
  767.                          FST_CLUSTER   : INTEGER;
  768.                    END;
  769.  
  770.          LISTREC = RECORD
  771.                          INDEX_PART : REAL;    { Holds file size }
  772.                          DATA_PART  : DIRREC;
  773.                          NEXT_PART  : POINTER;
  774.                    END;
  775.