home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / INDUCE.ZIP / INDUCE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1986-06-04  |  42.3 KB  |  1,214 lines

  1. {.IN+}
  2. {.PW132}
  3. (*$V-,R+,B- *)
  4. PROGRAM induce ;
  5.  
  6. (* Copyright 1986 - MicroExpert Systems
  7.                     Box 430 R.D. 2
  8.                     Nassau, NY 12123       *)
  9.  
  10. (* Induce implements the ID3 algorithm for the generation of rules from a data
  11.    set as described in the BYTE article "Finding Knowledge in Data".
  12.  
  13.    This program has been tested using Turbo ver 3.01A on an IBM PC. It has
  14.    been run under both DOS 2.1 and Concurrent 4.1 .
  15.  
  16.    The source for this program is contained in two files, INDUCE.PAS and
  17.    INDUCE.INC. The program produces one overlay file INDUCE.000 .
  18.  
  19.    INDUCE produces a knowledge base which can be used with MicroExpert.
  20.    MicroExpert is an expert system shell written in Turbo Pascal for the
  21.    IBM PC and Apple II. It is available for $49.95 and comes with complete
  22.    source code. It can be order by writing to :
  23.         McGraw-Hill Book Company
  24.         P.O. Box 400
  25.         Hightstown, NJ 08520
  26.  
  27.    Or calling 1-800-628-004 or in New York state 212/512-2999.
  28.  
  29.    We would be pleased to hear your comments, good or bad, or any applications
  30.    and modifications of the program. Contact us at the above address or
  31.    on BIX. Our id is bbt and we may be contacted via BIXmail or by leaving
  32.    comments in the MicroExpert conference.
  33.  
  34.    Bill and Bev Thompson    *)
  35.  
  36.  
  37.  CONST
  38.   ln2 = 0.69314718 ;
  39.   limit = 1.0E-20 ;
  40.   debug = false ;
  41.   back_space = ^H ;
  42.   tab = ^I ;
  43.   eof_mark = ^Z ;
  44.   esc = #27 ;
  45.   quote_char = #39 ;
  46.   left_arrow = #75 ;
  47.   end_key = #79 ;
  48.   del_line = ^X ;
  49.   return = ^M ;
  50.   bell = ^G ;
  51.  
  52.  TYPE
  53.   counter = 0 .. maxint ;
  54.   string80 = string[80] ;
  55.   string132 = string[132] ;
  56.   string255 = string[255] ;
  57.   text_file = text ;
  58.   char_set = SET OF char ;
  59.   node_type = (cons_node,symbol,number,free_node) ;
  60.   node_ptr = ^node ;
  61.   node = RECORD
  62.           in_use : boolean ;
  63.           CASE tag : node_type OF
  64.            cons_node : (tail_ptr : node_ptr ;
  65.                         head_ptr : node_ptr) ;
  66.            symbol    : (string_data : string80) ;
  67.            number    : (num_data : real) ;
  68.            free_node : (next_free : node_ptr ;
  69.                         block_cnt : counter) ;
  70.           END ;
  71.  
  72. (* node is the basic allocation unit for lists. The fields are used as
  73.    follows:
  74.  
  75.     in_use     - in_use = false tells the garbage collector that this node
  76.                  is available for re-use.
  77.     tag        - which kind of node this is.
  78.     cons_node  - cons_nodes consist of two pointers. one to the head (first item)
  79.                  the other to the rest of the list. They are the "glue" which
  80.                  holds the list together. The list (A B C) would be stored as
  81.                    -------         --------          --------
  82.                    | .| . |----->  |  .| . |------> |  .| . |---> NIL
  83.                    --|-----         --|------        --|-----
  84.                      |                |                |
  85.                      V                V                V
  86.                      A                B                C
  87.  
  88.                  The boxes are the cons nodes, the first part of the box
  89.                  holds the head pointer, then second contains the tail.
  90.     symbol     - holds string values, we don't actually use the entire 80
  91.                  characters in most cases.
  92.     number     - contains a real number.
  93.     free_node  - the garbage collector gathers all unused nodes and puts
  94.                  them on a free list. It also compacts the free space into
  95.                  contiguous blocks. next_free points to the next free block.
  96.                  block_cnt contains a count of the number of contiguous 8 byte free
  97.                  blocks which follow this one.    *)
  98.  
  99.  
  100.  VAR
  101.   example_file : text_file ;
  102.   line : string255 ;
  103.   c_list,examples,attrib_list,saved_list,initial_heap,free : node_ptr ;
  104.   total_free : real ;
  105.   no_of_cols : counter ;
  106.  
  107. (* The important globals are:
  108.    example_file - text file containing the original example set. See the
  109.                   documentation file for its format.
  110.    line         - line buffer for reading in the text file
  111.    c_list       - the classification tree
  112.    examples     - the list of examples
  113.    attrib_list  - list of attribute names and their values
  114.    saved_list   - list of all items that absolutely must be saved if garbage
  115.                   collection occurs. Usually has at least the examples and
  116.                   attrib_list attcahed to it.
  117.    initial_heap - the value of the heap pointer at the start of the program.
  118.                   used by the garbage collector
  119.    free         - the list of free nodes.
  120.    total_free   - total number of free blocks on the free list.
  121.    no_of_cols   - the total number of attributes + the class attribute in
  122.                   the example set.    *)
  123.  
  124.  
  125. (*$I induce.inc *)
  126.  
  127.  
  128.  PROCEDURE read_from_file(VAR f : text_file) ;
  129.   (* Read a line form file f and store it in the global variable, line.
  130.      It ignores blank lines and comments. When the end of file is reached
  131.      eof_mark is returned.   *)
  132.   CONST
  133.    in_comment : boolean = false ; (* static *)
  134.  
  135.   PROCEDURE read_a_line ;
  136.    BEGIN
  137.     (*$I- *)
  138.     readln(f,line) ;
  139.     (*$I+ *)
  140.     IF ioresult <> 0
  141.      THEN line := eof_mark
  142.     ELSE IF pos('(*',line) > 0
  143.      THEN
  144.       IF pos('*)',line) > 0
  145.        THEN delete(line,pos('(*',line),pos('*)',line) - pos('(*',line) + 2)
  146.        ELSE
  147.         BEGIN
  148.          in_comment := true ;
  149.          line := '' ;
  150.         END ;
  151.    END ; (* read_a_line *)
  152.  
  153.   BEGIN
  154.    line := '' ;
  155.    IF eof(f)
  156.     THEN line := eof_mark
  157.     ELSE
  158.      BEGIN
  159.       read_a_line ;
  160.       IF in_comment
  161.        THEN
  162.         IF pos('*)',line) > 0
  163.          THEN
  164.           BEGIN
  165.            delete(line,1,pos('*)',line) + 1) ;
  166.            in_comment := false ;
  167.           END
  168.          ELSE read_from_file(f) ;
  169.      END ;
  170.    strip_leading_blanks(line) ;
  171.    strip_trailing_blanks(line) ;
  172.    IF line = ''
  173.     THEN read_from_file(f) ;
  174.   END ; (* read_from_file *)
  175.  
  176.  
  177.  OVERLAY PROCEDURE expand(example_list : node_ptr ;
  178.                           VAR new_example_list : node_ptr) ;
  179.   (* Expand "don't care" values into values from attrib_list.
  180.      example_list     - unexpanded example set
  181.      new_example_list - expanded set    *)
  182.  
  183.   PROCEDURE dup_and_copy(list : node_ptr) ;
  184.    (* This routine creates a new version of the current row, pointed to by
  185.       list. If it finds a regular attribute value, it just appends the value
  186.       to the row it is constructing. If it finds a '*', indicating a
  187.       "don't care" value, it call copy_to_new_list to expand the value and
  188.       attach the new rows to new_example_list.
  189.       Notice that we attach anything we don't want to be trashed by the
  190.       garbage collector to the head of saved_list and remove it at the end
  191.       of the routine. copy_to_new_list saves new_list because it calls
  192.       dup_and_copy and that routine might initiate garbage collection.  *)
  193.    VAR
  194.     new_list,attr_ptr : node_ptr ;
  195.     copied : boolean ;
  196.  
  197.    PROCEDURE copy_to_new_list ;
  198.     (* This routine does the actual expansion. It attaches a value for the
  199.        attribute, pointed to by p, to the row that has been constructed
  200.        so far and attaches the rest of the list to the end of the row.
  201.        It calls dup_and_copy to expand any more *'s in the row and finally
  202.        attach the row to the new_example_list *)
  203.     VAR
  204.      p,new_row : node_ptr ;
  205.     BEGIN
  206.      saved_list := cons(new_list,saved_list) ;
  207.      copied := true ;
  208.      p := tail(head(attr_ptr)) ;
  209.      WHILE p <> NIL DO
  210.       BEGIN
  211.        new_row := append_list(new_list,cons(head(p),tail(list))) ;
  212.        dup_and_copy(new_row) ;
  213.        p := tail(p) ;
  214.       END ;
  215.      saved_list := tail(saved_list) ;
  216.     END ; (* copy_to_new_list *)
  217.  
  218.    BEGIN
  219.     saved_list := cons(list,saved_list) ;
  220.     test_memory ;
  221.     new_list := NIL ;
  222.     attr_ptr := attrib_list ;
  223.     copied := false ;
  224.     WHILE (list <> NIL) AND (NOT copied) DO
  225.      IF string_val(head(list)) = '*'
  226.       THEN copy_to_new_list
  227.       ELSE
  228.        BEGIN
  229.         new_list := append_list(new_list,cons(head(list),NIL)) ;
  230.         list := tail(list) ;
  231.         attr_ptr := tail(attr_ptr) ;
  232.        END ;
  233.     IF NOT copied
  234.      THEN new_example_list := append_list(new_example_list,cons(new_list,NIL)) ;
  235.     saved_list := cons(new_example_list,tail(saved_list)) ;
  236.    END ; (* dup_and_copy *)
  237.  
  238.   BEGIN
  239.    new_example_list := NIL ;
  240.    WHILE example_list <> NIL DO
  241.     BEGIN
  242.      dup_and_copy(head(example_list)) ;
  243.      example_list := tail(example_list) ;
  244.     END ;
  245.   END ; (* expand *)
  246.  
  247.  
  248.  OVERLAY FUNCTION conflicts(example_list : node_ptr) : boolean ;
  249.   (* Search for conflicts by using match_list to compare each row against
  250.      the rows which follow it in the example list. conflicts returns true
  251.      if a match is found.  *)
  252.   VAR
  253.    p : node_ptr ;
  254.    found_match : boolean ;
  255.  
  256.   PROCEDURE conflict_message ;
  257.    BEGIN
  258.     writeln ;
  259.     writeln('A conflict exists between rows:') ;
  260.     writeln ;
  261.     print_list(head(example_list)) ;
  262.     writeln ;
  263.     print_list(head(p)) ;
  264.     writeln ;
  265.     writeln('Processing cannot continue.') ;
  266.    END ; (* conflict_message *)
  267.  
  268.   BEGIN
  269.    found_match := false ;
  270.    WHILE (example_list <> NIL) AND (NOT found_match) DO
  271.     BEGIN
  272.      p := tail(example_list) ;
  273.      WHILE (p <> NIL) AND (NOT found_match) DO
  274.       IF match_lists(tail(head(example_list)),tail(head(p)))
  275.        THEN found_match := true
  276.        ELSE p := tail(p) ;
  277.      IF NOT found_match
  278.       THEN example_list := tail(example_list) ;
  279.     END ;
  280.    IF found_match
  281.     THEN conflict_message ;
  282.    conflicts := found_match ;
  283.   END ; (* conflicts *)
  284.  
  285.  
  286.  OVERLAY PROCEDURE build_table ;
  287.   (* Read the example file and build the attrib_list and examples. The
  288.      format of these two lists is described in the BYTE article mentioned
  289.      at the beginning of the program. This routine doesn't do much error
  290.      checking, so be careful with your example files.   *)
  291.   VAR
  292.    new_row : node_ptr ;
  293.    token : string80 ;
  294.  
  295.   PROCEDURE scan ;
  296.    (* Get a single token from the input line. This procedure strips leading
  297.       and trailing blanks and tabs, but interior spaces are sigificant.
  298.       A token is any string between the first non-space character and a
  299.       comma or end of line. Case is significant in tokens, 'Cat' and 'cat'
  300.       will be treated as different values by the program.    *)
  301.    VAR
  302.     comma_pos : byte ;
  303.    BEGIN
  304.     strip_leading_blanks(line) ;
  305.     IF line = ''
  306.      THEN token := ''
  307.      ELSE
  308.       BEGIN
  309.        comma_pos := pos(',',line) ;
  310.        IF comma_pos > 0
  311.         THEN
  312.          BEGIN
  313.           token := copy(line,1,comma_pos - 1) ;
  314.           delete(line,1,comma_pos) ;
  315.          END
  316.         ELSE
  317.          BEGIN
  318.           token := line ;
  319.           line := '' ;
  320.          END ;
  321.        IF token = ''
  322.         THEN token := '*' ;
  323.        strip_trailing_blanks(token) ;
  324.       END ;
  325.    END ; (* scan *)
  326.  
  327.   PROCEDURE build_a_row ;
  328.    (* Builds an example row. Symbolic and numerical attributes are handled
  329.       differently. Input lines are read one token at a time and storage is
  330.       allocated for the new token. The attrib_list is examined to see if
  331.       the new value appears on the list of values for that attribute. If it
  332.       does not, the value is added to the list. Symbolic values are added
  333.       to the end of the list of values for the attribute, numerical values are
  334.       stored in order. Once the new row is constructed it is appended
  335.       to the example set.   *)
  336.    VAR
  337.     at_list,row_list,token_ptr : node_ptr ;
  338.  
  339.    PROCEDURE length_error ;
  340.     (* Signal an error, probably a missing value. The row in question
  341.        will not be included in the example set, but the attribute list
  342.        may be damaged, so don't trust results after and error.   *)
  343.     BEGIN
  344.      writeln ;
  345.      writeln('Missing attribute in row:') ;
  346.      print_list(row_list) ;
  347.      writeln ;
  348.      writeln ;
  349.      wait ;
  350.     END ; (* length_error *)
  351.  
  352.    PROCEDURE add_value ;
  353.     (* Add a new value to the attribue list. The variable attrib_list keeps
  354.        track of the current column as the row is scanned. If token was
  355.        found to already be on the attrib_list, head(attrib_list) is appended
  356.        to at_list. If the token is a new value, it is added to the list at the
  357.        head of attrib_list, and then head(attrib_list) is appended to at_list.
  358.        After reading the entire row from the file, attrib_list is set to
  359.        point to at_list. This way attrib_list is reconstructed for each
  360.        row.    *)
  361.  
  362.     PROCEDURE insert_number ;
  363.      (* Insert a number into the attribute list. The list of values
  364.         for numerical attributes is maintained in order. This is done by
  365.         comparing the value of the token against the other items on the
  366.         list. As the comparison is done, the values are copied to new_list.
  367.         When a value is found that is greater than the token value or the
  368.         end of the list is reached, the token is appened to new_list and
  369.         then the reaming values on the old list are appended to new_list.
  370.         Finally new_list is appended to at_list. All of this appending
  371.         produces lots of garbage.  *)
  372.      VAR
  373.       new_list,p : node_ptr ;
  374.       r : real ;
  375.       inserted : boolean ;
  376.  
  377.      PROCEDURE build_new_list ;
  378.       (* This routine does the actual insetion described above. *)
  379.       BEGIN
  380.        WHILE (p <> NIL) AND (NOT inserted) DO
  381.         BEGIN
  382.          IF abs(r - num_val(head(p))) < limit
  383.           THEN
  384.            BEGIN
  385.             inserted := true ;
  386.             new_list := append_list(new_list,p) ;
  387.            END
  388.          ELSE IF r > num_val(head(p))
  389.           THEN
  390.            BEGIN
  391.             new_list := append_list(new_list,cons(head(p),NIL)) ;
  392.             p := tail(p) ;
  393.            END
  394.          ELSE
  395.           BEGIN
  396.            new_list := append_list(new_list,append_list(
  397.                                             cons(token_ptr,NIL),p)) ;
  398.            inserted := true ;
  399.           END ;
  400.         END ;
  401.       END ; (* build_new_list *)
  402.  
  403.      BEGIN
  404.       r := num_val(token_ptr) ;
  405.       inserted := false ;
  406.       new_list := cons(head(head(attrib_list)),NIL) ;
  407.       p := tail(head(attrib_list)) ;
  408.       build_new_list ;
  409.       IF (p = NIL) AND (NOT inserted)
  410.        THEN new_list := append_list(new_list,cons(token_ptr,NIL)) ;
  411.       at_list := append_list(at_list,cons(new_list,NIL)) ;
  412.      END ; (* insert_number *)
  413.  
  414.     BEGIN
  415.      IF tag_value(token_ptr) = number
  416.       THEN insert_number
  417.       ELSE at_list := append_list(at_list,
  418.                                   cons(append_list(head(attrib_list),
  419.                                                    cons(token_ptr,NIL)),
  420.                                         NIL)) ;
  421.     END ; (* add_value *)
  422.  
  423.    BEGIN
  424.     saved_list := cons(examples,attrib_list) ;
  425.     test_memory ;
  426.     at_list := NIL ;
  427.     row_list := NIL ;
  428.     scan ;
  429.     WHILE token <> '' DO
  430.      BEGIN
  431.       IF pos(':NUMBER',toupper(string_val(head(head(attrib_list))))) > 0
  432.        THEN token_ptr := alloc_num(toreal(token))
  433.        ELSE token_ptr := alloc_str(token) ;
  434.       IF (NOT on_list(token,head(attrib_list))) AND (token <> '*')
  435.        THEN add_value
  436.        ELSE at_list := append_list(at_list,cons(head(attrib_list),NIL)) ;
  437.       row_list := append_list(row_list,cons(token_ptr,NIL)) ;
  438.       attrib_list := tail(attrib_list) ;
  439.       scan ;
  440.      END ;
  441.     attrib_list := at_list ;
  442.     IF list_length(row_list) = no_of_cols
  443.      THEN examples := append_list(examples,cons(row_list,NIL))
  444.      ELSE length_error ;
  445.    END ; (* build_a_row *)
  446.  
  447.   PROCEDURE build_attrib_list ;
  448.    (* constructs the initial attrib_list from the first row in the file.
  449.       Initially the attrib_list is simply a list of the attribute names,
  450.       build_a_row adds the values to it. This routine also counts the
  451.       number of columns (attributes) in the table.   *)
  452.    BEGIN
  453.     attrib_list := NIL ;
  454.     no_of_cols := 0 ;
  455.     scan ;
  456.     WHILE token <> '' DO
  457.      BEGIN
  458.       attrib_list := append_list(attrib_list,cons(cons(alloc_str(token),NIL),
  459.                                                   NIL)) ;
  460.       no_of_cols := no_of_cols + 1 ;
  461.       scan ;
  462.      END ;
  463.    END ; (* build_attrib_list *)
  464.  
  465.   BEGIN
  466.    examples := NIL ;
  467.    line := '' ;
  468.    read_from_file(example_file) ;
  469.    IF line <> eof_mark
  470.     THEN build_attrib_list ;
  471.    read_from_file(example_file) ;
  472.    WHILE line <> eof_mark DO
  473.     BEGIN
  474.      build_a_row ;
  475.      read_from_file(example_file) ;
  476.     END ;
  477.   END ; (* build_table *)
  478.  
  479.  OVERLAY FUNCTION classify_it : node_ptr ;
  480.   (* is an overlay function which calls classify. We do it this way to avoid
  481.      swapping due to recursion. *)
  482.  
  483.   FUNCTION classify(example_list,chosen_list : node_ptr) : node_ptr ;
  484.    (* This is the main processing routine of the program. It is passed two
  485.       lists, a list of rows, example_list and a list of attributes already
  486.       chosen. The second list is simply for convenience. That way we don't
  487.       have to calculate the entropy for attribute which can no longer
  488.       contribute to splitting example_set. classify returns a pointer to the
  489.       classification tree built from the example_set.
  490.       If the example_list passed to it contains only a single class value,
  491.       classify returns the class_name (attribute name of the first column) and
  492.       the class value.
  493.       Variables:
  494.        split_elem    - the column (attribute number) to split on
  495.        classify_list - a temporary list to hold the tree
  496.        split_value   - for numerical attributes. It contains the value which
  497.                        produces the best numerical split.
  498.      classify prints a dot on the screen each time it is entered to show you
  499.      that the program really hasn't died.     *)
  500.    VAR
  501.     split_elem : counter ;
  502.     classify_list : node_ptr ;
  503.     split_value : real ;
  504.  
  505.    PROCEDURE find_split(VAR split_elem : counter ; VAR min_split_value : real) ;
  506.     (* finds the best attribute to split on. It returns the column number on
  507.        which to split and for numerical attribute, the value which produces the
  508.        best split. For each active attribute it constructs a class_list. the
  509.        class_list has the following format:
  510.        ( (attribute value #1 (class1 count) (class2 count) ....)
  511.          (attribute value #2 (class1 count) (class2 count) ....) .....)
  512.        The counts are the number of times each class appears in a row with
  513.        a particular value of the attribute. This list is used to calculate
  514.        the entropy of the attribute.  *)
  515.     VAR
  516.      i : counter ;
  517.      attrib : node_ptr ;
  518.      ent,min_entropy,split_value : real ;
  519.  
  520.     FUNCTION entropy(list : node_ptr ; cases : counter) : real ;
  521.      (* list is a class list. cases is the number of examples under
  522.         consideration. This routine calculates the entropy H(C|A) from the class
  523.         list. *)
  524.      VAR
  525.       sum,sum1,sum2,r : real ;
  526.       p : node_ptr ;
  527.  
  528.      FUNCTION log2(x : real) : real ;
  529.       BEGIN
  530.        IF abs(x) < limit
  531.         THEN log2 := 0.0
  532.         ELSE log2 := ln(x) / ln2 ;
  533.       END ; (* log2 *)
  534.  
  535.      BEGIN
  536.       sum := 0.0 ;
  537.       WHILE list <> NIL DO
  538.        BEGIN
  539.         sum1 := 0.0 ;
  540.         sum2 := 0.0 ;
  541.         p := tail(head(list)) ;
  542.         WHILE p <> NIL DO
  543.          BEGIN
  544.           r := num_val(head(tail(head(p)))) ;
  545.           sum1 := sum1 + r * log2(r) ;
  546.           sum2 := sum2 + r ;
  547.           p := tail(p) ;
  548.          END ;
  549.         sum := sum + (sum2 * log2(sum2)) - sum1 ;
  550.         list := tail(list) ;
  551.        END ;
  552.       entropy := sum / cases ;
  553.      END ; (* entropy *)
  554.  
  555.     PROCEDURE numeric_entropy(elem_no : counter ;
  556.                               VAR num_entropy,num_split_value : real) ;
  557.      (* Find the best split for a numeric attribute. elem_no is the column we
  558.         are working on. num_entropy is the best entropy for this attribute
  559.         and num_split_value is the split which gives that value. In
  560.         addition to the class list, this routine produces an ordered list
  561.         of the values for this attribute, called num_list. This list is
  562.         used in making the splits. Each split is half way between successive
  563.         values on the num_list. The entropy is calculated for each split. *)
  564.      VAR
  565.       class_list,sp,num_list : node_ptr ;
  566.       sp_val,num_ent : real ;
  567.       total_cases : counter ;
  568.  
  569.      PROCEDURE make_num_list ;
  570.       (* constructs num_list. This is essentially the same routine as
  571.          insert_number in build_table *)
  572.       VAR
  573.        new_list,p,q : node_ptr ;
  574.        r : real ;
  575.        inserted : boolean ;
  576.  
  577.       PROCEDURE add_to_new_list ;
  578.        BEGIN
  579.         WHILE (p <> NIL) AND (NOT inserted) DO
  580.          BEGIN
  581.           IF abs(r - num_val(head(p))) < limit
  582.            THEN
  583.             BEGIN
  584.              inserted := true ;
  585.              new_list := append_list(new_list,p) ;
  586.             END
  587.           ELSE IF r > num_val(head(p))
  588.            THEN
  589.             BEGIN
  590.              new_list := append_list(new_list,cons(head(p),NIL)) ;
  591.              p := tail(p) ;
  592.             END
  593.           ELSE
  594.            BEGIN
  595.             new_list := append_list(new_list,append_list(
  596.                                              cons(alloc_num(r),NIL),p)) ;
  597.             inserted := true ;
  598.            END ;
  599.          END ;
  600.        END ; (* add_to_new_list *)
  601.  
  602.       BEGIN
  603.        test_memory ;
  604.        num_list := NIL ;
  605.        q := example_list ;
  606.        WHILE q <> NIL DO
  607.         BEGIN
  608.          r := num_val(head(element(head(q),elem_no))) ;
  609.          new_list := NIL ;
  610.          p := num_list ;
  611.          inserted := false ;
  612.          add_to_new_list ;
  613.          IF (p = NIL) AND (NOT inserted)
  614.           THEN new_list := append_list(new_list,cons(alloc_num(r),NIL)) ;
  615.          num_list := new_list ;
  616.          q := tail(q) ;
  617.         END ;
  618.       END ; (* make_num_list *)
  619.  
  620.      PROCEDURE make_numeric_class_list(v : real) ;
  621.       (* builds the class list. v is the value to split on. The class_list
  622.          contains lists for two ranges < v and >= v. The list has the format:
  623.          ( (< v  (class1 count) (class2 count) .....)
  624.            (>= v (class1 count) (class2 count) .....))   *)
  625.       VAR
  626.        temp_list,p : node_ptr ;
  627.        v_str : string80 ;
  628.       BEGIN
  629.        str(v,v_str) ;
  630.        temp_list := NIL ;
  631.        p := tail(head(attrib_list)) ;
  632.        WHILE p <> NIL DO
  633.         BEGIN
  634.          temp_list := append_list(temp_list,cons(cons(head(p),
  635.                                                  cons(alloc_num(0.0),NIL)),NIL)) ;
  636.          p := tail(p) ;
  637.         END ;
  638.        class_list := cons(cons(alloc_str(concat('< ',v_str)),temp_list),
  639.                           cons(cons(alloc_str(concat('>= ',v_str)),
  640.                                copy_list(temp_list)),NIL)) ;
  641.       END ; (* make_numeric_class_list *)
  642.  
  643.      PROCEDURE count_numeric_classes(v : real ; elem_no : counter) ;
  644.       (* count the classes for each range. It reads the example list, extracts
  645.          the value for the attribute, searches the class list and increments
  646.          the appropriate class value in the list. v is the split value. *)
  647.       VAR
  648.        px,py : node_ptr ;
  649.  
  650.       PROCEDURE numeric_increment(list : node_ptr ; attr_v,atv : string80) ;
  651.        (* search list (class_list) and compare attr_v to v, the split_value.
  652.           atv is the class_value. Once we find the sub-list with the proper
  653.           range we search its tail for atv to increment the class count.   *)
  654.  
  655.        PROCEDURE do_increment(v_list : node_ptr) ;
  656.         VAR
  657.          p,q : node_ptr ;
  658.         BEGIN
  659.          q := tail(v_list) ;
  660.          WHILE q <> NIL DO
  661.           IF string_val(head(head(q))) = atv
  662.            THEN
  663.             BEGIN
  664.              p := head(tail(head(q))) ;
  665.              IF tag_value(p) = number
  666.               THEN p^.num_data := p^.num_data + 1.0 ;
  667.              total_cases := total_cases + 1 ;
  668.              q := NIL ;
  669.             END
  670.            ELSE q := tail(q) ;
  671.         END ; (* do_increment *)
  672.  
  673.        BEGIN
  674.         IF toreal(attr_v) < v
  675.          THEN do_increment(head(list))
  676.          ELSE do_increment(head(tail(list))) ;
  677.        END ; (* numeric_increment *)
  678.  
  679.       BEGIN
  680.        total_cases := 0 ;
  681.        px := example_list ;
  682.        WHILE px <> NIL DO
  683.         BEGIN
  684.          py := head(px) ;
  685.          numeric_increment(class_list,string_val(head(element(py,elem_no))),
  686.                   string_val(head(py))) ;
  687.          px := tail(px) ;
  688.         END ;
  689.       END ; (* count_numeric_classes *)
  690.  
  691.      BEGIN
  692.       num_entropy := 1.0E+37 ;
  693.       make_num_list ;
  694.       sp := tail(num_list) ;
  695.       saved_list := cons(num_list,saved_list) ;
  696.       WHILE sp <> NIL DO
  697.        BEGIN
  698.         test_memory ;
  699.         sp_val := num_val(head(num_list))
  700.                   + ((num_val(head(sp)) - num_val(head(num_list))) / 2.0) ;
  701.         make_numeric_class_list(sp_val) ;
  702.         count_numeric_classes(sp_val,elem_no) ;
  703.         num_ent := entropy(class_list,total_cases) ;
  704.         IF num_ent < num_entropy
  705.          THEN
  706.           BEGIN
  707.            num_entropy := num_ent ;
  708.            num_split_value := sp_val ;
  709.           END ;
  710.         num_list := sp ;
  711.         sp := tail(sp) ;
  712.        END ;
  713.       saved_list := tail(saved_list) ;
  714.      END ; (* numeric_entropy *)
  715.  
  716.     PROCEDURE symbol_entropy(val_list : node_ptr ; elem_no : counter ;
  717.                              VAR sym_ent,sym_split_val : real) ;
  718.      (* Find the entropy for a symbolic attribute. val_list is the list
  719.         of possible values for this attribute from the attrib_list.
  720.         elem_no is the column number, sym_ent is the entropy for this attribute.
  721.         sym_split_value is always 0. This routine constructs a class list
  722.         as described above and counts the classes for each value of the
  723.         attribute as in the numeric case, only there is no range splitting.
  724.         Symbolic attributes can result in mult-way partitions of the
  725.         example_list, numerical attributes always produce binary splits.  *)
  726.      VAR
  727.       class_list : node_ptr ;
  728.       total_cases : counter ;
  729.  
  730.      PROCEDURE make_class_list(a_list : node_ptr) ;
  731.       (* builds the initial class list. See above comments for format. *)
  732.       VAR
  733.        temp_list,p : node_ptr ;
  734.       BEGIN
  735.        WHILE a_list <> NIL DO
  736.         BEGIN
  737.          temp_list := cons(head(a_list),NIL) ;
  738.          p := tail(head(attrib_list)) ;
  739.          WHILE p <> NIL DO
  740.           BEGIN
  741.           temp_list := append_list(temp_list,cons(cons(head(p),
  742.                                                    cons(alloc_num(0.0),NIL)),NIL)) ;
  743.            p := tail(p) ;
  744.           END ;
  745.          class_list := append_list(class_list,cons(temp_list,NIL)) ;
  746.          a_list := tail(a_list) ;
  747.         END ;
  748.       END ; (* make_class_list *)
  749.  
  750.      PROCEDURE count_classes(elem_no : counter) ;
  751.       (* traverses the example_list and counts class values. *)
  752.       VAR
  753.        px,py : node_ptr ;
  754.  
  755.       PROCEDURE increment(list : node_ptr ; attr,v : string80) ;
  756.        (* search list (class_list) and compare attr to the head of each sub-list.
  757.           v is the class_value. Once we find the sub-list with the proper
  758.           range we search its tail for v to increment the class count.   *)
  759.        VAR
  760.         p,q : node_ptr ;
  761.        BEGIN
  762.         WHILE list <> NIL DO
  763.          IF string_val(head(head(list))) = attr
  764.           THEN
  765.            BEGIN
  766.             q := tail(head(list)) ;
  767.             WHILE q <> NIL DO
  768.              IF string_val(head(head(q))) = v
  769.               THEN
  770.                BEGIN
  771.                 p := head(tail(head(q))) ;
  772.                 IF tag_value(p) = number
  773.                  THEN p^.num_data := p^.num_data + 1.0 ;
  774.                 total_cases := total_cases + 1 ;
  775.                 list := NIL ;
  776.                 q := NIL ;
  777.                END
  778.               ELSE q := tail(q) ;
  779.            END
  780.           ELSE list := tail(list) ;
  781.         END ; (* increment *)
  782.  
  783.       BEGIN
  784.        total_cases := 0 ;
  785.        px := example_list ;
  786.        WHILE px <> NIL DO
  787.         BEGIN
  788.          py := head(px) ;
  789.          increment(class_list,string_val(head(element(py,elem_no))),
  790.                   string_val(head(py))) ;
  791.          px := tail(px) ;
  792.         END ;
  793.       END ; (* count_classes *)
  794.  
  795.      BEGIN
  796.       class_list := NIL ;
  797.       make_class_list(val_list) ;
  798.       count_classes(elem_no) ;
  799.       sym_ent := entropy(class_list,total_cases) ;
  800.       sym_split_val := 0.0
  801.      END ; (* symbol_entropy *)
  802.  
  803.     BEGIN
  804.      min_entropy := 1.0E+37 ;
  805.      FOR i := 2 TO no_of_cols DO
  806.       BEGIN
  807.        test_memory ;
  808.        attrib := head(element(attrib_list,i)) ;
  809.        IF NOT on_list(string_val(head(attrib)),chosen_list)
  810.         THEN
  811.          BEGIN
  812.           IF pos(':NUMBER',toupper(string_val(head(attrib)))) > 0
  813.            THEN numeric_entropy(i,ent,split_value)
  814.            ELSE symbol_entropy(tail(attrib),i,ent,split_value) ;
  815.           IF ent < min_entropy
  816.            THEN
  817.             BEGIN
  818.              min_entropy := ent ;
  819.              split_elem := i ;
  820.              min_split_value := split_value ;
  821.             END ;
  822.          END ;
  823.       END ;
  824.     END ; (* find_split *)
  825.  
  826.    FUNCTION split(elem_no : counter ; split_val : real) : node_ptr ;
  827.     (* This routine splits the example_list into sets which contain a single
  828.        value of the split attribute. elem_no is the column on which to split.
  829.        split_val is the split value for numerical attributes.
  830.        split_item points to the attribute's entry in the attribute list.
  831.        split_list is the tree which is returned by split. Its format is
  832.        (attribute_name (value1 classify(partition with attribute = value1)
  833.                        (value2 classify(partition with attribute = value2)
  834.                        ....... )    *)
  835.      VAR
  836.      split_list,split_item,new_chosen : node_ptr ;
  837.  
  838.     PROCEDURE numeric_split ;
  839.      (* Splitting on a numerical attribute splits the examples into
  840.         two groups, those with values < split_value and those with
  841.         values >= split_value. new_list1 and new_list2 are the example
  842.         sets for the two categories. It returns a split_list as follows:
  843.         (attribute_name ('< split_val'
  844.                          classify(all examples with attribute value < split_val)
  845.                         ('>= split_val'
  846.                          classify(all examples with attribute value >= split_val))
  847.         Notice all the lists placed on the saved_list. These are the items
  848.         that we must retain should any of the calls to classify invoke garbage
  849.         collection *)
  850.      VAR
  851.       new_list1,new_list2,q,valu : node_ptr ;
  852.       split_str : string80 ;
  853.      BEGIN
  854.       str(split_val,split_str) ;
  855.       valu := cons(alloc_str(concat('< ',split_str)),
  856.                    cons(alloc_str(concat('>= ',split_str)),NIL)) ;
  857.       q := example_list ;
  858.       new_list1 := NIL ;
  859.       new_list2 := NIL ;
  860.       WHILE q <> NIL DO
  861.        BEGIN
  862.         IF num_val(head(element(head(q),elem_no))) < split_val
  863.          THEN new_list1 := append_list(new_list1,cons(head(q),NIL))
  864.          ELSE new_list2 := append_list(new_list2,cons(head(q),NIL)) ;
  865.         q := tail(q) ;
  866.        END ;
  867.       saved_list := cons(split_list,cons(valu,cons(new_list2,saved_list))) ;
  868.       split_list := append_list(split_list,
  869.                               cons(cons(head(valu),
  870.                               cons(classify(new_list1,chosen_list),NIL)),NIL)) ;
  871.       saved_list := cons(split_list,tail(saved_list)) ;
  872.       split_list := append_list(split_list,
  873.                               cons(cons(head(tail(valu)),
  874.                               cons(classify(new_list2,chosen_list),NIL)),NIL)) ;
  875.       saved_list := tail(tail(tail(saved_list))) ;
  876.      END ; (* numeric_split *)
  877.  
  878.     PROCEDURE symbol_split ;
  879.      (* performs the split for symbolic attributes. For each value of the
  880.         attribute, it searches the example list for matches and attaches
  881.         examples with a match to new_example_list. If it finds any matches
  882.         it appends the value and the result of classifying the new_example_list
  883.         to split_list. This is a very inefficient way of doing this. It
  884.         would be better to sort the example_list using column elem_no as
  885.         a key.   *)
  886.      VAR
  887.       valu,q,new_example_list : node_ptr ;
  888.      BEGIN
  889.       valu := tail(split_item) ;
  890.       WHILE valu <> NIL DO
  891.        BEGIN
  892.         q := example_list ;
  893.         new_example_list := NIL ;
  894.         WHILE q <> NIL DO
  895.          BEGIN
  896.           IF string_val(head(valu)) = string_val(head(element(head(q),elem_no)))
  897.            THEN new_example_list := append_list(new_example_list,cons(head(q),NIL)) ;
  898.           q := tail(q) ;
  899.          END ;
  900.         IF new_example_list <> NIL
  901.          THEN
  902.           BEGIN
  903.            saved_list := cons(split_list,saved_list) ;
  904.            split_list := append_list(split_list,
  905.                                      cons(cons(head(valu),
  906.                                      cons(classify(new_example_list,new_chosen),NIL)),
  907.                                      NIL)) ;
  908.            saved_list := tail(saved_list) ;
  909.           END ;
  910.         valu := tail(valu) ;
  911.        END ;
  912.      END ; (* symbol_split *)
  913.  
  914.     BEGIN
  915.      split_item := head(element(attrib_list,elem_no)) ;
  916.      new_chosen := cons(head(split_item),chosen_list) ;
  917.      split_list := cons(head(split_item),NIL) ;
  918.      IF pos(':NUMBER',toupper(string_val(head(split_item)))) > 0
  919.       THEN numeric_split
  920.       ELSE symbol_split ;
  921.      split := split_list ;
  922.     END ; (* split *)
  923.  
  924.    FUNCTION single_class : boolean ;
  925.     (* returns true if the example_list contains only a single class value. *)
  926.     VAR
  927.      first_val : string80 ;
  928.      p : node_ptr ;
  929.      more_than_one : boolean ;
  930.     BEGIN
  931.      first_val := string_val(head(head(example_list))) ;
  932.      more_than_one := false ;
  933.      p := tail(example_list) ;
  934.      WHILE (p <> NIL) AND (NOT more_than_one) DO
  935.       IF string_val(head(head(p))) <> first_val
  936.        THEN more_than_one := true
  937.        ELSE p := tail(p) ;
  938.      single_class := NOT more_than_one ;
  939.     END ; (* single_class *)
  940.  
  941.    BEGIN
  942.     write('.') ;
  943.     split_elem := 0 ;
  944.     saved_list := cons(chosen_list,cons(example_list,saved_list)) ;
  945.     IF NOT single_class
  946.      THEN find_split(split_elem,split_value) ;
  947.     IF split_elem = 0
  948.      THEN classify_list := cons(head(head(attrib_list)),
  949.                                 cons(cons(head(head(example_list)),NIL),NIL))
  950.      ELSE classify_list := split(split_elem,split_value) ;
  951.     saved_list := append_list(tail(tail(saved_list)),cons(classify_list,NIL)) ;
  952.     classify := classify_list ;
  953.    END ; (* classify *)
  954.  
  955.   BEGIN
  956.    classify_it := classify(examples,NIL) ;
  957.   END ; (* classify_it *)
  958.  
  959.  
  960.  OVERLAY PROCEDURE print_rule_list(list : node_ptr) ;
  961.   (* This routine transforms the tree into a set of IF/THEN statements and
  962.      writes them to a file. It produces a knowledge base for MicroExpert [c],
  963.      if you want to produces rules for another shell, this routine will
  964.      have to be modified.  *)
  965.   VAR
  966.    rule_count : counter ;
  967.    rule_file : text_file ;
  968.    file_name : string80 ;
  969.    used_attribs : node_ptr ;
  970.  
  971.   PROCEDURE print_rule(tree,rule_list : node_ptr) ;
  972.    (* Do a depth first traversal of tree. On entry rule_list contains a
  973.       list of attribute value pairs. When tree is finally NIL, i.e. a
  974.       terminal node of the tree has been encountered, the rule_list is
  975.       printed. If entered with a non-NIL tree, the routine creates a new
  976.       attribute value pair, attaches it to rule_list and explores further
  977.       down the tree. It also attaches the attribute names to used_attribs
  978.       so that they can be used to generate prompts. *)
  979.    VAR
  980.     p : node_ptr ;
  981.  
  982.    PROCEDURE print_the_rule(list : node_ptr) ;
  983.     (* Prints the rule_list, with rules formatted for MicroExpert. *)
  984.     VAR
  985.      s : string80 ;
  986.  
  987.     PROCEDURE write_compare ;
  988.      VAR
  989.       comp_str : string[2] ;
  990.  
  991.      FUNCTION quote(w : string80) : string80 ;
  992.       BEGIN
  993.        quote := '''' + w + '''' ;
  994.       END ; (* quote *)
  995.  
  996.      BEGIN
  997.       comp_str := '' ;
  998.       WHILE s[1] <> ' ' DO
  999.        BEGIN
  1000.         comp_str := comp_str + s[1] ;
  1001.         delete(s,1,1) ;
  1002.        END ;
  1003.       strip_leading_blanks(s) ;
  1004.       strip_trailing_blanks(s) ;
  1005.       writeln(rule_file,'function compare(',attrib_value(head(head(list))),
  1006.               ',',quote(comp_str),',',quote(s),')') ;
  1007.      END ; (* write_compare *)
  1008.  
  1009.     BEGIN
  1010.      writeln(rule_file,rule_count) ;
  1011.      rule_count := rule_count + 1 ;
  1012.      write(rule_file,'If   ') ;
  1013.      WHILE list <> NIL DO
  1014.       BEGIN
  1015.        s := string_val(head(tail(head(list)))) ;
  1016.        IF s[1] IN ['<','>']
  1017.         THEN write_compare
  1018.         ELSE writeln(rule_file,attrib_value(head(head(list))),' is ',s) ;
  1019.        list := tail(list) ;
  1020.        IF list <> NIL
  1021.         THEN
  1022.          IF tail(list) = NIL
  1023.           THEN write(rule_file,'then ')
  1024.           ELSE write(rule_file,'and  ') ;
  1025.       END ;
  1026.      writeln(rule_file,'.') ;
  1027.      writeln(rule_file) ;
  1028.     END ; (* print_the_rule *)
  1029.  
  1030.    BEGIN
  1031.     IF tree = NIL
  1032.      THEN print_the_rule(rule_list)
  1033.      ELSE
  1034.       BEGIN
  1035.        IF head(tree) <> head(head(attrib_list))
  1036.         THEN
  1037.          IF NOT on_list(string_val(head(tree)),used_attribs)
  1038.           THEN used_attribs := cons(head(tree),used_attribs) ;
  1039.        p := tail(tree) ;
  1040.        WHILE p <> NIL DO
  1041.         BEGIN
  1042.          print_rule(head(tail(head(p))),
  1043.                     append_list(rule_list,cons(cons(head(tree),
  1044.                                                cons(head(head(p)),NIL)),
  1045.                                                NIL))) ;
  1046.          p := tail(p) ;
  1047.         END ;
  1048.       END ;
  1049.    END ; (* print_rule *)
  1050.  
  1051.   PROCEDURE print_prompts ;
  1052.    (* This routine traverses the attribute list and writes a prompt for
  1053.       each attribute on the list. MicroExpert does not automatically
  1054.       generate prompts, so this is necessary. The format
  1055.       of the questions may seem dumb. For a working knowledge base, you
  1056.       will want to edit the prompts and add translations.  *)
  1057.     VAR
  1058.      q : node_ptr ;
  1059.    BEGIN
  1060.     q := used_attribs ;
  1061.     WHILE q <> NIL DO
  1062.      BEGIN
  1063.       writeln(rule_file) ;
  1064.       IF pos(':NUMBER',toupper(string_val(head(q)))) > 0
  1065.        THEN writeln(rule_file,'Numeric prompt ',attrib_value(head(q)))
  1066.        ELSE writeln(rule_file,'Prompt ',attrib_value(head(q))) ;
  1067.       writeln(rule_file,'What is the value of ',attrib_value(head(q)),' ?') ;
  1068.       writeln(rule_file,'.') ;
  1069.       q := tail(q) ;
  1070.      END ;
  1071.    END ; (* print_prompts *)
  1072.  
  1073.   BEGIN
  1074.    writeln ;
  1075.    write('Output the rules to what file (Press <ENTER> for screen.) ? ') ;
  1076.    readln(file_name) ;
  1077.    strip_leading_blanks(file_name) ;
  1078.    IF file_name = ''
  1079.     THEN file_name := 'con:' ;
  1080.    assign(rule_file,file_name) ;
  1081.    rewrite(rule_file) ;
  1082.    writeln(rule_file) ;
  1083.    rule_count := 1 ;
  1084.    used_attribs := NIL ;
  1085.    print_rule(list,NIL) ;
  1086.    print_prompts ;
  1087.    IF is_console(rule_file)
  1088.     THEN wait ;
  1089.    close(rule_file) ;
  1090.   END ; (* print_rule_list *)
  1091.  
  1092.  
  1093.  OVERLAY PROCEDURE print_tree(list : node_ptr) ;
  1094.   (* Print the tree. This is really just a pretty print routine, which
  1095.      indents each sub_list. *)
  1096.   VAR
  1097.    indent_level : counter ;
  1098.    tree_file : text_file ;
  1099.    file_name : string80 ;
  1100.  
  1101.   PROCEDURE print_the_tree(tree : node_ptr ; VAR indent : counter) ;
  1102.    VAR
  1103.     p : node_ptr ;
  1104.    BEGIN
  1105.     IF tree <> NIL
  1106.      THEN
  1107.       CASE tree^.tag OF
  1108.        number,
  1109.        symbol    : BEGIN
  1110.                     write(tree_file,attrib_value(tree),' ') ;
  1111.                     indent := indent + length(attrib_value(tree)) + 1 ;
  1112.                    END ;
  1113.        cons_node : BEGIN
  1114.                     write(tree_file,'(') ;
  1115.                     indent := indent + 1 ;
  1116.                     print_the_tree(head(tree),indent) ;
  1117.                     p := tail(tree) ;
  1118.                     WHILE p <> NIL DO
  1119.                      BEGIN
  1120.                       print_the_tree(head(p),indent) ;
  1121.                       IF list_length(p) > 1
  1122.                        THEN
  1123.                         BEGIN
  1124.                          writeln(tree_file) ;
  1125.                          write(tree_file,' ' : indent) ;
  1126.                         END ;
  1127.                       p := tail(p) ;
  1128.                      END ;
  1129.                     indent := indent - length(attrib_value(head(tree))) - 2 ;
  1130.                     write(tree_file,') ') ;
  1131.                    END ;
  1132.       END ;
  1133.    END ; (* print_the_tree *)
  1134.  
  1135.   BEGIN
  1136.    writeln ;
  1137.    write('Output the tree to what file (Press <ENTER> for screen.) ? ') ;
  1138.    readln(file_name) ;
  1139.    strip_leading_blanks(file_name) ;
  1140.    IF file_name = ''
  1141.     THEN file_name := 'con:' ;
  1142.    assign(tree_file,file_name) ;
  1143.    rewrite(tree_file) ;
  1144.    writeln(tree_file) ;
  1145.    indent_level := 0 ;
  1146.    print_the_tree(list,indent_level) ;
  1147.    writeln(tree_file) ;
  1148.    writeln(tree_file) ;
  1149.    IF is_console(tree_file)
  1150.     THEN wait ;
  1151.    close(tree_file) ;
  1152.   END ; (* print_tree *)
  1153.  
  1154.  
  1155.  OVERLAY FUNCTION got_file : boolean ;
  1156.   (* asks for an example file name and tries to open it. If it can't
  1157.      open the file, it complains and asks for a new file *)
  1158.   VAR
  1159.    example_name : string80 ;
  1160.   BEGIN
  1161.    writeln ;
  1162.    write('Example File (Press <ENTER> to quit.) : ') ;
  1163.    readln(example_name) ;
  1164.    IF example_name = ''
  1165.     THEN got_file := false
  1166.     ELSE
  1167.      BEGIN
  1168.       IF pos('.',example_name) = 0
  1169.        THEN example_name := concat(example_name,'.EX') ;
  1170.       IF open(example_file,example_name)
  1171.        THEN got_file := true
  1172.        ELSE
  1173.         BEGIN
  1174.          writeln ;
  1175.          writeln(toupper(example_name),' could not be found.') ;
  1176.          writeln ;
  1177.          got_file := got_file ;
  1178.         END ;
  1179.      END ;
  1180.   END ; (* got_file *)
  1181.  
  1182.  
  1183.  BEGIN
  1184.   free := NIL ;
  1185.   initial_heap := HeapPtr ;
  1186.   total_free := 0.0 ;
  1187.   clrscr ;
  1188.   WHILE got_file DO
  1189.    BEGIN
  1190.     build_table ;
  1191.     close(example_file) ;
  1192.     IF NOT conflicts(examples)
  1193.      THEN
  1194.       BEGIN
  1195.        saved_list := cons(attrib_list,examples) ;
  1196.        expand(examples,examples) ;
  1197.        writeln ;
  1198.        saved_list := cons(attrib_list,examples) ;
  1199.        c_list := classify_it ;
  1200.        saved_list := cons(c_list,attrib_list) ;
  1201.        writeln ;
  1202.        test_memory ;
  1203.        print_tree(c_list) ;
  1204.        writeln ;
  1205.        test_memory ;
  1206.        writeln ;
  1207.        print_rule_list(c_list) ;
  1208.        clrscr ;
  1209.       END ;
  1210.    END ;
  1211.  END.
  1212.  
  1213.  
  1214.