Prolog - creating cumulative duplicates in a list? - list

I have a list L given and my task is to create cumulative duplicates, depending on how many I want.
I have the list [a,b,c,d] for example and want the first element to be duplicated 4 times (optional), then every subsequent element has to be duplicated like the previous one + 1.
Let's assume my predicate is called list_copy(L,N,R), the result with L = [a,b,c,d] and K = 2 should be:
?- list_copy([a,b,c,d],2,R).
R = [a,a,b,b,b,c,c,c,c,d,d,d,d,d]
I've managed to create a universal code for duplicating a list two times:
dupl([],[]).
dupl([O|U],[O,O|U1]) :- dupl(U,U1).
Depending on how many O's I put in my second line I get that many duplicates.
My question is, though: How can I implement a 3rd variable as a cumulative counter, in order to get the desired outcome?

When you have to "count" stuff one by one, consider using succ/2. It has the nice property that it works both ways, and fails when you call it with succ(X, 0).
So first, the predicate that does "cumulative duplicates":
cum_dup([], _, []).
cum_dup([X|Xs], N, Ys) :-
repeat_n(X, N, Ys, Back),
succ(N, N1),
cum_dup(Xs, N1, Back).
This uses the predicate repeat_n/4, which takes an elements, a non-negative integer, and repeats the element. It leaves a "hole" in the back of the list that you can fill with the rest of the result using cum_dup/3. Here is a straight-forward implementation of repeat_n/4:
repeat_n(_, 0, L, L).
repeat_n(X, N, [X|Xs], Rest) :-
succ(N0, N),
repeat_n(X, N0, Xs, Rest).
This will already give you the result you need:
?- cum_dup([a,b,c,d], 2, R).
R = [a, a, b, b, b, c, c, c, c, d, d, d, d, d] ;
false
It leaves behind a harmless choice point. There are too many ways to make a repeat_n/4 that does not leave unnecessary choice points:
use CLP(FD)
use a cut
use a conditional (if-then-else)
use a structure instead of an integer
Just one example:
repeat_n(X, N, L, Back) :-
length(Ns, N),
repeat_n_(Ns, X, L, Back).
repeat_n_([], _, L, L).
repeat_n_([_|Ns], X, [X|Xs], L) :-
repeat_n_(Ns, X, Xs, L).
Here, instead of counting with an integer, you (ab)use a list of that length.
I guess you can think about it yourself and ask another question if you really need to.

--- EDIT --- modified using succ/2, as suggested by Boris (thanks!).
I suppose you can use a helper clause. Like list_copy_h/4 in the following example
list_copy_h([], _, _, []).
list_copy_h([_ | Tin], 0, Num, Lout) :-
succ(Num, Np1),
list_copy_h(Tin, Np1, Np1, Lout).
list_copy_h([H | Tin], Count, Num, [H | Tout]) :-
succ(Cm1, Count),
list_copy_h([H | Tin], Cm1, Num, Tout).
list_copy(Lin, Num, Lout) :-
list_copy_h(Lin, Num, Num, Lout).
The idea is use a counter (the second argument) that decrease to zero. When zero, the head of the input list is discharged and the cycle begin again with the following element of the input list but increasing the starting value of the counter. The third argument is the starting value.

Related

Slicing the list in prolog between two indexes

