Prolog Programming for Artificial Intelligence_3_

Document Sample
Prolog Programming for Artificial Intelligence_3_ Powered By Docstoc
					Part 1 The Prolog Language


Chapter 8
Programming Style and
Technique



                             1
8.1 General principles of good
programming

   What is a good program?
     Generally accepted criteria include the
      following:
        Correctness
        User-friendliness

        Efficiency

        Readability

        Modifiability

        Robustness

        Documentation



                                                2
8.2 How to think about Prolog
programs

   During the process of developing a solution
    we have to find ideas for reducing problems
    to one or more easier subproblems.
   How do we find proper subproblems?
       Use of recursion (Section 8.2.1)
       Generalization (Section 8.2.2)
       Using pictures (Section 8.2.3)




                                              3
8.2.1 Use of recursion
   The principle here is to split the problem into two
    cases:
     (1) trivial, or boundary cases;
     (2) general cases where the solution is constructed(建構)
       from solutions of (simpler) versions of the original
       program itself.


   An example:
        Processing a list of items so that each item is
         transformed by the same transformation rule.
         maplist( List, F, NewList)
         where List is an original list, F is a transformation rule
         and NewList is the list of all transformed items.


                                                                      4
    8.2.1 Use of recursion
   The problem of transforming List can be split into two
    cases:
     (1) Boundary case: List = []
         if List = [] then NewList = [], regardless(不管) of F
     (2) General case: List = [X|Tail]
         To transform a list of the form [X|Tail] do:
             transform the item X by rule F obtaining NewX, and
             transform the list Tail obtaining NewTail;
             the whole transformed list is [NewX|NewTail].
   In Prolog:
    maplist( [], _, []).
    maplist( [X|Tail], F, [NewX|NewTail]) :-
     G =.. [F, X, NewX], call( G), maplist(Tail, F, NewTail).

                                                              5
8.2.1 Use of recursion
   Suppose we have a list of numbers and want to
    compute the list of their squares.
    square( X, Y) :- Y is X * X.

     maplist( [], _, []).
     maplist( [X|Tail], F, [NewX|NewTail]) :-
         G =.. [F, X, NewX],
         call( G),
         maplist(Tail, F, NewTail).

     | ?- maplist([2,6,5], square, Square).
     Square = [4,36,25]
     yes


                                                    6
8.2.2 Generalization
   It is often a good idea to generalize the original
    problem, so that the solution to the generalized
    problem can be formulated recursively.
   The original problem is then solved as a special case
    of its more general version.
   The example is the eight queens problem.
       The original problem was to place eight queens on the
        chessboard so that they do not attack each other.
        eightqueens( Pos)
        This is true if Pos is a position with eight non-attacking
        queens.
       A good idea in this case is to generalize the number of
        queens from eight to N.
        nqueens( Pos, N)
                                                                 7
8.2.2 Generalization
    The advantage of this generalization is that there is
     an immediate recursive formulation of the nqueens
     relation:
    (1) Boundary case: N = 0
         To safely place zero queens is trivial.
    (2) General case: N > 0
         To safely place N queens on the board, satisfy
         the following:
       Achieve a safe configuration of (N-1) queens;
        and
       Add the remaining queen so that she does not
        attach any other queen.

     eightqueens( Pos) :- nqueens( Pos, 8)
                                                             8
    4.5.3 The eight queens problem—
    Program 3
                    -7                  -2   +7
                                              u=x-y
y
8                                                The domains for all
7                                                 four dimensions are:
6                                             Dx=[1,2,3,4,5,6,7,8]
5                                             Dy=[1,2,3,4,5,6,7,8]
4       ●                                     Du=[-7,-6,-5,-4,-3,-2,
3                                               -1,0,1,2,3,4,5,6,7]
2                                             Dv=[2,3,4,5,6,7,8,9,10,
                                                11,12,13,14,15,16]
1
                                        x
    1   2   3   4       5   6   7   8


                                                  v=x+y
                    2               6        16


                                                                    9
   4.5.3 The eight queens problem—
   Program 3
% Figure 4.11     Program 3 for the eight queens problem.

 solution( Ylist) :-
    sol( Ylist, [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8],
                [-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7],
                [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] ).

sol( [], [], Dy, Du, Dv).
sol( [Y | Ylist], [X | Dx1], Dy, Du, Dv) :-
     del( Y, Dy, Dy1), U is X-Y, del( U, Du, Du1), V is X+Y,
     del( V, Dv, Dv1), sol( Ylist, Dx1, Dy1, Du1, Dv1).

del( Item, [Item | List], List).
del( Item, [First | List], [First | List1] ) :-
    del( Item, List, List1).
                                                               10
4.5.3 The eight queens problem—
Program 3
   To generation of the domains:
    gen( N1, N2, List)
    which will, for two given integers N1 and N2, produce the
    list
    List = [ N1, N1+1, N1+2, ..., N2-1, N2]

   Such procedure is:
    gen( N, N, [N]).
    gen( N1, N2, [N1|List]) :-
      N1 < N2, M is N1+1, gen(M, N2, List).

   The gereralized solution relation is:
    solution( N, S) :-
         gen(1, N, Dxy), Nu1 is 1-N, Nu2 is N-1,
         gen(Nu1, Nu2, Du), Nv2 is N+N,
         gen(2, Nv2, Dv), sol( S, Dxy, Dxy, Du, Dv).
                                                            11
