Forcing variable to reassign (Prolog)

你说的曾经没有我的故事 提交于 2020-01-24 12:54:26

问题


The homework is to take in two variables, a number between 0 and 10,000 and a number of how many circular primes there are between 1 and that number.

I am having trouble passing the variable back up through the recursion (backtracing is what it is called, I think.) I get the right number and I am pretty sure I have the concept down, the problem I am having is that it throws an error when I try to reassign a variable (?!)

Here is the code:

circPrimeCompare(Below, NumCirc):-
    RealNum is 0,
    circPrimeCompare(1, Below, RealNum, []),
    print('R: '),
    print(RealNum),
    nl,
    (NumCirc =:= RealNum).

circPrimeCompare(_, 0, _, _).     
circPrimeCompare(N, 1, _, _):- prime(N), print('aaa').
circPrimeCompare(X, Below, RealNum, L):-
    ( prime(X), X<Below ->
        print(X),
        nl,
        numDigits(X, Y),
        rotate(X, Y, N2),
        (   prime(N2) 
            ->  RealNum2 is RealNum + 1 
            ;   RealNum2 is RealNum
            ),
        X2 is X + 1,
        (   not(circPrimeCompare(X2, Below, RealNum2, L)) 
            ->  RealNum = RealNum2, print(RealNum), nl 
            ;   RealNum = RealNum2, print(RealNum), nl
            ),
        print('RealNum2: '),
        print(RealNum),
        nl
    ;
    ( X<Below ->
        X2 is X + 1,
        RealNumPass is RealNum,
        (   not(circPrimeCompare(X2, Below, RealNumPass, L)) 
            ->  RealNum = RealNumPass, print(RealNum), nl 
            ;   RealNum = RealNumPass, print(RealNum), nl
            ),
        print('RealNum: '),
        print(RealNum),
        nl
        )
    ).

Here is the trace:

   Fail: (26) circPrimeCompare(10, 10, 4, []) ? creep
^  Exit: (25) not(user:circPrimeCompare(10, 10, 4, [])) ? creep
   Call: (25) 4=4 ? creep
   Exit: (25) 4=4 ? creep
...
   Exit: (24) circPrimeCompare(9, 10, 4, []) ? creep
^  Fail: (23) not(user:circPrimeCompare(9, 10, 4, [])) ? creep
   Call: (23) 4=4 ? creep
   Exit: (23) 4=4 ? creep
...
   Exit: (22) circPrimeCompare(8, 10, 4, []) ? creep
^  Fail: (21) not(user:circPrimeCompare(8, 10, 4, [])) ? creep
   **Call: (21) 3=4 ? creep
   Fail: (21) 3=4 ? creep**
   Redo: (21) numDigits(7, _G589) ? creep

The bolded part is what is throwing me. I don't really get why it is acting this way. Is it because variables are essentially one use only? Any ideas on how to fix it?

(And yes, I realize this is really, really terrible code. I had never written anything in Prolog before this assignment.)


回答1:


You are not thinking about the problem in a "Prolog" way. In particular trying to "reassign" a variable is not the Prolog way. Variables are bound along the way of satisfying goals and subgoals, and instead of reassigning a value, we often arrange the code to carry an "accumulator" variable, only to pass its value to another "final" variable in the last stage of a computation.

Backtracking and recursion are different things. Backtracking occurs when a goal (or subgoal) fails, and the Prolog "engine" tries to satisfy that goal in a different way. If we run out of different rules, etc. to satisfy the goal, then it fails. (Which may cause the Prolog engine to backtrack into a previous subgoal and try to satisfy that in a different way.)

Recursion is when a predicate is defined in terms of itself, i.e. if invoking the predicate can cause the same predicate to be invoked again as a subgoal.

To do many tasks in Prolog we have to think about them in terms of recursion rather than iteration, as would be natural in a procedural language. This is why we resort to "accumulator" variables, which appear to the unaccustomed eye as extra and possibly unnecessary arguments in a predicate.

However once you see the idea in action, you may get some measure of satisfaction in being able to apply the concept in new ways.

Let's take as a model problem adding up the numbers in a given list. The naive approach would be to write:

sum(List,Sum).

where the List is a list of numbers and Sum should be our output, holding the prescribed sum of values in the list.

The basic idea is pretty natural, you consider the Head and Tail of the list, and if the list is not (yet) empty, you want to "reassign" Sum = Sum + Head, then proceed recursively to tackle the Tail (until the list is empty and we have the Sum we wanted).

But this doesn't make sense in Prolog, because you cannot change the value of Sum in the same way a procedural language allows. This is where an accumulator argument gets added to our predicate and does the same work, but in a way that Prolog likes.

sumAux([ ],Sum,Sum).
sumAux([H|T],Accum,Sum) :-
    NewAccum is Accum + H,
    sumAux(T,NewAccum,Sum).

sum(List,Sum) :- sumAux(List,0,Sum).

