Prolog accumulator reversing

2019-09-05 23:27发布

I'm having a problem where my answer for every Prolog problem I'm posed ends up with the answer list being reversed. It's because I was shown how to use an accumulator to reverse a list, and now the only way I seem to be able to solve a problem is to use a similar program, which gives the answer, but also reverses the list.

For example, when asked to write a program which replaces all instances of the second argument inside the first argument with the third argument, and gives the resulting list as it's fourth argument, I wrote this code:

replace(L, A, B, X):-  
    accrep(L, [], A, B, X).   

accrep([H | T], Y, A, B, X):-
    H = A,  
    accrep(T, [B | Y], A, B, X). 

accrep([H | T], Y, A, B, X):-  
    H \= A,  
    accrep(T, [H | Y], A, B, X). 

accrep([], X, _, _, X).

This does indeed technically solve the problem, but also reverses the list, which wasn't asked for, and I doubt anyone would be impressed when I hand them the correct answer backwards. But I can't think of a way to avert this, and it seems ridiculous to add in code that unreverses the list, when I should never have reversed it in the first place.

Can anyone tell me how to use accumulators without running into this problem? I've had the same issue with multiple other simple programs too.

Thanks, Alex.

标签: prolog
2条回答
一纸荒年 Trace。
2楼-- · 2019-09-05 23:50

I think you use an accumulator because you're thinking to a procedural solution. Simply avoid it, your code will be far simpler, and will solve the problem. Here is a start, please complete it.

replace(L, A, B, X):-
    accrep(L, A, B, X).

accrep([], _, _, []).
accrep([A|R], A, B, [B|S]) :- accrep(R, A, B, S).
....
查看更多
戒情不戒烟
3楼-- · 2019-09-06 00:06

Here is one way of looking at it: When you have a recursion with accumulator (usually implemented as an argument pair), it typically looks like this:

generic(..., Final, Final, ...).
generic(..., Previous, Final, ...) :-
    ...
    generic(..., Next, Final, ...).

The recursive clause

  • receives the Previous value of the accumulator
  • computes the Next accumulator value
  • and returns the Final accumulator value from the recursive call to the caller.

This is most obvious when implementing a counter, where all the accumulator arguments are integers. Previous is the old counter value, Next is one more, and Total is the final result:

counter(..., Total, Total, ...).
counter(..., Previous, Total, ...) :-
    ...
    Next is Previous+1,
    counter(..., Next, Total, ...).

What you have done is very similar: Each of your accumulator arguments is a proper list: Previous is the list you have built so far, Next is Previous with an extra element prefixed in front, and Final is the final list.

revlist(..., Final, Final, ...).
revlist(..., Previous, Final, ...) :-
    ...
    Next = [New|Previous],
    revlist(..., Next, Final, ...).

So far, so good. Now, remember that Prolog is pretty relaxed about the construction of data structures. You can leave holes in them (in the form of free variables), and fill them in later (by instantiating the variables). For lists, that means you can have a list with an uninstantiated tail, and provide the rest later. Put that together with the accumulator idea, and let all accumulator arguments be list tails: Previous is the end of the list so far, and you extend the list by instantiating Previous to another list element. This gives you a new tail, Next, which you pass into the recursion. You see that the only difference to the previous schema is the way the list element is constructed:

fwdlist(..., Final, Final, ...).
fwdlist(..., Previous, Final, ...) :-
    ...
    Previous = [New|Next],
    fwdlist(..., Next, Final, ...).

On success, the caller of this predicate will find the beginning of the list in the first accumulator argument, and the tail in the second. The list is in the same order the elements were created, and can be terminated by setting Final to [].

Notes: (1) the Final argument in the last case can be eliminated - I've just left it there to show the symmetry. (2) this whole explanation is very procedural and in terms of input/output arguments - in reality the list examples will work in multiple modes.

查看更多
登录 后发表回答