4.5.3 The eight queens problem—
Program 3
   For example, a solution to the 12-queens probelm would
    be generated by:

    ?- solution( 12, S).
    S=[1,3,5,8,10,12,6,11,2,7,9,4]




                                                             12
8.2.3 Using pictures
   When searching for ideas about a problem, it is
    often useful to introduce some graphical
    representation of the problem.
   A picture may help us to perceive(理解) some
    essential relations in the problem.
   The use of pictorial representations is very useful in
    Prolog.
       Prolog is particularly suitable for problems that involve
        objects and relations between objects. Such problem
        can be naturally illustrated by graph.
       Structured data objects in Prolog are naturally pictured
        as trees.



                                                               13
    2.1.3 Structures
       Tree representation of the objects:
        P1 = point( 1, 1)
        S = seg( P1, P2)
          = seg( point(1,1), point(2,3))
        T = triangle( point(4,2), point(6,4), point(7,1))

                                        Principal factor
P1=point                S=seg                              T=triangle



1        1          point       point              point       point        point



                1       1   2       3          4       2   6       4    7       1



                                                                                    14
8.3 Programming style
   The purpose of conforming to some stylistic(文體的)
    conventions(慣例) is:
       To reduce the danger of programming errors;
        and
       To produce programs that are
            readable and easy to understand
            easy to debug and to modify




                                                       15
8.3.1 Some rules of good style
   Program clauses should be short.
   Procedures should be short because long procedures are
    hard to understand.
   Mnemonic(有助記憶的) names for procedures and variables
    should be used.
   The layout(版面編排) of programs is important.
       Spacing, blank lines and indentation(縮排) should be
        consistently used for the sake(目的) of readability.
       Clauses about the same procedure should be clustered
        together.
       There should be blank lines between clauses.
   Stylistic conventions of this kind may vary from program
    to program. However, it is important that the same
    conventions are used consistently throughout the whole
    program.

                                                               16
8.3.1 Some rules of good style
   The cut operator should be used with care.
       Cut should not be used if it can be easily avoided.
       It is better to use ‘green cuts’ rather than ‘red cuts’.

   The not procedure can also lead to surprising
    behavior, as it is related to cut.
       If there is a dilemma(兩難的選擇) between not and cut,
        the former is perhaps better than some obscure(模糊的)
        construct with cut.

   Program modification by assert and retract can
    grossly(非常地) degrade the transparency (降低透明度)
    of the program’s behavior.
       In particular, the same program will answer the same
        question differently at different times.
                                                                   17
8.3.1 Some rules of good style
   The use of a semicolon(;) may obscure(使不顯著) the
    meaning of a clause.
       The readability can sometimes be improved by
        splitting the clause containing the semicolon into more
        clauses.


   To illustrate some points of this section, consider the
    relation
    merge( List1, List2, List3)
    where List1 and List2 are ordered lists that merge
    into List3.
       For example:
        merge([2,4,7], [1,3,4,8], [1,2,3,4,4,7,8])

                                                             18
8.3.1 Some rules of good style
    A bad style

    merge( List1, List2, List3) :-
          List1 = [], !, List3 = List2;
          List2 = [], !, List3 = List1;
          List1 = [X|Rest1],
          List2 = [Y|Rest2],
          ( X < Y, !,
             Z = X,
             merge( Rest1, List2, Rest3);
             Z = Y,
             merge( List1, Rest2, Rest3)),
          List3 = [Z| Rest3].



                                             19
8.3.1 Some rules of good style
   A better version

    merge1( [], List, List) :- !.

    merge1( List, [], List).

    merge1( [X|Rest1], [Y|Rest2], [X|Rest3]) :-
      X < Y, !,
      merge1( Rest1, [Y|Rest2], Rest3).

    merge1( List1, [Y|Rest2], [Y|Rest3]) :-
      merge1( List1, Rest2, Rest3).




                                                  20
8.3.1 Some rules of good style
| ?- merge1([2,4,7],[1,3,4,8], List).
List = [1,2,3,4,4,7,8]
yes

| ?- merge([2,4,7],[1,3,4,8], List).
List = [1,2,3,4,4,7,8]
Yes

| ?- merge1([2], [8], [2,8]).
yes

| ?- merge([2], List, [2,8]).
no

| ?- merge1([2], List, [2,8]).
uncaught exception: error(instantiation_error,(<)/2)



                                                       21
8.3.2 Tabular organization of long
procedures

   Long procedures are acceptable if they have some
    uniform structure.
   Such a form is a set of facts when a relation is
    effectively defined in the tabular(列表的) form.
   The advantages of such an organization of a long
    procedure are:
       Its structure is easily understood.
       Incrementability: it can be refined by simply adding
        new facts.
       It is easy to check and correct or modify by simply
        replacing some fact independently of other facts.




                                                               22
8.3.3 Commenting
   The main purpose of comments is to enable the user
    to use the program, to understand it and to possibly
    modify it.
   Long passages(一段) of comments should precede
    the code they refer to, while short comments should
    be interspersed with the code itself.




                                                      23