Here we've introduced an "auxillary" predicate sumAux/3 with an extra argument, and we've defined sum/2 in terms of that predicate by setting the "accumulator" middle argument to zero, but passing the List and Sum arguments along.

A little thinking about how this works will repay your efforts, and soon I expect you'll be applying this paradigm in all sorts of novel ways (such as counting circular primes).




回答2:


"backtracking" is the name of the concept; it means going back on oneself, reneging on one's decision if that has proved unfortunate. In Prolog programs we have rules, that show which sub-goals must be proven, for the main goal (head of rule) to be deemed proven. When there are several rules, we might try 1st; and if later it turns out that it can't be proved, we backtrack to the latest decision point, and try to prove the next rule, if available.

Going forward, we instantiate our variables, giving them values. Once a value is determined for a variable, it can't be changed, while we're still going forward towards proving our goal. When we say that X=2, we commit to this choice. We can't now say that X=5, we would be liars then, and our "proof" would become worthless.

The essence of Prolog is, while we go forward towards proving our goal, and make various decisions about what is what, we collect those values into whats know as substitution, a mapping between variables and their values, which is not self-contradictory; and when we've reached the final goal, this set of values for our variables is our answer.

But when we fail and must backtrack, all the instantiations we've done so far are undone, in the reverse order.

Specifically, NumCirc =:= RealNum is not an assignment. It is a numeric comparison which might be true or false. If it is true, we say that the goal NumCirc =:= RealNum succeeds (is proven). If it is false, we say that the goal failed, and thus we backtrack.

Another key concept is unification: when we say X = Y, we claim they are both the same. Of course 4=4 succeeds, and 3=4 fails.

And yes, Prolog is essentially a set-once language (sans backtracking, which undoes the assignment, not changes it, making the variable unassigned again).




回答3:


A common prolog idiom is, as has been noted, using 'helper' predicates that are seeded with an accumulator to get work done. Further, it helps to decompose the problem into smaller, simplier parts. Often you'll have what I call a 'public predicate' that doesn't do much except enforce constraints and invoke a 'private' helper predicate that does all the work. Often that helper predicate will have the same name as the public predicate with a different arity (foo/3 as opposed to the public predicate's foo/2, for instance). Something like this:

%
% count the number of circular primes below the specified
% upper limit.
%    
count_circular_primes( Max , Cnt ) :-
  integer(Max) ,
  Max >=     1 ,
  Max =< 10000 ,
  count_circular_primes( Max , 0 , Cnt )
  .

The helper predicate here, count_circular_primes/3 uses an accumulator value that is seeded as 0. It looks something like:

count_circular_primes( 0 , Cnt , Cnt ) .
count_circular_primes( N , T , Cnt ) :-
  N > 0 ,
  is_circular_prime(N) ,
  T1 is T + 1 ,
  N1 is N - 1 ,
  count_circular_primes(N1,T1,Cnt)
  .

You'll notice that this is where all the counting happens. All that's really left to do is determine whether or not a particular integer is a circular prime or not. Easy!

You'll also note that the result variable Cnt is not unified with anything until the entire recursive operation is completed. When N hits zero, the accumulator T is unified with the result.

Now that the framework is in place, let's think about how to determine whether or not a single integer is a circular prime or not. That should be pretty easy:

  1. Is the number prime?
  2. Treat it as a circular buffer and rotate the digits 1 place to the left
  3. Repeat until you arrive back at the beginning.

Here's how I would do a list rotation. Backtracking will yield the entire set of list rotations:

rotate(Xs,Ys) :-
  append(A,[B|C],Xs) , % get the prefix (A) , suffix (C) and current item (B) from Xs
  append(C,A,D)      , % append the suffic (C) to the prefix (A) to get the leftmost bit (B)
  append([B],D,Ys)     % append the current item (B) to the leftmost bit (D) to get the rotation
  .

Here's how it works: append/3 can be used to generate all possible prefix/suffix combination for a specified list. If I say append(Prefix,Suffix,[1,2,3,4]) the complete set of solutions returned via backtracking is

Prefix    Suffix
========= =========
[]        [1,2,3,4]
[1]         [2,3,4]
[1,2]         [3,4]
[1,2,3]         [4]
[1,2,3,4]        []

Once you can generate the rotations, findall/3 can be used to get the entire solution set as a list, given a particular input. So, given the above rotate/2 predicate, I can say something like:

findall( X , rotate( [1,2,3,4] , X ) , Rotations ).

Rotations will be unified with this list of lists:

[ [1,2,3,4] , [2,3,4,1] , [3,4,1,2] , [4,1,2,3] ]

Once you have the set of rotations, all you've got to do it evaluate whether or not it represents a prime number. You can do that with forall/2, along these lines:

forall( member(Rotation,Rotations), is_prime(Rotation) )

Things get a little trickier if you're not allowed to use findall/3 and forall/2, but this should send you on your way, I think.



来源:https://stackoverflow.com/questions/16282034/forcing-variable-to-reassign-prolog

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!