I'm trying to slice a list, and create a result list that contains elements that fall between two indices, exclusive.
indexOf([Element|_], Element, 0). % We found the element
indexOf([_|Tail], Element, Index):-
indexOf(Tail, Element, Index1), % Check in the tail of the list
Index is Index1+1. % and increment the resulting index
less_than(0, 0).
less_than(X, N) :-
X < N.
greater_than(0, 0).
greater_than(X, N) :-
X > N.
slice([], 0, 0, []).
slice([H|T], I, N, R) :-
indexOf([H, T], H, Ind), % get the index of the H
(less_than(Ind, N),
greater_than(Ind, I), % check if that index is between I and N
slice(T, I, N, H|R); % if it is - call the slice again, but append the H to the result
slice(T, I, N, R)). % if it's not, just call the slice regularly with the rest of the items
For instance, slice([a,b,c,d], 0, 4, R) R should be [b,c].
Now this always fails but I'm not sure why.
Is it the base case, or is is the 'if-else' statement between the parenthesis?
I would like to accomplish this without using build-in predicates.
There are some fundamental flaws in your algorithm. You seem to be processing one item at a time but didn't take into account that your input list keeps shrinking, so you should decrement your starting index by one each time. Your base case probably should not check that the ending index be 0, you never modify it and you wouldn't call it with 0.
Also your indexOf/3 procedure seems to find any location where the item is found (there may be duplicate items in your list).
You are also misusing the list structure [Head|Tail] (forgot the square brackets).
You may do your slicing by using append/3:
slice(L, From, To, R):-
length(LFrom, From),
length([_|LTo], To),
append(LTo, _, L),
append(LFrom, R, LTo).
In this answer I assumed your index starts at 1.
Sample run:
?- slice([a,b,c,d,e], 1, 4, R).
R = [b, c].

creating list of an elemente multipled n times

So my problem is this, i have a predicate which is repete_el(El,N,L) in which El is an element, N is the number of times is repeated and L is the list contaning that element repeated N times.
My problem is that istead of repeating the element its giving false and i dont understand why.
Example:
My output:
?- repete_el(a,3,L).
false
Correct output:
?- repete_el(a,3,L).
L = [a,a,a].
Program:
repete_el(El,0,[]) :- !.
repete_el(El,N,L) :- repete_el(El,N,L,[],N).
repete_el(El,N,L,L2) :- length(L2,C),
C =< N,
append(L2,[N],NL),
repete_el(El,N,L,NL).
By the way, i can only do this iteratively.
You can use the standard findall/3 predicate and the de facto standard between/3 predicate. For example:
| ?- findall(a, between(1,5,_), List).
List = [a, a, a, a, a]
yes
The reason this will not work is because at the moment you call repete_el/4, L2 is a free variable, so length(L2, C) will start building all sorts of lists with lengths. Then you make a recursive call on a list NL with one element extra, and you require that list to have again length C (which should again be smaller than N). But eventually C will be larger than N, and thus the predicate will fail.
You can write such predicate as:
repete_el(_, 0, []). %% (1)
repete_el(X, N, [X|T]) :- %% (2)
N > 0,
N1 is N-1,
repete_el(X, N1, T).
Here we thus say:
(1) A list where we repeat an element 0 times is an empty list; and
(2) a list where we repeat X, N times, with N greater than 0 is a list that starts with X and ends with a list where we repeat X, N-1 times.

Prolog: Sum one element of a list at a time