8.4 Debugging
   The basis for debugging aids is tracing.
   ‘Tracing a goal’ means that the information regarding the
    goal’s satisfaction is displayed during execution. This
    information includes:
        Entry information
        Exit information
        Re-entry information
   Such debugging aids are activated by system-dependent
    built-in predicates.
   A typical subset of such predicates is as follows:
    trace: trigger exhaustive tracing of goals that follow.
    notrace: stop further tracing.
    spy( P): specifies that a predicate P be traced.
    nospy( P): stops spying P.

                                                                24
8.4 Debugging
| ?- trace.
The debugger will first creep -- showing everything (trace)
yes
{trace}

| ?- merge1([2], [8], [2,8]).
     1 1 Call: merge1([2],[8],[2,8]) ?
     2 2 Call: 2<8 ?
     2 2 Exit: 2<8 ?
     3 2 Call: merge1([],[8],[8]) ?
     3 2 Exit: merge1([],[8],[8]) ?
     1 1 Exit: merge1([2],[8],[2,8]) ?
yes
{trace}

| ?- merge([2], [8], [2,8]).
     1 1 Call: merge([2],[8],[2,8]) ?
     2 2 Call: 2<8 ?
     2 2 Exit: 2<8 ?
     3 2 Call: merge([],[8],_114) ?
     3 2 Exit: merge([],[8],[8]) ?
     1 1 Exit: merge([2],[8],[2,8]) ?
(15 ms) yes
{trace}

| ?- notrace.
The debugger is switched off
                                                              25
yes
8.4 Debugging
| ?- spy( merge).
Spypoint placed on merge/3
The debugger will first leap -- showing spypoints (debug)
(15 ms) yes
{debug}

| ?- merge([2], [8], [2,8]).
 + 1 1 Call: merge([2],[8],[2,8]) ?
     2 2 Call: 2<8 ?
     2 2 Exit: 2<8 ?
 + 3 2 Call: merge([],[8],_114) ?
 + 3 2 Exit: merge([],[8],[8]) ?
 + 1 1 Exit: merge([2],[8],[2,8]) ?
yes
{debug}

| ?- merge1([2], [8], [2,8]).
yes
{debug}

| ?- nospy( merge).
Spypoint removed from merge/3
yes
{debug}


                                                            26
8.5 Improving efficiency
   Ideas for improving the efficiency of a program
    usually come from a deeper understanding of the
    problem.
   A more efficient algorithm can result from
    improvements of two kinds:
       Improving search efficiency by avoiding unnecessary
        backtracking and stopping the execution of useless
        alternatives as soon as possible.
       Using more suitable data structures to represent
        objects in the program, so that operations on objects
        can be implemented more efficiently.




                                                                27
8.5.1 Improving the efficiency of an
eight queens program

   In the program of Figure 4.7:
    member( Y, [1,2,3,4,5,6,7,8])

   The queens in adjacent columns will attach each
    other if they are not placed at least two squares
    apart in the vertical direction.

   According to this observation, we can rearrange the
    candidate coordinate values to improve the
    efficiency:
    member( Y, [1,5,2,6,3,7,4,8])



                                                        28
8.5.1 Improving the efficiency of an
eight queens program
% Figure 4.7 Program 1 for the eight queens problem.

solution( [] ).
solution( [X/Y | Others] ) :-
 solution( Others), member( Y, [1,2,3,4,5,6,7,8] ),
 noattack( X/Y, Others).
                         member( Y, [1,5,2,6,3,7,4,8] ),
noattack( _, [] ).
noattack( X/Y, [X1/Y1 | Others] ) :-
 Y =\= Y1, Y1-Y =\= X1-X, Y1-Y =\= X-X1, noattack( X/Y,
   Others).

member( Item, [Item | Rest] ).
member( Item, [First | Rest] ) :- member( Item, Rest).

% A solution template
template( [1/Y1,2/Y2,3/Y3,4/Y4,5/Y5,6/Y6,7/Y7,8/Y8] ).
                                                           29
