I'm sorry to post another question on this, but I just seem to be going in circles here!
For my program I need to make a list of lists, with each sublist containing 2 numbers, X and Y along with the sum and product of these 2 numbers. So far I have the following:
genList(100,N, X,[]).
genList(S,N, X,[[X,Y,Sum,Product]|Xs]):-
Y is N+1,
Sum is X+Y,
NewS is Sum,
Sum<101,
Product is X*Y,
N1 is N+1,
genList(NewS,N1, X,Xs).
genList(S,N,X,Q):-
N+X < 101,
NextX is X + 1,
genList(0,NextX,NextX,Q).
The goal is to find every pair of numbers where sum<= 100. So running through the above for one starting value, X would find every pair 1 < X < Y, where sum<=100, and running through it with all numbers 2-N would give a complete list of possible pairs.
For those interested, the problem I'm working through is the sum/product problem, described here (Second on the page)
If anyone could help with this it would be greatly appreciated!
Also, no built in prolog predicates are able to be used, hence the complicated way of doing this rather than with a findall.
A small extract of the output produced by this predicate is as follows:
[[5,6,11,30],[5,7,12,35],[5,8,13,40],[5,9,14,45],[5,10,15,50],[5,11,16,55],[5,12,17,60],[5,13,18,65],[5,14,19,70],[5,15,20,75],[5,16,21,80],[5,17,22,85],[5,18,23,90],[5,19,24,95],[5,20,25,100],[5,21,26,105],[5,22,27,110], ...
I think it's very close, but there's still something not quite right.
It cycles through number pairs, but requires the use of ";" to view all the answers, which isn't what I want. Additionally, it returns false after all the answers are exhausted. I just can't figure it out.
Also, it gives a complete answer for the starting value, but then removes a sublist each time until I'm left with only the last set of pairs.
E.g. genList(0,48,48,Q). gives me:
[[48,49,97,2352],[48,50,98,2400],[48,51,99,2448],[48,52,100,2496]]
[[48,49,97,2352],[48,50,98,2400],[48,51,99,2448],[48,52,100,2496],[49,50,99,2450],[49,51,100,2499]]
[[48,49,97,2352],[48,50,98,2400],[48,51,99,2448],[49,50,99,2450],[49,51,100,2499]]
[[48,49,97,2352],[48,50,98,2400],[49,50,99,2450],[49,51,100,2499]]
[[48,49,97,2352],[49,50,99,2450],[49,51,100,2499]]
[[49,50,99,2450],[49,51,100,2499]]
false.
As you can see, a sublist gets removed each time, I just can't see why!
Well, you're almost there. Since you spent quite some time on this problem already I'll just show you some valid code and comment it:
First we call a worker predicate that'll carry
X
andY
as arguments and initialize them to0
:Then we handle our base case. Since we started from
0
, the base case is the upper bound. We could have went the other way around, it's just a choice really. Note that the cut here means that we won't have to worry aboutY
being superior to100
in our following clauses since they won't be executed in that case.Here is now the case where
X
fits the correct limits for the sum to be under100
. We first check that everything is ok and then we use the!/0
predicate to once more prevent the execution to reach our last clause since that wouldn't make sense. After it's done we just avec to calculate the interesting values and add them to the list.The only case left to handle is when
X
goes above the limit we fixed so that the sum is under100
. When that happens we start again with the nextY
and resetX
to0
.If anything troubles you please ask for clarification in comments.
Note: the cuts used here are red cuts - ie the correctness of the predicate totally depends on the order of the clauses. It's bad practise. Try to supplement them with proper guards (like
X =< 100
for example), it'd be a good addition :)Edit:
Now let's audit your code :D I'll start by commenting on the style: In this clause (called fact since it has no body), you use
N
andX
only once ie you do not care about storing their value. In this case, we prefix their name with a_
or just use the anonymous variable_
:turns into
or
Same thing here with
S
. It's not used in this clause. It's only used in the first one. You can replace it by_
or_Sum
if you want to document its use in the other clause here too (good practise). Then, you use two variables to hold the exact same value. It has no interest here. Just call your nextgenList/4
withSum
as first argument instead of declaring a new variable just for that. Same thing withY
andN1
. A proper version of this clause turns:into
Your last clause has an issue with arithmetic:
N + X
is just the term+(N, X)
. It's not the valueN + X
. You have to use theis/2
predicate just as in your other clauses. Same issue as the second clause forS
. Those little edits turn:into
So, at the moment your corrected program looks like:
Since it was just style edits, it doesn't change its behavior.
Now let's look at what was wrong about it, not in the style, but in the logic. First, the base case. Here everything is fine. You check for the sum being your upper bound and if it is return
[]
. Perfect!Now, your "inner recursion". It's almost fine. Let's look at the detail that bugs me: you have a value that holds your previous sum, but compute a new one and retest it against the upper bound + 1. a better idea would be to test
PreviousSum
against< 100
and to remove theSum < 101
test. It'd better justify the fact that you have an argument just for that! Plus it's more understandable that it's guard being used to prevent execution of the clause in that limit case. So,would turn into
Note that this modification is kinda stylistic, it doesn't modify the program behavior. It's still makes it way more readable though!
Now, the big bad wolf: genList(_PreviousSum, N, X, Q) :- Sum is N + X, Sum < 101, NextX is X + 1, genList(0, NextX, NextX, Q). Many things to say here. First and once again, you do not need to compute
Sum
sincePreviousSum
already holds the value. Then, it should be tested for< 100
instead of< 101
. Then, it should be tested thatX >= N
, because that's only in that case that you do not want to go through your second clause but in this one instead. And last but not least, instead of starting a new iteration withgenList(0, NextX, NextX, Q)
you should start it with genList(NextX, 0, NextX, Q). Here you didn't reset properly the values. The resulting clause is:As you saw, we know formalized that we can't go through our second clause if
N >= X
. We should add to it the proper test to be assured that it's correct:Here you're done, your program is correct!
Final version is:
The variable naming is still poor. A clearer version would be:
Here you still have a problem (yeah it nevers ends :D): the fact that you increment your variables BEFORE calculating the sum product etc means that you skip some values. Try to increment after. It's let as an exercise :)