I'm new to Prolog and have decided to try to solve a problem in which I have a sequence of symbols that each have the value 1 or -1. What I need is to add them all together, one element at a time, and extract at which index the sum for the first time drops below 0. Since I'm coming from an imperative background, I'm imagining a count variable and a for-loop, but obviously I can't do that in Prolog.
value('(', 1).
value(')', -1).
main(R) :- readFile("input", R), ???
readFile(Path, R) :-
open(Path, read, File),
read_string(File, _, Str),
stringToCharList(Str, Xs),
maplist(value, Xs, R).
stringToCharList(String, Characters) :-
name(String, Xs),
maplist(toChar, Xs, Characters ).
toChar(X, Y) :- name(Y, [X]).
As you can see, all that I've really managed so far is to read the file that contains the sequence, and convert it to 1s and -1s. I have no idea where to go from here. I suppose the problem is three-fold:
I need to iterate over a list
I need to sum each element in the list
I need to return a certain index
Any suggestions? Can I somehow cut off the list where iteration would have dropped the sum below zero, and just return the length?
I'll use a principle in Prolog of an auxiliary variable to act as a counter until the conditions reach what we want. Then the auxiliary counter is unified with a variable at that point in the base case.
I'm assuming here, blindly, that your code works as stated. I did not test it (that's up to you).
main(IndexAtZeroSum) :- readFile("input", R), index_at_zero_sum(R, IndexAtZeroSum).
readFile(Path, R) :-
open(Path, read, File),
read_string(File, _, Str),
stringToCharList(Str, Xs),
maplist(value, Xs, R).
stringToCharList(String, Characters) :-
name(String, Xs),
maplist(toChar, Xs, Characters ).
toChar(X, Y) :- name(Y, [X]).
% The following predicate assumes indexing starting at 0
index_at_zero_sum([V|Vs], IndexAtZeroSum) :-
index_at_zero_sum(Vs, V, 0, IndexAtZeroSum).
% When sum is zero, Index is what we want
index_at_zero_sum(_, 0, Index, Index).
index_at_zero_sum([V|Vs], Sum, CurIndex, Index) :-
S is Sum + V,
NextIndex is CurIndex + 1,
index_at_zero_sum(Vs, S, NextIndex, Index).
index_at_zero_sum/2 provides the index for the given list where the sum becomes zero. It does so by using an auxiliary predicate, index_at_zero_sum/4, starting with a sum at the first value (the sum being the value itself) and the current index starting at 0. So the 2nd argument is the sum at index 0. Subsequent calls to index_at_zero_sum/4 increment the index and accumulate the sum until the sum becomes 0. At that point, the base case succeeds and unifies the 4th argument with the current index. If the sum never becomes 0 before the list becomes empty, the predicate fails.
You can also avoid reading the entire file and creating a numeric list by using get_char/2:
index_at_zero_sum(Path, Index) :-
open(Path, read, File),
get_char(File, C),
value(C, V),
( index_at_zero_sum(File, V, 0, Index)
-> close(File)
; close(File),
fail
).
index_at_zero_sum(_, 0, Index, Index).
index_at_zero_sum(File, Sum, CurIndex, Index) :-
get_char(File, C),
value(C, V),
S is Sum + V,
NewIndex is CurIndex + 1,
index_at_zero_sum(File, S, NewIndex, Index).

Iterating over list elements in turns in prolog

I have this predicate repeat/3 which I need to construct. It is supposed to repeat all the elements in a list n amount of times. For example:
?- repeat([a,b,a,a,c],2,X).
Would produce
X = [a, a, b, b, a, a, a, a, c, c].
The current code I have written for it is the following:
repeat([],_,[]).
repeat(_,0,[]).
repeat([A],Int,[A|X]):- Int1 is Int-1, repeat([A],Int1,X).
repeat(A,1,A).
repeat([A|Tail],Int,[A|X]):- Int1 is Int-1, repeat([A|Tail],Int1,X).
It will return:
1) An empty list upon giving it an empty list.
2) An empty list upon giving it the number 0 as an argument.
3) One letter n amount of times.
4) The given list one time.
Now, the issue I'm having with is the final line of code.
5) What this line will do for me currently is return all the elements in a list after repeating the first element n amount of times.
Example:
?- repeat([a,b,b,c],3,X).
X = [a, a, a, b, b, c]
I figure the solution is for me to go through the list and for every element repeat it n amount of times but I do not have any clue as to how to do it.
One idea which I attempted was to have the number I pass into the predicate turn into the original upon reaching 1 and then continue the predicate using the tail:
repeat([],_,[]).
repeat(_,0,[]).
repeat([A],Int,[A|X]):- Int1 is Int-1, repeat([A],Int1,X).
repeat([A|Tail],1,[A|X]):- repeat(Tail,Int,X). % The line where I made a change.
repeat([A|Tail],Int,[A|X]):- Int1 is Int-1, repeat([A|Tail],Int1,X).
This did not work out. I do now know whether I am on the right track for this or not. Any help would be appreciated.
Although there are definitely some other issues as well, the most important issue is that you decrement N until it hits zero to repeat the first element. But after it hits zero, of course you do no longer can obtain the original N.
So how can we store the original N? We can simply introduce a new predicate repeat/4, with:
repeat(L, N, LN) :-
repeat(L, N, N, LN).
So we redirect repeat/3 to repeat/4 by copying N. The idea is that we will decrement only one of the parameters. From the moment that parameter hits zero, we will "reset" the parameter, by fetching the value from the second N (which we do not decrement).
So now we only need to work out repeat/4. In case we reached the end of the list, then - regardless of the value of N - the repetition is an empty list:
repeat([], _, _, []).
in case the first N has reached zero, we proceed to the next element of the list, and reset that N:
repeat([_|T], 0, N, LN) :-
repeat(T, N, N, LN).
and finally in case we did not yet hit zero, we of course prepend the result with the head of the first list:
repeat([H|T], A, N, [H|LN]) :-
A > 0,
A1 is A-1,
repeat([H|T], A1, N, LN).
If we put it all together, we obtain:
repeat(L, N, LN) :-
repeat(L, N, N, LN).
repeat([], _, _, []).
repeat([_|T], 0, N, LN) :-
repeat(T, N, N, LN).
repeat([H|T], A, N, [H|LN]) :-
A > 0,
A1 is A-1,
repeat([H|T], A1, N, LN).