8.5.2 Improving the efficiency in a map
coloring program
   The map coloring problem is to assign each country
    in a given map one of four given colors in such a
    way that no two neighboring countries are painted
    with the same color.
   Assume that a map is specified by the neighbor
    relation
    ngb( Country, Neighbors)
    where Neighbors is the list of countries bordering on
    Country.
   So the map of Europe, with 30 countries, would be
    specified as:
    ngb( albania(阿爾巴尼亞), [greece(希臘), macedonia(馬其頓),
    yugoslavia(南斯拉夫)]).
    ngb( andorra(安道爾共和國), [france, spain]).
    … (see http://www.csie.ntnu.edu.tw/~violet)
                                                            30
       8.5.2 Improving the efficiency in a map
       coloring program
          For a given map, the names of countries are fixed in
           advance, and the problem is to find the values for the
           colors.

          The problem is to find a proper instantiation of variables
           C1, C2, C3, etc. in the list:
           [albania/C1, andorra/C2, austria/C3,…]



ngb( albania(阿爾巴尼亞), [greece(希臘), macedonia(馬其頓), yugoslavia(南斯拉夫)]).
ngb( andorra(安道爾共和國), [france, spain]).
…




                                                                        31
8.5.2 Improving the efficiency in a map
coloring program
   Define the predicate
    colors( Country_color_list)
     which is true if the Country_color_list satisfies the map
    coloring constraint with respect to a given ngb relation.
   Let the four colors be yellow, blue, red and green.
   The condition that no two neighboring countries are of the
    same color can be formulated in Prolog as follows:
    colors([]).
    colors([ Country/Color | Rest]) :-
      colors( Rest),
      member( Color, [yellow, blue, red, green]),
      not( member( Country1/Color, Rest),
           neighbor( Country, Country1)).
    neighbor( Country, Country1) :-
      ngb( Country, Neighbors), member( Country1, Neighbors).
                                                            32
8.5.2 Improving the efficiency in a map
coloring program

   Assuming that the built-in predicate setof is
    available, one attempt to color Europe could be as
    follows.
       Define the relation
        country( C) :- ngb( C, _)
       Then the question for coloring Europe can be formulated
        as:
        ?- setof( Cntry/Color, country( Cntry),
        CountryColorList),
           colors( CountryColorList).
           The setof goal will construct a template country/color list
            for Europe in which uninstantiated variables stand for
            colors.
           Then the colors goal is supposed to instantiate the color.

       However, this attempt will probably fail because of
        inefficiency.
                                                                     33
    8.5.2 Improving the efficiency in a map
    coloring program
   For example:
    ngb( albania, [greece]).
    ngb( greece, [albania]).
    ngb( andorra, [france, spain]).
    ngb( france, [andorra, spain]).
    ngb( spain, [andorra, france]).
    country( C) :- ngb( C, _).
    colors([]).
    colors([ Country/Color | Rest]) :-
          colors( Rest),
          member( Color, [yellow, blue, red, green]),
          not((member( Country1/Color, Rest),
                neighbor( Country, Country1))).
    neighbor( Country, Country1) :-
          ngb( Country, Neighbors), member( Country1, Neighbors).

                                                                    34
           8.5.2 Improving the efficiency in a map
           coloring program
| ?- setof( Cntry/Color, country( Cntry), CountryColorList).
CountryColorList = [albania/_,andorra/_,france/_,greece/_,spain/_]
Yes

| ?- setof( Cntry/Color, country( Cntry), CountryColorList),
      colors( CountryColorList).

CountryColorList   =   [albania/blue,andorra/red,france/blue,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/red,andorra/red,france/blue,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/green,andorra/red,france/blue,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/blue,andorra/green,france/blue,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/red,andorra/green,france/blue,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/green,andorra/green,france/blue,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/blue,andorra/blue,france/red,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/red,andorra/blue,france/red,greece/yellow,spain/yellow] ? ;
CountryColorList   =   [albania/green,andorra/blue,france/red,greece/yellow,spain/yellow] ? ;
…
                                                        ngb(   albania, [greece]).
                                                        ngb(   greece, [albania]).
                                                        ngb(   andorra, [france, spain]).
                                                        ngb(   france, [andorra, spain]).
                                                        ngb(   spain, [andorra, france]).
                                                                                            35
8.5.2 Improving the efficiency in a map
coloring program

   Why inefficiency?
       Countries in the country/color list are arranged in
        alphabetical(照字母次序的) order, and this has nothing to do
        with their geographical(地理的) arrangement.
       This may easily lead to a situation in which a country that is to
        be colored is surrounded by many other countries, already
        painted with all four available colors.
       Then backtracking is necessary, which leads to inefficiency.
       It is clear that the efficiency depends on the order in which the
        countries are colored.
       Suggestion: start with some country that has many neighbors,
        and then proceed to the neighbors, then to the neighbors of
        neighbors, etc.
           For example: Germany has most neighbors in Europe.




                                                                      36
8.5.2 Improving the efficiency in a map
coloring program
    The following procedure, makelist, can construct a properly ordered list of
     countries.
    Germany has to be put at the end of the list and other countries have to be
     added at the front of the list.
    It starts the construction with some specified country (Germany in our case)
     and collects the countries into a list called Closed.
    Each country is first put into another list, called Open, before it is
     transferred to Closed.
    Each time that a country is transferred from Open to Closed, its neighbors
     are added to Open.

    makelist( List):- collect( [germany], [], List).
    collect([], Closed, Closed).
    collect([ X | Open], Closed, List):-
          member( X, Closed),!, collect( Open, Closed, List).
    collect([ X | Open], Closed, List):-
         ngb( X, Ngbs), conc( Ngbs, Open, Open1),
         collect( Open1, [X|Closed], List).

                                                                              37
8.5.2 Improving the efficiency in a map
coloring program
ngb(   albania,[greece]).
ngb(   greece, [albania, germany]).
ngb(   andorra,[france, germany, spain]).
ngb(   france, [andorra, germany, spain]).
ngb(   spain, [andorra, france, germany]).
ngb(   germany, [andorra, france, greece, spain]).

con_list([], L).
con_list( [X| L1], [X/_|L3]) :- con_list( L1, L3).

makelist( List):- collect( [germany], [], List).
collect([], Closed, Closed).
collect([ X | Open], Closed, List):-
      member( X, Closed),!, collect( Open, Closed, List).
collect([ X | Open], Closed, List):-
     ngb( X, Ngbs), conc( Ngbs, Open, Open1),
     collect( Open1, [X|Closed], List).


                                                            38
        8.5.2 Improving the efficiency in a map
        coloring program
| ?- makelist( L).
L = [albania,greece,spain,france,andorra,germany]
Yes

| ?- makelist( L), con_list( L, L1).
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/_,greece/_,spain/_,france/_,andorra/_,germany/_|_]
(16 ms) yes

