Reversible tree length relation

后端 未结 2 1573
走了就别回头了
走了就别回头了 2021-01-13 04:47

I\'m trying to write reversible relations in \"pure\" Prolog (no is, cut, or similar stuff. Yes it\'s homework), and I must admit I don\'t have a clue how. I do

相关标签:
2条回答
  • 2021-01-13 05:33

    Interesting problem.

    Here's what I would do. Basically, your relation isn't reversible since add/3 isn't. What I essentially did was, replace counting with numbers with counting with lists of a size that corresponds to the number of leaves - which is reversible (well, append/3 and length/2 are reversible).

    Is this something like what you need? The posted code is runnable under YAP.

    PS: this might not be the most concise solution, but it's from the top of my head. I'll try to help if you have any further questions.

    :-  use_module(library(lists)).
    
    do_tree_len(n(_,leaf,leaf), [X]).
    do_tree_len(n(op,G,D), [X1,X2|T]) :-
        append(TG, TD, [X1,X2|T]),
        TG \= [X1,X2|T], % To prevent infinite loops, when TG or TD is []
        TD \= [X1,X2|T],
        do_tree_len(G, TG),
        do_tree_len(D, TD).
    
    tree_len(Tree, N):-
        length(L, N),
        do_tree_len(Tree, L).
    
    0 讨论(0)
  • 2021-01-13 05:43

    Reason for non-termination

    First, let us try to understand why your definition is not reversible. I will use a failure-slice to better explain it. So consider the query tree_len(X,1). At first sight, everything is perfect, you get even a nice answer!

    ?- tree_len(T,1).
    T = n(_G267, leaf, leaf) 
    

    But never ask for another answer, for it will loop:

    ?- tree_len(T,1).
    T = n(_G267, leaf, leaf) ;
    ** LOOPS **
    

    So the answer we got was a bit of a distraction. It at first glance looked like everything was OK, and only on backtracking the real problem was encountered. This is something to get accustomed to in Prolog. Evidently, your query using 3 was better chosen. But that was luck.

    There is an easy way to ensure this in general. Simply add the extra goal false to the query. Adding false means, that we are no longer interested in any answers, since it can no longer succeed. In this manner all distraction is removed, and we face the problem directly:

    ?- tree_len(T,1), false.
    ** LOOPS **
    

    So, where did this loop come from?

    In pure, monotonic Prolog programs (such as this one), we can localize a reason for non-termination by adding some goals false into our program. If the resulting program (called failure-slice) does not terminate, then the original program does not terminate either. This is the minimal failure-slice for our query:

    ?- tree_len(T,1), false.
    
    tree_len(n(_,leaf,leaf),1) :- false.
    tree_len(n(op,G,D),N) :-
        tree_len(G,TG), false,
        tree_len(D,TD),
        N is TG+TD.
    

    There is not much left of our program! It is this tiny fragment that is responsible for the non-termination. If we want to fix the problem, we have to do something in that tiny part. Everything else is futile.

    So what we need to do is to somehow change the program such that this fragment no longer loops.

    Actually, we have two choices, we could use successor arithmetics or constraints like clpfd. The former is available in any Prolog system, the latter is provided only in some like SWI, YAP, SICStus, GNU, B.

    Using successor-arithmetics

    Now, 3 is represented by s(s(s(0))).

    tree_lensx(T, s(N)) :-
       tree_lendiff(T, N,0).
    
    tree_lendiff(n(_,leaf,leaf), N,N).
    tree_lendiff(n(op,G,D), s(N0),N) :-
       tree_lendiff(G, N0,N1),
       tree_lendiff(D, N1,N).
    

    I used here several common coding techniques.

    Differences

    The actual relation is tree_lendiff/3 which represents a natural number not by one argument, but rather using two. The actual number is the difference between both. In this manner it is possible to retain the definition reversible.

    Avoiding left-recursion

    The other technique was to avoid left-recursion. The length described by tree_lendiff/3 actually is the length minus one. Remember the failure-slice we got first? The very same failure-slice would be present here too! However by "shifting" the length by one, the head of the recursive rule now ensures termination.

    Using library(clpfd)

    Originally, constrains over finite domains were developed to solve combinatorial problems. But you can use them also to get reversible arithmetics. The implementation in SWI and YAP even goes that far that it compiles into code which often equals in efficiency to the traditional non-reversible (is)/2 while still being reversible.

    :- use_module(library(clpfd)).
    
    tree_fdlen(n(_,leaf,leaf),1).
    tree_fdlen(n(op,G,D),N) :-
       N #= TG+TD,
       TG #>= 1,
       TD #>= 1,
       tree_fdlen(G,TG),
       tree_fdlen(D,TD).
    

    This program more closely corresponds to your original definition. Nevertheless, remark the two goals TG #>= 1 and TD #>= 1 which added redundant information to ensure the termination of this program.

    We can now enumerate all trees of a certain range like so:

    ?- Size in 0..4, tree_fdlen(T, Size).
    Size = 1, T = n(_A,leaf,leaf) ;
    Size = 2, T = n(op,n(_A,leaf,leaf),n(_B,leaf,leaf)) ;
    Size = 3, T = n(op,n(_A,leaf,leaf),n(op,n(_B,leaf,leaf),n(_C,leaf,leaf))) ;
    Size = 4, ... ;
    Size = 4, ... ;
    Size = 3, ... ;
    Size = 4, ... ;
    Size = 4, ... ;
    Size = 4, ... ;
    false.
    

    Note the exact order of the answer substitutions! It's not just going 1,2,3,4! Rather, answers are found in some order. It does not matter which one, as long as we are only interested in finding all solutions!

    0 讨论(0)
提交回复
热议问题