Finding the most similar list from predicate knowledge base in Prolog

I have a problem in which I have a list of elements and I have to cycle through all instances of a particular /2 predicate to find which one has he highest number of matching elements in its list. In terms of implementation I can't seem to figure out how I should be updating the highest match so far and then stopping when there are no more.
findAnswer(MyList, HighMatchNum,_):-
answer(X,Y),
myIntersection(MyList, Y, NUM), //handles a single instance check and returns how many elements match.
NUM > HighMatchNum,
findAnswer(MyList, NUM, answer(X,Y)).
//Knowledge base
answer(sample1, [a,b,c,d]).
answer(sample2, [d,c,e]).
there is library(aggregate):
findAnswer(MyList, HighMatchNum, K) :-
aggregate_all(max(N, Key),
( answer(Key, List),
myIntersection(MyList, List, N)
),
max(HighMatchNum, K)).
myIntersection(MyList, List, N) :-
intersection(MyList, List, L),
length(L, N).
% Knowledge base
answer(sample1, [a,b,c,d]).
answer(sample2, [d,c,e]).
yields
?- findAnswer([a], C, K).
C = 1,
K = sample1.
?- findAnswer([d,e], C, K).
C = 2,
K = sample2.
To find the best, we have to search through the whole list, to its end. We will maintain the best so far and its score as additional arguments:
best_match(MyList,R-RN):-
findall(X, (answer(A,L), X=A-L), ALL),
ALL = [A-L|T],
myIntersection(MyList, L, N),
find_best(MyList,T,A,N,R,RN).
find_best(_,[],A,N,A,N).
find_best(MyList,[B-H|T],A,N,R,RN):-
myIntersection(MyList, H, K),
( K>N -> find_best( MyList, T, B, K, R, RN)
; find_best( MyList, T, A, N, R, RN ).
this produces the name and score of the best match.
Simply assert it, I can not see how you can propagate the max value in your solution.
:- dynamic maxval/1
:- maxval(0).
findAnswer(MyList, HighMatchNum) :-
answer(X,Y),
myIntersection(MyList, Y, NUM), %handles a single instance check and returns how many elements match.
NUM > HighMatchNum, %If this fails, try other answer
retract(maxval(_), assert(maxval(X)),!, %else retract the previous value and assert the new one
findAnswer(MyList, NUM).
Finally check the value of maxval/1 as maxval(X). This algorithm will always fail so you will get the solution in the user database, the problem is with your implementation, you may check your logic. However it will assert the proper answer. You must remember to always implement a base case for any recursive procedure.