| ?- makelist( L), con_list( L, L1), colors( L1).
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/yellow,greece/blue,spain/green,france/red,andorra/blue,germany/yellow] ? ;
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/red,greece/blue,spain/green,france/red,andorra/blue,germany/yellow] ? ;
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/green,greece/blue,spain/green,france/red,andorra/blue,germany/yellow] ? ;
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/yellow,greece/red,spain/green,france/red,andorra/blue,germany/yellow] ? ;
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/blue,greece/red,spain/green,france/red,andorra/blue,germany/yellow] ? ;
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/green,greece/red,spain/green,france/red,andorra/blue,germany/yellow] ? ;
L = [albania,greece,spain,france,andorra,germany]
L1 = [albania/yellow,greece/green,spain/green,france/red,andorra/blue,germany/yellow] ?
 …
                                                                                    39
       8.5.3 Improving efficiency of list
       concatenation by difference lists
          In our programs so far, the concatenation of list has been
           programmed as:
            conc([], L, L).
            conc([X| L1], L2, [X| L3]) :- conc( L1, L2, L3).
          This is inefficient when the first list is long.
          The following example explains why?
            ?- conc([a,b,c],[d,e],L).
            This produces the following sequence of goals:

conc([a,b,c],[d,e],L)                      {trace}
conc([b,c],[d,e],L’)    where L = [a|L’]   | ?- conc([a,b,c],[d,e],L).
                                                1 1 Call: conc([a,b,c],[d,e],_26) ?
conc([c],[d,e],L’’)   where L’ = [b|L’’]
                                                2 2 Call: conc([b,c],[d,e],_59) ?
conc([],[d,e],L’’’) where L’’ = [c|L’’’]        3 3 Call: conc([c],[d,e],_86) ?
true                where L’’’ = [d,e]          4 4 Call: conc([],[d,e],_113) ?
                                                4 4 Exit: conc([],[d,e],[d,e]) ?
                                                3 3 Exit: conc([c],[d,e],[c,d,e]) ?
                                                2 2 Exit: conc([b,c],[d,e],[b,c,d,e]) ?
                                                1 1 Exit: conc([a,b,c],[d,e],[a,b,c,d,e]) ?
                                           L = [a,b,c,d,e]
                                           (62 ms) yes
                                           {trace}                                       40
8.5.3 Improving efficiency of list
concatenation by difference lists
   The program scans all of the first list until the empty list is
    encountered.
   If we could simply skip the whole of the first list in a single
    step, then the program will be more efficient.
   To do this, we need to know where the end of a list is; that
    is, we need another representation of lists.
   One solution is the data sturcture called difference lists.
   For example:
      The list [a,b,c] can be represented by the two lists:
         L1 = [a,b,c,d,e]
         L2 = [d,e]
         Such a pair of lists, L1 – L2, represents the ‘difference’
         between L1 and L2.
        This only works under the condition that L2 is a suffix of
         L1.

                                                                41
8.5.3 Improving efficiency of list
concatenation by difference lists
   Note the same list can be represented by
    several ’difference pairs’.
   For example: the list [a,b,c] can be represented by
       [a,b,c] – []
    or [a,b,c,d,e] – [d,e]
    or [a,b,c,d,e|T] – [d,e|T]
    or [a,b,c|T] – [T]
     where T is any list.
   The empty list is represented by any pair of the form L – L.
   As the second member of the pair indicates the end of the
    list, the end is directly accessible.
   This can be used for an efficient implementation of
    concatenation.



                                                             42
8.5.3 Improving efficiency of list
concatenation by difference lists
     A1              Z1 A2               Z2


              L1                 L2

                        L3
   The corresponding concatenation relation translates
    into Prolog as the fact:
    concat( A1-Z1, Z1-Z2, A1-Z2)

    ?- concat([a,b,c|T1]-T1, [d,e|T2]-T2, L).
    T1 = [d,e|T2]
    L = [a,b,c,d,e|T2]-T2
    (concat is not a built-in predicate in GNU Prolog)

                                                         43
8.5.4 Last call optimization and
accumulators
   Recursive call normally take up memory space, which
    is only freed after the return from the call.
   A large number of nested recursive calls may lead to
    shortage of memory.
   In special cases, it is possible to execute nested
    recursive calls without requiring extra memory.
   In such a case a recursive procedure has a special
    form, call tail recursion.
   A tail-recursive procedure
       It only has one recursive call, and the call appears as
        the last goal of the last clause in the procedure.
       The goals preceding the recursive call must be
        deterministic, so that no backtracking occurs after this
        last call.

                                                               44
8.5.4 Last call optimization and
accumulators
   Typically a tail-recursive procedure looks like this:
    p(...) :- ...     % No recursive call in the body of this clause
    p(...) :- ...     % No recursive call in the body of this clause
    p(...) :- ..., !, % The cut ensure no backtracking
             p(...). % Tail-recursive call

   In the cases of such tail-recursive procedures, no information is
    needed upon the return from a call.
   Therefore such recursion can be carried out simply as iteration in
    which a next cycle in the loop does not require additional memory.
   A Prolog system will notice such an opportunity of saving memory
    and realize tail recursion as iteration.
   This is called tail recursion optimization, or last call optimization.




                                                                       45
