How to enumerate combinations using DCGs with CLP(FD) and multiple constraints

前端 未结 2 415
被撕碎了的回忆
被撕碎了的回忆 2020-12-06 22:25

This question starts from Mat\'s answer to Algorithm improvement for enumerating binary trees which has only one input value that determines the number of all nodes for the

2条回答
  •  南笙
    南笙 (楼主)
    2020-12-06 23:05

    Basic tree expression parser with counters

    Assuming a compound term representation for binary-unary trees (e.g., b(t,u(b(t,t,)))), here is a basic parser. CLP(FD) is generally recommended for reasoning over integers.

    expression(U, B, E) :-
        terminal(U, B, E).
    expression(U, B, E) :-
        unary(U, B, E).
    expression(U, B, E) :-
        binary(U, B, E).
    
    terminal(0, 0, t).
    
    unary(U, B, u(E)) :-
        U1 #>= 0,
        U #= U1 + 1,
        expression(U1, B, E).
    
    binary(U, B, b(E1,E2)) :-
        U1 #>= 0, U2 #>= 0,
        U #= U1 + U2,
        B1 #>= 0, B2 #>= 0,
        B #= B1 + B2 + 1,
        expression(U1, B1, E1),
        expression(U2, B2, E2).
    

    There are a couple of things I've done intentionally here. One is to use CLP(FD) to give me more relational reasoning over the counts for unary and binary terms. The other thing I've done is put the simpler expression/3 clause first which doesn't do recursion. That way, Prolog will hit terminals first in the process of exploring possible solutions.

    Example executions:

    | ?- expression(1,2,E).
    
    E = u(b(t,b(t,t))) ? a
    
    E = u(b(b(t,t),t))
    
    E = b(t,u(b(t,t)))
    
    E = b(t,b(t,u(t)))
    
    E = b(t,b(u(t),t))
    
    E = b(u(t),b(t,t))
    
    E = b(u(b(t,t)),t)
    
    E = b(b(t,t),u(t))
    
    E = b(b(t,u(t)),t)
    
    E = b(b(u(t),t),t)
    
    (1 ms) no
    


    | ?- expression(U, B, E).
    
    B = 0
    E = t
    U = 0 ? ;
    
    B = 0
    E = u(t)
    U = 1 ? ;
    
    B = 0
    E = u(u(t))
    U = 2 ? ;
    ...
    

    Using a DCG for sequential representation

    A DCG is used for parsing sequences. The compound term can be parsed as a sequence of tokens or characters, which can, through the use of a DCG, be mapped to the compound term itself. We might, for example, represent the compound tree term b(t,u(b(t,t))) as [b, '(', t, u, '(', b, '(', t, t, ')', ')', ')']. Then we can use a DCG and include that representation. Here's a DCG that reflects the above implementation with this sequence format:

    expression(U, B, E) -->
        terminal(U, B, E) |
        unary(U, B, E) |
        binary(U, B, E).
    
    terminal(0, 0, t) --> [t].
    
    unary(U, B, u(E)) -->
        [u, '('],
        { U1 #>= 0, U #= U1 + 1 },
        expression(U1, B, E),
        [')'].
    
    binary(U, B, b(E1, E2)) -->
        [b, '('],
        { U1 #>= 0, U2 #>= 0, U #= U1 + U2, B1 #>= 0, B2 #>= 0, B #= B1 + B2 + 1 },
        expression(U1, B1, E1),
        expression(U2, B2, E2),
        [')'].
    

    Again, I put the terminal//3 as the first course of query for expression//3. You can see the parallelism between this and the non-DCG version. Here are example executions.

    | ?-  phrase(expression(1,2,E), S).
    
    E = u(b(t,b(t,t)))
    S = [u,'(',b,'(',t,b,'(',t,t,')',')',')'] ? a
    
    E = u(b(b(t,t),t))
    S = [u,'(',b,'(',b,'(',t,t,')',t,')',')']
    
    E = b(t,u(b(t,t)))
    S = [b,'(',t,u,'(',b,'(',t,t,')',')',')']
    
    E = b(t,b(t,u(t)))
    S = [b,'(',t,b,'(',t,u,'(',t,')',')',')']
    
    E = b(t,b(u(t),t))
    S = [b,'(',t,b,'(',u,'(',t,')',t,')',')']
    
    E = b(u(t),b(t,t))
    S = [b,'(',u,'(',t,')',b,'(',t,t,')',')']
    
    E = b(u(b(t,t)),t)
    S = [b,'(',u,'(',b,'(',t,t,')',')',t,')']
    
    E = b(b(t,t),u(t))
    S = [b,'(',b,'(',t,t,')',u,'(',t,')',')']
    
    E = b(b(t,u(t)),t)
    S = [b,'(',b,'(',t,u,'(',t,')',')',t,')']
    
    E = b(b(u(t),t),t)
    S = [b,'(',b,'(',u,'(',t,')',t,')',t,')']
    
    no
    
    | ?-  phrase(expression(U,B,E), S).
    
    B = 0
    E = t
    S = [t]
    U = 0 ? ;
    
    B = 0
    E = u(t)
    S = [u,'(',t,')']
    U = 1 ? ;
    
    B = 0
    E = u(u(t))
    S = [u,'(',u,'(',t,')',')']
    U = 2 ?
    ...
    

    Hopefully this answers question #1, and perhaps #4 by example. The general problem of converting any set of predicates to a DCG, though, is more difficult. As I mentioned above, DCG is really for handling sequences.

    Using length/2 to control solution order

    In answer to #2, now that we have a DCG solution that will generate solutions properly, we can control the order of solutions given by using length/2, which will provide solutions in order of length rather than depth-first. You can constrain the length right from the beginning, which is more effective and efficient than constraining the length at each step in the recursion, which is redundant:

    ?- length(S, _), phrase(expression(U,B,E), S).
    
    B = 0
    E = t
    S = [t]
    U = 0 ? ;
    
    B = 0
    E = u(t)
    S = [u,'(',t,')']
    U = 1 ? ;
    
    B = 1
    E = b(t,t)
    S = [b,'(',t,t,')']
    U = 0 ? ;
    
    B = 0
    E = u(u(t))
    S = [u,'(',u,'(',t,')',')']
    U = 2 ? ;
    
    B = 1
    E = u(b(t,t))
    S = [u,'(',b,'(',t,t,')',')']
    U = 1 ? ;
    
    B = 1
    E = b(t,u(t))
    S = [b,'(',t,u,'(',t,')',')']
    U = 1 ? ;
    
    B = 1
    E = b(u(t),t)
    S = [b,'(',u,'(',t,')',t,')']
    U = 1 ? 
    ...
    

    If I were using the sequential representation of the unary-binary tree for constraining solutions, not for parsing, I would get rid of the parentheses since they aren't necessary in the representation:

    unary(U, B, u(E)) -->
        [u],
        { U1 #>= 0, U #= U1 + 1 },
        expression(U1, B, E).
    
    binary(U, B, b(E1, E2)) -->
        [b],
        { U1 #>= 0, U2 #>= 0, U #= U1 + U2, B1 #>= 0, B2 #>= 0, B #= B1 + B2 + 1 },
        expression(U1, B1, E1),
        expression(U2, B2, E2).
    

    It's probably a little more efficient since there are a fewer number of list lengths that correspond to invalid sequences. This results in:

    | ?- length(S, _), phrase(expression(U, B, E), S).
    
    B = 0
    E = t
    S = [t]
    U = 0 ? ;
    
    B = 0
    E = u(t)
    S = [u,t]
    U = 1 ? ;
    
    B = 0
    E = u(u(t))
    S = [u,u,t]
    U = 2 ? ;
    
    B = 1
    E = b(t,t)
    S = [b,t,t]
    U = 0 ? ;
    
    B = 0
    E = u(u(u(t)))
    S = [u,u,u,t]
    U = 3 ? ;
    
    B = 1
    E = u(b(t,t))
    S = [u,b,t,t]
    U = 1 ? ;
    
    B = 1
    E = b(t,u(t))
    S = [b,t,u,t]
    U = 1 ? ;
    
    B = 1
    E = b(u(t),t)
    S = [b,u,t,t]
    U = 1 ? ;
    
    B = 0
    E = u(u(u(u(t))))
    S = [u,u,u,u,t]
    U = 4 ? ;
    
    B = 1
    E = u(u(b(t,t)))
    S = [u,u,b,t,t]
    U = 2 ? ;
    ...
    

    So, if you have a recursive definition of a general term, Term, which can be expressed as a sequence (thus, using a DCG), then length/2 can be used in this way to constrain the solutions and order them by length of sequence, which corresponds to some ordering of the original terms. Indeed, the introduction of the length/2 may prevent your DCG from infinitely recursing without presenting any solutions, but I would still prefer to have the DCG be better behaved to start with by attempting to organize the logic to walk the terminals first.

提交回复
热议问题