Related
The scenario is to mimic rolling 3 six-sided die in Prolog while trying to obey the recursive nature of prolog. This is easily done with the Fibonacci series
n_factorial(N, F) :-
N #> 0,
F #= N*F1,
N1 #= N-1,
n_factorial(N1, F1).
I'm having difficulty translating this to the dice paradigm, where we add a random number to the sum.
# N = number of dice, S = number of sides, R = result
roll_dice(N, S, R) :-
N1 #> 0,
R = random_between(1, S, R1),
N1 #= N-1,
roll_dice(N1, S, R1).
throws an error but neglects the sum anyway. I would normally use += in other languages.
A few things not quite right there:
R = random_between(1, S, R1) stores that structure in R, it does not generate random numbers.
R1 is used for "the random number you get for rolling one die" and "the sum of all the random numbers from rolling all the remaining dice" and it is unlikely those will be the same value, and when they isn't the same, that will fail.
There is no code to do the summing. (The equivalent of the Factorial video using F #= N * F1)
No recursive base case (the first line of the factorial from the video is not shown in your question, it is n_factorial(0, 1).). When N dice remaining gets down to 0, there is nothing to handle that case.
Here is one implementation:
:- use_module(library(clpfd)).
roll_dice(0, _, 0).
roll_dice(NDice, Sides, Sum) :-
NDice #> 0,
random_between(1, Sides, Roll),
Sum #= Roll + RunningTotal,
NDiceLeft #= NDice - 1,
roll_dice(NDiceLeft, Sides, RunningTotal).
while trying to obey the recursive nature of Prolog.
I guess that's important to understand; this can be done with less bolierplate and fewer temporary variables to name, e.g.:
roll_dice(NDice, Sides, Sum) :-
length(Rolls, NDice),
maplist(random(1, Sides), Rolls),
sum_list(Rolls, Sum).
which makes a list Rolls the right size to hold all the rolls, fills them in using maplist to apply random on each of them, and sums that list for the answer.
I'd do something along the lines of:
roll(N,S,R) :- positive_int(N), positive_int(S), roller(N,S,0,R) .
positive_int(N) :- integer(N), N > 0 .
roller(0,_,R,R) .
roller(N,S,T,R) :-
T1 is 1+random(S),
N1 is N-1,
roller(N1,S,T1,R)
.
Suppose I have a list like [1,[2,1,[2],[3,1,1,[[3]],1,[1]],1],2,1,2,1,3] and I want to delete every second occurrence of '1' from it for a resultant list of [1,[2,[2],[3,1,[[3]],1,[]],1],2,2,1,3]. So far what I've come up with is this:
delete_second_occurrence([], [], _, _).
delete_second_occurrence([X|L], [X|R], X, N) :-
0 is mod(N, 2),
N1 is N + 1,
delete_second_occurrence(L, R, X, N1).
delete_second_occurrence([X|L], R, X, N) :-
1 is mod(N, 2),
N1 is N + 1,
delete_second_occurrence(L, R, X, N1).
delete_second_occurrence([E|L], [E|R], X, N) :-
is_list(E),
delete_second_occurrence(E, R, X, N),
delete_second_occurrence(L, R, X, N).
delete_second_occurrence([E|L], [E|R], X, N) :-
delete_second_occurrence(L, R, X, N).
To clarify,
R is the resultant list
L is the input list
X is the element I want to be removed
N is the number of times X has been encountered
It removes every second occurrence in the lowermost level but does nothing for the nested lists. How would I go about removing the duplicates in the nested lists as well?
The main problem is the fourth clause:
delete_second_occurrence([E|L], [E|R], X, N) :-
is_list(E),
delete_second_occurrence(E, R, X, N),
delete_second_occurrence(L, R, X, N).
The second condition is true if R is the result of deleting every second occurrence of X in E. The third condition is true if R is the result of deleting every second occurrence of X in L. Furthermore, N must have the same value after calling delete_second_occurrence on E and L. Not many lists have these properties. There are two problems to address:
Choose a different variable name in the first invocation of delete_second_occurrence and update the head of the clause appropriately.
Introduce another argument that represents the state of the accumulator after the recursive call.
Instead of counting the number of times that X has been encountered, you can use a Boolean variable. There is no need to perform modular arithmetic. After doing this, you will notice that your program generates one correct solution and several incorrect ones. For example, consider the fifth clause:
delete_second_occurrence([E|L], [E|R], X, N) :-
delete_second_occurrence(L, R, X, N).
This clause states that [E|R] is the result of deleting every second occurrence of X in [E|L] if R is the result of deleting every second occurrence of X in L. This is not always true. For example, if N is 1 and E unifies with X, you certainly don't want to include E in the output list. Similarly, if E is a list that contains X, you probably shouldn't generate solutions that simply prepend E to the result of the recursive call.
I m trying to check if, in a list of lists, all sublist is equal to the length of the list of lists.
For example, if I have [[1,2],[3,4]] is true, because I have 2 lists
of 2 elements.
Otherwise, if I have
[[1],[2,3]] is false because have 2 lists but not all list has 2
elements
[[1,2],[2,3],[3,4]] is false because I have 2 lists and all list have
2 elements instead of two
.
I did these two function:
count([],0).
count([_H|T],N):-count(T,N1),N is N1+1 .
ma([],0).
ma([H|T],N):- count(H,M1),ma(T,N1), M1 is N1.
I did "count" (and work) for count element in a list and return N the number of elements in a list.
The "ma" function doesn't work because "count" is executed until 0, and return 2, after executing ma but until 1 step, and after making directly the M1 is N1, and obviously return false.
I wish to make M1 is N1 at end of the program (like in another programming language, but I think is the then't correct form.
EDIT:
Daniel suggest to use :
ma([H], N) :- length(H, N).
ma([H|T], N) :- length(H, N), ma(T, N).
But a list with 3 sublists all with 2 elements gives result 2, instead the result will be false(error) because N number of the list must be equal to N number of elements in ALL Sublist.
I will do on my own, without build-in predicate of prolog.
Here a very basic solution without built in predicates:
count_elements([],N,N).
count_elements([_|T],N,N0):-
N1 is N+1,
count_elements(T,N1,N0).
count_length_sub([],_).
count_length_sub([H|T],N):-
count_elements(H,0,N),
count_length_sub(T,N).
solve(L):-
count_elements(L,0,NO),
count_length_sub(L,NO).
?- solve([[1,2],[3,4]]).
true.
?- solve([[1,2],[2,3],[3,4]]).
false.
Your count/2 is like the length/2 builtin, except that the built-in has more instantiation patterns (try length(X, Y) and see). Prefer length/2.
You're right that your ma/2 predicate is unhelpful because 0 is not a length of a sublist. Basically, you've chosen the wrong base case here; your base case should be a list with exactly one item in it:
ma_1([H], N) :- length(H, N).
ma_1([H|T], N) :- length(H, N), ma(T, N).
You will need to wrap this in something that ensures the length matches the length of the outer list:
ma(L, N) :- length(L, N), ma_1(L, N).
Note that there is no need to obtain separate variables and assert their equality (your dance with N and N1). Prolog will simply fail, which is what you want, if N does not have the right value. (Side note, do not use is for unification. The purpose of is is to reduce an arithmetic expression on the right side to a value and assign it to the variable on the left, e.g. X is 2 + 3*4.)
Another approach would be to write your actual request in a logical form and write that instead. A logical form of this request would be something like "ma(L, N) holds if N is the length of L and for all items X of L, they are lists of length N as well". This looks like so:
ma(L, N) :-
length(L, N),
forall(member(X, L),
length(X, N)).
This has an advantage in that no spare choice points are left around, although worrying about that is usually premature optimization.
Another approach would be to employ maplist/N, which has the advantage that it will give you back lists with variables. Unfortunately, length/2 has its parameters in the wrong order so you can't do the really cute thing and just write maplist(length(2), L). However, you can make a flip/3 predicate that flips around the arguments:
flip(P, Y, X) :- call(P, X, Y).
ma(L, N) :- length(L, N), maplist(flip(length, N), L).
Or, you can import library(yall) and use its lambda expressions:
ma(L, N) :- length(L, N), maplist({N}/[X]>>length(X, N), L).
Both of these approaches allow solutions like these:
?- ma(X, N).
X = [],
N = 0 ;
X = [[_1976]],
N = 1 ;
X = [[_1982, _1988], [_1994, _2000]],
N = 2 ;
X = [[_1988, _1994, _2000], [_2006, _2012, _2018], [_2024, _2030, _2036]],
N = 3
...
Is there a faster way to implement these 2 functions in swi-prolog , in order to save time and memory?
insert(Ind,List,Val,NList) :-
nth0(Ind,List,_,R),
nth0(Ind,NList,Val,R).
build(X,N,List) :-
length(List,N),
maplist(=(X),List).
Explaining: The first one inserts at the Nth position of a list a value and the second one builds a list of N elements all equal to number X
Edit : I have figured out a way to improve insert for my program
insert(Ind,List,NList,R,Elem) :-
Valb=Val,
Val is Val+1,
nth0(Ind,NList,Val,R),
Elem=Valb.
This should replace an element of a list with its value plus 1 , but also return the previous Value (Valb) before adding 1 . I know the code is wrong how could I make it right without using nth0 another time?
In terms of time complexity, both work in O(n) time and O(n) memory. You can not construct a list with n elements in less than O(n) time, and it will require at least O(n) memory. One could construct the list lazily and thus postpone the creation, but for a real list, in terms of time complexity.
What we can do however is "merge" the logic of the two predicates, such that we do more per recursive call, for example:
build(X,N,List) :-
var(N),
!,
build_gen_(List, X, N).
build(X, N, List) :-
integer(N),
!,
build_num_(N, List, X).
build_gen_([], _, 0).
build_gen_([X|T], X, N) :-
build_gen_(T, X, N1),
N is N1 + 1.
build_num_(0, [], _) :-
!.
build_num_(N, [X|T], X) :-
N > 0,
N1 is N-1,
build_num_(N1, T, X).
We thus basically construct a special predicate that performs the two tasks (constructing a list of length N, and setting all the elements to X) with the same predicate.
I leave specializing the first predicate insert/4 as an exercise.
I am beginner in Prolog programming. I wrote this program to sum the atomic of a deep list with accumulator.
deep_sum(Xs, N) :- deep_sum(Xs, 0, N).
deep_sum([], N, N).
deep_sum([X|Y], M, N) :- atomic(X), Q is M + X, deep_sum(Y, Q, N).
deep_sum([X|Y], M, N) :- M is P + Q, deep_sum(X, P, N), deep_sum(Y, Q, N).
Why do I get "ERROR: is/2: Arguments are not sufficiently instantiated"?
It works fine without accumulator:
deep_sum([], 0).
deep_sum([X|Y], S) :- atomic(X), !, deep_sum(Y, Q), S is Q + X.
deep_sum([X|Y], S) :- deep_sum(X, P), deep_sum(Y, Q), S is P + Q.
The problem is with your last clause:
deep_sum([X|Y], M, N) :- M is P+Q, deep_sum(X,P,N), deep_sum(Y,Q,N).
The direct issue of the error is that neither P nor Q have a value when M is P+Q is executed. Just moving it to the back won't solve it though, the clause has more issues.
Let's take a look at the recursive calls. deep_sum(X,P,N) in words means "N is the deep sum of the head (X), given accumulator P". There are two issues here: P does not have a value, and N is supposed to be the total sum of the entire list, not just the head.
The same issues are there in the second recursive call. The accumulator Q does not have a value yet, and again N is used as the result. So now N has 3 meanings: deep sum of the head, deep sum of the tail, and deep sum of the entire list! Obviously something is not right there.
Let's try to put in words how the recursive rule should behave. The result N should be equal to the sum of a) the current accumulator, b) the deep sum of the head and c) the deep sum of the tail. a and b can be easily combined: simply pass in the current accumulator as the accumulator for the recursive call: deep_sum(X, M, N1). Here I use another variable N1 to hold this result. Now we just have to sum this with the deep sum of the tail. Again, we can simply pass N1 as accumulator for the recursive call, and everything will be accumulated as expected.
Putting everything together, your recursive rule should look like this:
deep_sum([X|Y], M, N) :-
deep_sum(X, M, N1),
deep_sum(Y, N1, N).
For completeness, my deep_sum/3 implementation would look something like this:
deep_sum(X, M, N) :-
number(X),
N is M + X.
deep_sum([], N, N).
deep_sum([X|Y], M, N) :-
deep_sum(X, M, N1),
deep_sum(Y, N1, N).
The main differences are:
Only one recursive clause; the clause where you handle numbers doesn't need to be recursive.
number/1 instead ofatomic/1, so you don't attempt to add strings or so.
Rearranged the order of the clauses to not leave any useless choice point once the calculation is done.