8.5.4 Last call optimization and
accumulators
   For example:
    Consider the predicate for computing the sum of a list of numbers
    sumlist( List, Sum)
    It can be defined as:
    sumlist([], 0).
    sumlist([First |Rest], Sum) :-
          sumlist( Rest, Sum0), Sum is First + Sum0.
      This is not tail recursive, so the summation over a very long
        list will require many recursive calls and therefore a lot of
        memory.
    sumlist1( List, Sum) :- sumlist1( List, 0, Sum).
    sumlist1([], Sum, Sum).
    sumlist1([First|Rest], PartialSum, TotalSum) :-
         NewPartialSum is PartialSum + First,
         sumlist1( Rest, NewPartialSum, TotalSum).
      This is now tail recursive and Prolog can benefit from last call
        optimization.
                                                                     46
8.5.4 Last call optimization and
accumulators
{trace}                                   {trace}
| ?-sumlist([1,3,5,7], Sum).              | ?- sumlist1([1,3,5,7], Sum).
     1 1 Call: sumlist([1,3,5,7],_24) ?        1 1 Call: sumlist1([1,3,5,7],_24) ?
     2 2 Call: sumlist([3,5,7],_93) ?          2 2 Call: sumlist1([1,3,5,7],0,_24) ?
     3 3 Call: sumlist([5,7],_117) ?           3 3 Call: _121 is 0+1 ?
     4 4 Call: sumlist([7],_141) ?             3 3 Exit: 1 is 0+1 ?
     5 5 Call: sumlist([],_165) ?              4 3 Call: sumlist1([3,5,7],1,_24) ?
     5 5 Exit: sumlist([],0) ?                 5 4 Call: _174 is 1+3 ?
     6 5 Call: _193 is 7+0 ?                   5 4 Exit: 4 is 1+3 ?
     6 5 Exit: 7 is 7+0 ?                      6 4 Call: sumlist1([5,7],4,_24) ?
     4 4 Exit: sumlist([7],7) ?                7 5 Call: _227 is 4+5 ?
     7 4 Call: _222 is 5+7 ?                   7 5 Exit: 9 is 4+5 ?
     7 4 Exit: 12 is 5+7 ?                     8 5 Call: sumlist1([7],9,_24) ?
     3 3 Exit: sumlist([5,7],12) ?             9 6 Call: _280 is 9+7 ?
     8 3 Call: _251 is 3+12 ?                  9 6 Exit: 16 is 9+7 ?
     8 3 Exit: 15 is 3+12 ?                   10 6 Call: sumlist1([],16,_24) ?
     2 2 Exit: sumlist([3,5,7],15) ?          10 6 Exit: sumlist1([],16,16) ?
     9 2 Call: _24 is 1+15 ?                   8 5 Exit: sumlist1([7],9,16) ?
     9 2 Exit: 16 is 1+15 ?                    6 4 Exit: sumlist1([5,7],4,16) ?
     1 1 Exit: sumlist([1,3,5,7],16) ?         4 3 Exit: sumlist1([3,5,7],1,16) ?
                                               2 2 Exit: sumlist1([1,3,5,7],0,16) ?
Sum = 16                                       1 1 Exit: sumlist1([1,3,5,7],16) ?

yes                                       Sum = 16
{trace}                                   yes
                                          {trace}
                                                                                       47
8.5.4 Last call optimization and
accumulators
   Another example:
    reverse( List, ReversedList)
    ReversedList has the same elements as List, but in the reverse
    order.
    It can be defined as:
    reverse([], []).
    reverse([X |Rest], Reversed) :-
          reverse( Rest, RevRest), conc( RevRest, [X], Reversed).
      This is not tail recursive.

      The program is very inefficient because to reverse a list of
        length n, it require time proportional to n2.
    reverse1( List, Reversed) :- reverse1( List, [], Reversed).
    reverse1([], Reversed, Reversed).
    reverse1([X|Rest], PartReversed, TotalReversed) :-
         reverse1( Rest, [X|PartReversed], TotalReversed).
      This is efficient and tail recursive.


                                                                48
8.5.4 Last call optimization and
accumulators
| ?- reverse([1,3,5,7], List).
     1 1 Call: reverse([1,3,5,7],_24) ?         | ?- reverse1([1,3,5,7], List).
     2 2 Call: reverse([3,5,7],_93) ?                1 1 Call: reverse1([1,3,5,7],_24) ?
     3 3 Call: reverse([5,7],_117) ?                 2 2 Call: reverse1([1,3,5,7],[],_24) ?
     4 4 Call: reverse([7],_141) ?                   3 3 Call: reverse1([3,5,7],[1],_24) ?
     5 5 Call: reverse([],_165) ?                    4 4 Call: reverse1([5,7],[3,1],_24) ?
     5 5 Exit: reverse([],[]) ?                      5 5 Call: reverse1([7],[5,3,1],_24) ?
     6 5 Call: conc([],[7],_193) ?                   6 6 Call: reverse1([],[7,5,3,1],_24) ?
     6 5 Exit: conc([],[7],[7]) ?                    6 6 Exit: reverse1([],[7,5,3,1],[7,5,3,1]) ?
     4 4 Exit: reverse([7],[7]) ?                    5 5 Exit: reverse1([7],[5,3,1],[7,5,3,1]) ?
     7 4 Call: conc([7],[5],_222) ?                  4 4 Exit: reverse1([5,7],[3,1],[7,5,3,1]) ?
     8 5 Call: conc([],[5],_209) ?                   3 3 Exit: reverse1([3,5,7],[1],[7,5,3,1]) ?
     8 5 Exit: conc([],[5],[5]) ?                    2 2 Exit: reverse1([1,3,5,7],[],[7,5,3,1]) ?
     7 4 Exit: conc([7],[5],[7,5]) ?                 1 1 Exit: reverse1([1,3,5,7],[7,5,3,1]) ?
     3 3 Exit: reverse([5,7],[7,5]) ?           List = [7,5,3,1]
     9 3 Call: conc([7,5],[3],_279) ?           yes
    10 4 Call: conc([5],[3],_266) ?             {trace}
    11 5 Call: conc([],[3],_293) ?
    11 5 Exit: conc([],[3],[3]) ?
    10 4 Exit: conc([5],[3],[5,3]) ?
     9 3 Exit: conc([7,5],[3],[7,5,3]) ?
     2 2 Exit: reverse([3,5,7],[7,5,3]) ?
    12 2 Call: conc([7,5,3],[1],_24) ?
    13 3 Call: conc([5,3],[1],_351) ?
    14 4 Call: conc([3],[1],_378) ?
    15 5 Call: conc([],[1],_405) ?
    15 5 Exit: conc([],[1],[1]) ?
    14 4 Exit: conc([3],[1],[3,1]) ?
    13 3 Exit: conc([5,3],[1],[5,3,1]) ?
    12 2 Exit: conc([7,5,3],[1],[7,5,3,1]) ?
     1 1 Exit: reverse([1,3,5,7],[7,5,3,1]) ?
List = [7,5,3,1]
yes                                                                                             49
{trace}
8.5.5 Simulating arrays with arg
   The list structure is the easiest representation for sets
    in Prolog.
   However, accessing an item in a list is done by
    scanning the list.
   For long lists this is very inefficient.
   In such cases, array structures are the most effective
    because they enable direct access to a required
    element.
   There is no array facility in Prolog, but array can be
    simulated to some extent by using the built-in
    predicates arg and functor.




                                                          50
8.5.5 Simulating arrays with arg
   The goal
    functor( A, f, 100)
    make a structure with 100 elements:
    A = f(_, _, _, ...)

   The goal
    arg( 60, A, 1)
    means the initial value of the 60th element of array A
    is 1. ( A[60] := 1)
       Then, arg (60, A, X) means X := A[60].




                                                       51
8.5.5 Simulating arrays with arg
   For example: the eight queens problem in Chapter 4
    (Figure 4.11)

solution( Ylist) :-
    sol( Ylist, [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8],
                [-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7], % Du
                [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] ).

sol( [], [], Dy, Du, Dv).
sol( [Y | Ylist], [X | Dx1], Dy, Du, Dv) :-
     del( Y, Dy, Dy1), U is X-Y, del( U, Du, Du1), V is X+Y,
     del( V, Dv, Dv1), sol( Ylist, Dx1, Dy1, Du1, Dv1).

del( Item, [Item | List], List).
del( Item, [First | List], [First | List1] ) :-
    del( Item, List, List1).

                                                               52
8.5.5 Simulating arrays with arg
   For example: the eight queens problem in Chapter 4
    (Figure 4.11)
       The program places a next queen into a currently free
        column (X-coordinate), row (Y-coordinate), upward
        diagonal (U-coordinate) and downward diagonal( V-
        coordinate).
       The sets of currently free coordinates are maintained,
        and when a new queen is placed the corresponding
        occupied coordinates are deleted from these sets.
       The deletion of U and V coordinates in Figure 4.11
        involves scanning the corresponding lists, which is
        inefficient.
       Efficiency can easily be improved by simulated arrays.




                                                             53
8.5.5 Simulating arrays with arg
   For example: the eight queens problem in Chapter 4
    (Figure 4.11)
       The set of all 15 upward diagonals can be represented
        by:
         Du = u(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)
                 [-7,-6,-5,-4,-3,-2,-1,0, 1, 2, 3, 4, 5, 6, 7], % Du
       Consider placing a queen at the square (X, Y) = (1,1).
         u = X-Y = 0
         the 8th component of Du is set to 1
         arg( 8, Du, 1) % Here X = 1.
         Du = u(_,_,_,_,_,_,_,1,_,_,_,_,_,_,_)
         if later a queen is attempted to be placed at (X,Y)=(3,3)
         u = X-Y = 0
         arg( 8, Du, 3) % Here X = 3.
         This will fail beacuse the 8th component of Du is already 1.
           So the program will not allow another queen to be placed on
        the same diagonal.

                                                                       54
8.5.6 Improving efficiency by asserting
derived facts
   Sometimes during computation the same goal has to be satisfied
    again and again.
   As Prolog has no special machanism to discover such situations
    whole computation sequences are repeated.
   For example, consider a program to computer the Nth Fibonacci
    number for a given N.
      The Fibonacci sequence is:

        1, 1, 2, 3, 5, 8, 13,...
      Each number in the squence is the sum of the previous two
        number.
      We can define a predicate

        fib( N, F)

       fib(1,1).
       fib(2,1).
       fib( N, F) :- N > 2, N1 is N – 1, fib(N1, F1),
                            N2 is N – 2, fib(N2, F2), F is F1 + F2.
                                                                 55
         8.5.6 Improving efficiency by asserting
         derived facts
                                         f(6)                                  | ?- fib( 6, F).
                                                                               F=8?;
                                            +                                  (16 ms) no

                            f(5)                                 f(4)

                             +                                   +


              f(4)                     f(3)               f(3)          f(2)

              +                         +                  +             1


       f(3)          f(2)      f(2)         f(1)   f(2)        f(1)

        +             1            1          1      1           1


f(2)        f(1)                   In this example, the third Fibonacci number, f(3),
                                   is needed in three places and the same computation
 1            1                    is repeated each time.
                                                                                                  56
8.5.6 Improving efficiency by asserting
derived facts
   A better idea is to use the built-in procedure asserta and
    to add this results as facts to the database.
       fib2(1,1).
       fib2(2,1).
       fib2( N, F) :- N > 2, N1 is N – 1, fib2(N1, F1),
                             N2 is N – 2, fib2(N2, F2),
                             F is F1 + F2,
                             asserta(fib2(N, F)).
      (uncaught exception: error(permission_error
       (modify,static_procedure,fib2/2),asserta/1))


      fib2(1,1).
      fib2(2,1).
      fib2(N,F) :- fib3(N,F).
      fib2(N,F) :- N>2,N1 is N-1,fib2(N1, F1),N2 is N-
       2,fib2(N2,F2),F is F1+F2, asserta( fib3(N, F)).

                                                             57
8.5.6 Improving efficiency by asserting
derived facts
                                      f(6)

                                        +

                            f(5)                    f(4)

                                                3, retrieved
                             +

              f(4)                  f(3)

              +                  2, retrieved

       f(3)          f(2)

        +             1


f(2)        f(1)

 1            1
                                                               58
8.5.6 Improving efficiency by asserting
derived facts
   Asserting intermediate results, also called caching(是一種將先前讀進
    來的資料留著,預備下一次讀取的技術), is a standard technique for
    avoiding repeated computations.
   It should be noted that we can preferably avoid repeated
    computation by using another algorithm, rather than by asserting
    intermediate results.
   The other algorithm will lead to a program that is more diffcult to
    understand, but more efficient to execute.
   The idea is not to define the Nth Fibonacci number simply as the
    sum of its two predecessors and leave the recursive calls to
    unfold(展開) the whole computation ‘downwards’ to the two initial
    Fibonacci numbers.
   Instead, we can work ‘upwards’ starting with the initial two
    numbers, and compute the numbers in the sequence one by one in
    the forward direction.
   We have to stop when we have computed the Nth number.



                                                                    59
         8.5.6 Improving efficiency by asserting
         derived facts

                                          NextF2
     1       1      2        F1      F2                          F
     1       2      3                      NextM                 N



   We can define a predicate
      forwardfib( M, N, F1, F2, F)
    Here, F1 and F2 are the (M-1)st and the Mth Fibonacci numbers,
      and F is the Nth Fibonacci number.

    fib3(N,F) :- forwardfib(2, N, 1, 1, F).
    forwardfib(M,N,F1, F2, F2) :- M >= N.          Tail-recursive call
    forwardfib(M,N,F1, F2, F) :- M < N, NextM is M+1,
            NextF2 is F1 + F2, forwardfib( NextM, N, F2, NextF2, F).
                                                                     60
8.5.6 Improving efficiency by asserting
derived facts
{trace}                                        | ?- fib3( 6, F).
| ?- fib3( 6, F).
     1 1 Call: fib3(6,_16) ?
                                               F=8?
     2 2 Call: forwardfib(2,6,1,1,_16) ?       yes
     3 3 Call: 2>=6 ?
     3 3 Fail: 2>=6 ?
     3 3 Call: 2<6 ?
     3 3 Exit: 2<6 ?
     4 3 Call: _140 is 2+1 ?
     4 3 Exit: 3 is 2+1 ?
     5 3 Call: _168 is 1+1 ?
     5 3 Exit: 2 is 1+1 ?
     6 3 Call: forwardfib(3,6,1,2,_16) ?
     7 4 Call: 3>=6 ?
     7 4 Fail: 3>=6 ?
                                      fib3(N,F) :- forwardfib(2, N, 1, 1, F).
     7 4 Call: 3<6 ?                  forwardfib(M,N,F1, F2, F2) :- M >= N.
     7 4 Exit: 3<6 ?                  forwardfib(M,N,F1, F2, F) :- M < N,
     8 4 Call: _248 is 3+1 ?              NextM is M+1,
     8 4 Exit: 4 is 3+1 ?                 NextF2 is F1 + F2,
                                          forwardfib( NextM, N, F2, NextF2, F).
     9 4 Call: _276 is 1+2 ?
     9 4 Exit: 3 is 1+2 ? …
                                                                          61
Exercise

   Exercise 8.5
       The following procedure computes the maximum value
        in a list of numbers:
         max([X],X).
         max([X|Rest], Max) :-
            max(Rest, MaxRest),
            (MaxRest >= X,!, Max = MaxRest
             ;
             Max = X).
         Transform this into a tail-recursive procedure.
         Hint: Introduce accumulator argument MaxSoFar.


                                                           62

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:3
posted:11/2/2011
language:English
pages:62