I'm trying to divide a list in Prolog into 3 equal parts (...well, as equal as possible).
My algorithm is the following:
Find out the size of the initial list.
Call the procedure with two extra parameters (the size of the list and a counter that will tell me when I should stop adding elements to one list and start adding to another)
The procedure looks like this:
With 4 parameters:
div3(InitialList,FirstNewList,SecondNewList,ThirdNewList).
With 2 extra parameters:
div3(InitialList,FirstList,SecondList,ThirdList,InitialListSize,Counter).
Here's my code:
div3([],[],[],[]).
div3([X],[X],[],[]).
div3([X,Y],[X],[Y],[]).
div3([X,Y,Z],[X],[Y],[Z]).
div3([X | Y],A,B,C) :- length([X | Y],Sz),
Sz1 is 0,
div3([X | Y],A,B,C,Sz,Sz1).
div3([X | Y],A,B,C,Sz,Sz1) :- Sz1 < Sz//3, % am I done adding to the 1st list?
append(X,L,A), % add to the 1st list
Sz2 is Sz1+1, % increment the counter
div3(Y,L,B,C,Sz,Sz2),!.
div3([X | Y],A,B,C,Sz,Sz1) :- Sz1 < 2*Sz//3, % am I done adding to the 2nd list?
append(X,L,B), % add to the 2nd list
Sz2 is Sz1+1, % increment the counter
div3(Y,A,L,C,Sz,Sz2),!.
div3([X | Y],A,B,C,Sz,Sz1) :- Sz1 < Sz, % am I done adding to the 3rd list?
append(X,L,C),% add to the 3rd list
Sz2 is Sz1+1, % increment the counter
div3(Y,A,B,L,Sz,Sz2),!.
I think the first part of your code was almost right...
What you are looking for is a recursive predicate with 3 base cases and just one recursive clause.
div3([], [], [], []).
div3([X], [X], [], []).
div3([X,Y], [X], [Y], []).
div3([X,Y,Z|Tail], [X|XTail], [Y|YTail], [Z|ZTail]):-
div3(Tail, XTail, YTail, ZTail).
there are no end-cases in the code for the recursive predicate div3/5, the first 3 clauses are applied only for div3/3 calls (that's why calls like div3([4,2,42],X,Y,Z) succeed)
also, you call append/3 with an element, not a list, so it fails (unless you have a list of lists but even in that case, it's not what you want)
i would suggest switching to a more "declarative" approach, maybe with a predicate like get_N_elements(List,List_N,Rest) to avoid code repetition too
if maintaining source order does not matter, the following should suffice.
divide( [] , [] , [] , [] ) . % we're done when the source list is exhausted, OR ...
divide( [X] , [X] , [] , [] ) . % - it's only got 1 element, OR ...
divide( [X,Y] , [X] , [Y] , [] ) . % - it's only got 2 elements
divide( [X,Y,Z|T] , [X|Xs] , [Y|Ys] , [Z|Zs] ) :- % otherwise, split three elements amount the result lists and
divide(T,Xs,Ys,Zs) % - recurse down.
. %
The code above partitions the list
[a,b,c,d,e,f,g]
into
[a,d,g]
[b,e]
[c,f]
If you wish to maintain the order, this would work, describing what constitutes a correct solution (e.g., lists of lengths as equal as possible) and letting append/3 find the correct solution(s):
divide( L , X , Y , Z ) :-
append(X,T,L) , % split X off as a prefix of the source list L
append(Y,Z,T) , % divide the remainder (T) into a prefix Y and suffix Z
length(X,X1) , % compute the length of X
length(Y,Y1) , % compute the length of Y
length(Z,Z1) , % compute the length of Z
min_max([X1,Y1,Z1],Min,Max) , % determine the shortest and longest such length
Max - Min =< 1 , % and ensure that the delta is 1 or less
.
min_max([],0,0) .
min_max([H|T],Min,Max) :-
min_max(T,H,H,Min,Max)
.
min_max([],Min,Max,Min,Max) .
min_max([H|T], T1 , T2 , Min , Max ) :-
( H < T1 -> T3 = H ; T3 = T1 ) ,
( H > T2 -> T4 = H ; T4 = T2 ) ,
min_max(T,T3,T4,Min,Max)
.
The above basically says
Divide list L into 3 sublists X, Y and Z such that the delta between the lengths of
each sublist does not exceed 1.
In this case, you should see the list
[a,b,c,d,e,f,g]
divided into
[a,b]
[c,d]
[e,f,g]
One should note that this is non-deterministic and backtracking will find all possible such solutions.
Related
I tried resolving it by myself in the following way:
list_of_positives(L1, L2) :-
list_of_positives(L1, L2, []).
list_of_positives([], L, L).
list_of_positives([H|T], L2, L3) :-
( H > 0
-> list_of_positives(T,L2,[H|L3])
; list_of_positives(T,L2,L3)
).
The problem with this solution is that I get as response a reversed list of positive numbers. Can someone help me to find a way to get the list in the "correct order"?
You can solve the problem as follows:
positives([], []).
positives([H|T], P) :-
( H > 0
-> P = [H|R] % desired order!
; P = R),
positives(T, R) .
Example:
?- positives([2,-3,6,-7,1,4,-9], P).
P = [2, 6, 1, 4].
You want to use a difference list, a non-closed, or open list. So, something like this:
positives( [] , [] ) . % An empty list has not positives, and closes the list.
positives( [N|Ns] , [N|Rs] ) :- % For a non-empty list, we prepend N to the result list
N > 0, % - if N is positive
positives(Ns,Rs) % - and recurse down.
. %
positives( [N|Ns] , Rs ) :- % For non-empty lists, we discard N
N =< 0, % - if N is non-positive
positives(Ns,Rs) % - and recurse down.
. %
I am trying to create a prolog program that allows to convert a list into a list with the same length consisting of only 1 element from the original list. This element must be chosen in such a way that a minimal number of elements from the original list needs to be altered and all solutions are provided through backtracking e.g. [a,b] can become either [a,a] or [b,b] and [a,b,a] should become [a,a,a].
As you might have noticed, this is the same problem as finding the element with the most occurrences and making a new list with the same length as the original list containing only that one element. This resulted in the following code:
make_all_equal(List, Cost, Result):-
sort(0, #=<, List, Sorted),
occurs_most(Sorted, X, Nr),
length(List, N),
Cost is N - Nr,
repeat(X, N, Result).
occurs_most([], _, 0).
occurs_most([E|List], X, Nr):-
count(E, List, 1, N, Left),
occurs_most(Left, Y, Nr1),
(Nr1 =:= N ->
(X = Y, Nr = Nr1
; X = E, Nr = N) % I would like some backtracking here
;
(Nr1 > N ->
X = Y, Nr = Nr1
;
X = E, Nr = N
)
).
count(_, [], Acc, Acc, []).
count(X, [X|T], Acc, N, Tail):-
Acc1 is Acc + 1,
count(X, T, Acc1, N, Tail).
count(X, [Y|T], Acc, Acc, [Y|T]):-
X \= Y.
repeat(_, 0, []):- !. % There is no existing predicate, is there?
repeat(X, N, [X|T]):-
N > 0,
N1 is N - 1,
repeat(X, N1, T).
This code works, but as you might have noticed, the definition of occurs_most/3 looks terrible with all those if-statements. I also want to be able to get all solutions through backtracking as I did.
If anybody could help me with the occurs_most/3 predicate or better solution strategies for this seemingly simple problem, I would be very thankful. I'm afraid I've been trying for too long already.
PS: this is not a homework, but rather something like a 100th prolog problem...
Determinstic variant
First a more efficient but deterministic approach:
occurs_most([],_,0).
occurs_most(List,X,Nr) :-
msort(List,[H|T]),
most_sort(T,H,1,H,1,X,Nr).
most_sort([Hb|T],Ha,Na,Hb,Nb,Hr,Nr) :-
!,
Nb1 is Nb+1,
most_sort(T,Ha,Na,Hb,Nb1,Hr,Nr).
most_sort([Hc|T],_,Na,Hb,Nb,Hr,Nr) :-
Nb > Na,
!,
most_sort(T,Hb,Nb,Hc,1,Hr,Nr).
most_sort([Hc|T],Ha,Na,_,_,Hr,Nr) :-
most_sort(T,Ha,Na,Hc,1,Hr,Nr).
most_sort([],Ha,Na,_,Nb,Ha,Na) :-
Na >= Nb,
!.
most_sort([],_,_,Hb,Nb,Hb,Nb).
First you use msort/2 to sort the list. Then you iterate over the list. Each time you keep track of the currently most occuring one. From the moment the new head Hc differs from the previous one (Hb), you know you will never visit a Hb again (because of the transitivity of the order relation). So you keep counting the number of times in the current sequence. In case the sequence ends, you compare it with the previous sequence. In case it is larger, you accept that one.
Nondeterminstic variant
Now we can turn the predicate into a non-determinstic one:
occurs_most([],_,0).
occurs_most(List,X,Nr) :-
msort(List,[H|T]),
most_sort(T,H,1,X,Nr).
most_sort([Ha|T],Ha,Na,Hr,Nr) :-
!,
Na1 is Na+1,
most_sort(T,Ha,Na1,Hr,Nr).
most_sort([Hb|T],Ha,Na,Hr,Nr) :-
most_sort(T,Hb,1,Hc,Nc),
(Nc =< Na ->
((Hr = Ha,Nr = Na);
(Nc = Na ->
(Hr = Hc,Nr = Nc)
)
);
(Hr = Hc,Nr = Nc)
).
most_sort([],Ha,Na,Ha,Na).
A problem with this approach is that if there are multiple strikes that are less than on the right, we will repeat our current strike a few times (we will solve this later). For example (occurs_most([a,b,b,c,d],X,C)) will give twice L=b,C=2 simply because c is propagated back as well as d; and for both, we will pass b.
In this version, we don't need to keep track of the currently found maximum. We only work on the current stike. From the moment we reach the end of the list, we return the length of the current strike. Furthermore if we start a new strike, we first look to the strikes on the right. Than we compare this with the current one. If the current one less than, we only let the ones on the right pass. In case these are equal, we both pass th strikes on the right and the current one. If our own strike is larger than the one(s) on the right, we let only the current strike pass.
This algorithm runs in O(n log n) (for sorting) and O(n) for finding the value that occurs most.
Getting rid of the duplicate answers
We can get rid of the duplicated answers, by simply first construct a bag of tail strikes:
most_sort([Hb|T],Ha,Na,Hr,Nr) :-
findall(Hx/Nx,most_sort(T,Hb,1,Hx,Nx),Bag),
Bag = [_/Nc|_],
(
(Nc =< Na -> (Hr = Ha,Nr = Na); fail);
(Nc >= Na -> member(Hr/Nr,Bag); fail)
).
We know there is definitely something in the bag, because there are still elements on the right of the list, that will form a new strike. We collect these in the bag. We furthermore know that these elements all have the same count (otherwise they would not have passed other count tests). So we take the first element from the bag. Inspect the length, in case the length is less than or equal, we first answer with our own strike. In case the strikes in the bag are larger or equal, we pass all members in the bag.
Boosting it a bit further
Because you use the occurs_most frequently, on the same list, you can optimize the algorithm a bit, by sorting only once in the make_all_equal method). Furthermore you can also put length/2 in the front, since the length of a list is fixed so you don't calculate the length every time you find such most occurring value. Finally you can boost repeat/2 as well: only construct one list with one variable, and then instantiate the single variable will save you a lot of work (say the list is thousands of elements long, you can do instantiation in O(1)).
make_all_equal(List, Cost, Result):-
length(List, N),
msort(List,Sorted),
repeat(X, N, Result),
occurs_most(Sorted, X, Nr),
Cost is N - Nr.
occurs_most([],_,0).
occurs_most([H|T],X,Nr) :-
most_sort(T,H,1,X,Nr).
most_sort([Ha|T],Ha,Na,Hr,Nr) :-
!,
Na1 is Na+1,
most_sort(T,Ha,Na1,Hr,Nr).
most_sort([Hb|T],Ha,Na,Hr,Nr) :-
findall(Hx/Nx,most_sort(T,Hb,1,Hx,Nx),Bag),
Bag = [_/Nc|_],
(
(Nc =< Na -> (Hr = Ha,Nr = Na); fail);
(Nc >= Na -> member(Hr/Nr,Bag); fail)
).
most_sort([],Ha,Na,Ha,Na).
repeat(_, 0, []):-
!.
repeat(X, N, [X|T]) :-
N > 0,
N1 is N - 1,
repeat(X, N1, T).
My approach would be something like this:
make_all_equal( List , Cost , Result ) :-
frequencies( List , Frequencies ) , % compute the frequency table ordered in descending frequency
length(List,L) , % get the length of the source list
member( N:X , Frequencies ) , % get a frequency table entry
Cost is L-N , % compute the cost
repeat(X,L,Result) % generate the result by repeating the item the right number of times.
. % Easy!
%
% generate a list consisting of an item X repeated N times.
%
repeat( _ , 0 , [] ) .
repeat( X , N , [X|Xs] ) :- N > 0 , N1 is N-1 , repeat(N1,X,Xs) .
%
% compute a frequency table of pairs (N:X) ordered by descending frequency
%
frequencies( Xs , Fs ) :-
msort( Xs , S ) , % sort the source list
compute_freqs( S , R ) , % compute the [unordered] list of frequencies
msort( Xs , T ) , % sort that by frequency
reverse( T , Fs ) % reverse it to get descending sequence
. % Easy!
compute_freqs( [] , [] ) . % empty list? we're done!
compute_freqs( [X|Xs] , Fs ) :- % otherwise...
compute_freqs( Xs , X , 1 , Fs ) % - call the worker with the accumulators initialied properly
. % - Easy!
compute_freqs( [] , X , N , [N:X] ) . % when the source list is exhausted, put the frequency pair on the result set.
compute_freqs( [H|T] , H , N , Fs ) :- % otherwise, if we don't have a sequence break...
N1 is N+1 , % - increment the frequency count
compute_freqs(T,H,N1,Fs) % - recurse down, passing the new frequency count
. %
compute_freqs( [H:T] , X , N , [N:X|Fs] ) :- % otherwise, put the frequency pair on the result set
H \= X , % - assuming we have a sequence break,
compute_freqs(T,H,1,Fs) % - then recurse down, starting a new sequence
. % Easy!
How to write a prolog program to trim first N elements from a List in prolog using conc operation.
trim(L1,N,L2) which is true if L2 contains the first N elements of L1
Can somebody please help me.
Here is my answer and is it correct?
trim(L1, N, L2):- conc(L2,T,L1), length(L2,N),length(L1,N2), N2>= N
The easy solution uses length/2 and append/3, along these lines:
trim(L,N,S) :- % to trim N elements from a list
length(P,N) , % - generate an unbound prefix list of the desired length
append(P,S,L) . % - and use append/3 to get the desired suffix.
Note that the order doesn't really matter. This will work, too:
trim(L,N,S) :- % to trim N elements from a list
append(P,S,L) , % - split L into a prefix and suffix
length(P,N) . % - succeed if the prefix is of the desired length
I imagine that your instructor wants you to figure out a/the recursive solution. One might note that the algorithm for trimming items from the left end of a list is pretty simple:
Walk the list until you've visited N elements.
Once you've done that, whatever is left over is the desired result.
That leads to a simple solution:
trim( L , 0 , L ) . % Trimming zero elements from a list yields the original, unchanged list
trim( [H|T] , N , R ) :- % Otherwise,
N > 0 , % - assuming N is greater than zero
N1 is N-1 , % - we decrement N
trim( T , N1 , R ) % - and recurse down, discarding the head of the list.
. % That's about all there is too it.
If you wanted to be pedantic, one could enforce a constraint that the list should actually be a list (or at least list-like), something like:
trim( [] , 0 , [] ) . % Trimming zero elements from the empty list yields the empty list
trim( [H|T] , 0 , [H|T] ) . % Trimming zero elements from a non-empty list yields the same list
trim( [H|T] , N , R ) :- % Otherwise,
N > 0 , % - given that N is greater than zero
N1 is N-1 , % - we decrement N
trim( T , N1 , R ) % - and recurse down, discarding the head of the list.
. % That's about all there is to it.
Note that that something like
trim( [a,b,c] , 5 , R ) .
will fail: See if you can figure out how to make something like the above succeed, with R = []. Hint: it's not difficult.
Edited to Note: If you actually want to take the 1st N elements of the list, that's no more difficult:
prefix_of(L,N,P) :-
append(P,_,L) ,
length(P,N)
.
Or, rolling your own, you could do something like:
prefix_of( _ , 0 , [] ) . % once we've counted down to zero, close the result list and succeed.
prefix_of( [X|Xs] , N , [X|Ys] ) :- % otherwise,
N > 1 , % - given that N is greater than zero,
N1 is N-1 , % - decrement N
prefix_of( Xs , N1 , Ys ) % - and recurse down, with X prepended to the resullt list.
. % Again, that's about all there is to it.
Could you help me solve the following?
Write a ternary predicate delete_nth that deletes every n-th element from a list.
Sample runs:
?‐ delete_nth([a,b,c,d,e,f],2,L).
L = [a, c, e] ;
false
?‐ delete_nth([a,b,c,d,e,f],1,L).
L = [] ;
false
?‐ delete_nth([a,b,c,d,e,f],0,L).
false
I tried this:
listnum([],0).
listnum([_|L],N) :-
listnum(L,N1),
N is N1+1.
delete_nth([],_,_).
delete_nth([X|L],C,L1) :-
listnum(L,S),
Num is S+1,
( C>0
-> Y is round(Num/C),Y=0
-> delete_nth(L,C,L1)
; delete_nth(L,C,[X|L1])
).
My slightly extravagant variant:
delete_nth(L, N, R) :-
N > 0, % Added to conform "?‐ delete_nth([a,b,c,d,e,f],0,L). false"
( N1 is N - 1, length(Begin, N1), append(Begin, [_|Rest], L) ->
delete_nth(Rest, N, RestNew), append(Begin, RestNew, R)
;
R = L
).
Let's use clpfd! For the sake of versatility and tons of other good reasons:
:- use_module(library(clpfd)).
We define delete_nth/3 based on if_/3 and (#>=)/3:
delete_nth(Xs,N,Ys) :-
N #> 0,
every_tmp_nth_deleted(Xs,0,N,Ys).
every_tmp_nth_deleted([] ,_ ,_,[] ). % internal auxiliary predicate
every_tmp_nth_deleted([X|Xs],N0,N,Ys0) :-
N1 is N0+1,
if_(N1 #>= N,
(N2 = 0, Ys0 = Ys ),
(N2 = N1, Ys0 = [X|Ys])),
every_tmp_nth_deleted(Xs,N2,N,Ys).
Sample query:
?- delete_nth([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],2,Ys).
Ys = [1,3,5,7,9,11,13,15] % succeeds deterministically
Ok, how about something a little more general?
?- delete_nth([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],N,Ys).
N = 1 , Ys = []
; N = 2 , Ys = [1, 3, 5, 7, 9, 11, 13, 15]
; N = 3 , Ys = [1,2, 4,5, 7,8, 10,11, 13,14 ]
; N = 4 , Ys = [1,2,3, 5,6,7, 9,10,11, 13,14,15]
; N = 5 , Ys = [1,2,3,4, 6,7,8,9, 11,12,13,14 ]
; N = 6 , Ys = [1,2,3,4,5, 7,8,9,10,11, 13,14,15]
; N = 7 , Ys = [1,2,3,4,5,6, 8,9,10,11,12,13, 15]
; N = 8 , Ys = [1,2,3,4,5,6,7, 9,10,11,12,13,14,15]
; N = 9 , Ys = [1,2,3,4,5,6,7,8, 10,11,12,13,14,15]
; N = 10 , Ys = [1,2,3,4,5,6,7,8,9, 11,12,13,14,15]
; N = 11 , Ys = [1,2,3,4,5,6,7,8,9,10, 12,13,14,15]
; N = 12 , Ys = [1,2,3,4,5,6,7,8,9,10,11, 13,14,15]
; N = 13 , Ys = [1,2,3,4,5,6,7,8,9,10,11,12, 14,15]
; N = 14 , Ys = [1,2,3,4,5,6,7,8,9,10,11,12,13, 15]
; N = 15 , Ys = [1,2,3,4,5,6,7,8,9,10,11,12,13,14 ]
; N in 16..sup, Ys = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].
Please follow aBathologist instructive answer and explanation (+1). I just post my own bet at solution since there is a problem in ditto solution for ?‐ delete_nth([a,b,c,d,e,f],0,L)..
delete_nth(L,C,R) :-
delete_nth(L,C,1,R).
delete_nth([],_,_,[]).
delete_nth([_|T],C,C,T1) :- !, delete_nth(T,C,1,T1).
delete_nth([H|T],N,C,[H|T1]) :- C<N, C1 is C+1, delete_nth(T,N,C1,T1).
yields
1 ?- delete_nth([a,b,c,d,e,f],2,L).
L = [a, c, e].
2 ?- delete_nth([a,b,c,d,e,f],1,L).
L = [].
3 ?- delete_nth([a,b,c,d,e,f],0,L).
false.
A minor (?) problem: this code is deterministic, while the samples posted apparently are not (you have to input ';' to get a false at end). Removing the cut will yield the same behaviour.
An interesting - imho - one liner variant:
delete_nth(L,C,R) :- findall(E, (nth1(I,L,E),I mod C =\= 0), R).
but the C==0 must be ruled out, to avoid
ERROR: mod/2: Arithmetic: evaluation error: `zero_divisor'
Edited, correcting the mistake pointed out by #CapelliC, where predicate would succeed on N = 0.
I can see where you're headed with your solution, but you needn't bother with so much arithmetic in this case. We can delete the Nth element by counting down from N repeatedly until the list is empty. First, a quick note about style:
If you use spaces, line breaks, and proper placement of parenthesis you can help your readers parse your code. Your last clause is much more readable in this form:
delete_nth([X|L], C, L1):-
listnum(L, S),
Num is S+1,
C>0 -> Y is round(Num/C),
Y=0 -> delete_nth(L, C, L1)
; delete_nth(L, C, [X|L1]).
Viewing your code now, I'm not sure whether you meant to write
( C>0 -> ( Y is round(Num/C),
Y=0 -> delete_nth(L, C, L1) )
; delete_nth(L, C, [X|L1])
).
or if you meant
C>0 -> Y is round(Num/C),
( Y=0 -> delete_nth(L, C, L1)
; delete_nth(L, C, [X|L1])
).
or perhaps you're missing a ; before the second conditional? In any case, I suggest another approach...
This looks like a job for auxiliary predicates!
Often, we only need a simple relationship in order to pose a query, but the computational process necessary to resolve the query and arrive at an answer calls for a more complex relation. These are cases where it is "easier said than done".
My solution to this problem works as follows: In order to delete every nth element, we start at N and count down to 1. Each time we decrement the value from N, we move an element from the original list to the list of elements we're keeping. When we arrive at 1, we discard the element from our original list, and start counting down from N again. As you can see, in order to ask the question "What is the list Kept resulting from dropping every Nth element of List?" we only need three variables. But my answer the question, also requires another variable to track the count-down from N to 1, because each time we take the head off of List, we need to ask "What is the Count?" and once we've reached 1, we need to be able to remember the original value of N.
Thus, the solution I offer relies on an auxiliary, 4-place predicate to do the computation, with a 3-place predicate as the "front end", i.e., as the predicate used for posing the question.
delete_nth(List, N, Kept) :-
N > 0, %% Will fail if N < 0.
delete_nth(List, N, N, Kept), !. %% The first N will be our our counter, the second our target value. I cut because there's only one way to generate `Kept` and we don't need alternate solutions.
delete_nth([], _, _, []). %% An empty list has nothing to delete.
delete_nth([_|Xs], 1, N, Kept) :- %% When counter reaches 1, the head is discarded.
delete_nth(Xs, N, N, Kept). %% Reset the counter to N.
delete_nth([X|Xs], Counter, N, [X|Kept]) :- %% Keep X if counter is still counting down.
NextCount is Counter - 1, %% Decrement the counter.
delete_nth(Xs, NextCount, N, Kept). %% Keep deleting elements from Xs...
Yet another approach, following up on #user3598120 initial impulse to calculate the undesirable Nth elements away and inspired by #Sergey Dymchenko playfulness. It uses exclude/3 to remove all elements at a 1-based index that is multiple of N
delete_nth(List, N, Kept) :-
N > 0,
exclude(index_multiple_of(N, List), List, Kept).
index_multiple_of(N, List, Element) :-
nth1(Index, List, Element),
0 is Index mod N.
I am trying to solve a simple prolog question but I am not able to solve it. From a list a need to create a sublist given the index I and then from I the next elements given as N. If the index is greater than the list lenght I will get the sublist empty. If N (number of elements) is greater than the rest of elements in the list I will get all the elements from I until the end.
Here, I got one part of the assignment, I can get from the index I, the next elements N. Now I ask about the other parts in the assignment:
1) When I (index) is longer than the list length, I have to get an empty list in the sublist.
?- sublist([a,b,c,d],5,2,L)
L=[]
2) When N (Next elements) is greater than the number of elements we have rest, I need to get all the elements from that position till the end.
?- sublist([a,b,c,d],4,4,L)
L=[d]
The code I already have is the next one, this one is working:
sublist([X|_],1,1,[X]).
sublist([],_,_,[]).% I use this one for the case bases
sublist([X|Xs],1,K,[X|Ys]):-
K>1,
K1 is K-1,
sublist(Xs,1,K1,Ys).
sublist([_|Xs],I,K,Ys):-
I > 1,
I1 is I-1,
sublist(Xs,I1,K,Ys).
sublist([X|_], 1, 1, [X]).
This is a good clause. It says that a sublist of length 1 starting at 1 taken from the list [X|_] is [X].
sublist([X|Xs], 1, K, [X|Ys]) :-
K > 1,
K1 is K - 1,
sublist(Xs, 1, K1, Ys).
This is also a good clause. It says that the sublist of length K starting at 1 taken from [X|Xs] starts with X and has a tail Ys which is the sublist of length K-1 from the tail of the first list (Xs) starting at 1.
sublist([_|Xs], I, K, Ys) :-
I > 1,
I1 is I - 1,
K1 is K - 1,
sublist(Xs, I1, K1, Ys).
This clause has an issue. If you have a list [_|Xs] and want to take a sublist of length K start at I (for I greater than 1), you take the sublist of length K-1 from its tail starting at position I-1. The question is: why would the sublist now need to be length K-1? The purpose of this clause should be to reduce the problem to the case where you're dealing with a starting index of 1, then let the second clause take care of the rest.
Then in your definition of the desired behavior, you have: If N (number of elements) is greater than the rest of elements in the list I will get all the elements from I until the end. This notion currently isn't in any of the clauses. The base case is currently your first clause which specifically requires a length of 1 to produce a list of length 1. You need another base case clause that handles the case where the first list goes empty but K might still be any value:
sublist([], ?, _, ?).
Just fill in the ? with something logical. :)
just to show how nondeterministic builtins like nth1/3 can help...
sublist(List, From, Count, SubList) :-
findall(E, (nth1(I, List, E), I >= From, I < From + Count), SubList).
edit a note to say that this 'one liner' is actually a lot less efficient than a crafted sublist/4.
Indeed,
2 ?- N=1000000,length(L,N),time(sublist(L,N,1,V)).
% 3,000,014 inferences, 2.129 CPU in 2.134 seconds (100% CPU, 1409024 Lips)
N = 1000000,
L = [_G28, _G31, _G34, _G37, _G40, _G43, _G46, _G49, _G52|...],
V = [_G3000104].
3 ?- N=1000000,length(L,N),time(sublist(L,1,1,V)).
% 4,000,012 inferences, 2.549 CPU in 2.553 seconds (100% CPU, 1569076 Lips)
N = 1000000,
L = [_G28, _G31, _G34, _G37, _G40, _G43, _G46, _G49, _G52|...],
V = [_G3000104].
I'm going to see if some kind of cut inside findall' predicate could solve this problem, but it's unlikely. This one is better:
sublist(List, From, Count, SubList) :-
To is From + Count - 1,
findall(E, (between(From, To, I), nth1(I, List, E)), SubList).
18 ?- N=1000000,length(L,N),time(sublist(L,3,3,V)).
% 28 inferences, 0.000 CPU in 0.000 seconds (93% CPU, 201437 Lips)
N = 1000000,
L = [_G682, _G685, _G688, _G691, _G694, _G697, _G700, _G703, _G706|...],
V = [_G3000762, _G3000759, _G3000756].
Here's one solution (though it's probably not what your professor wants):
sublist( Xs , Offset , Count , Ys ) :- %
length(Prefix,Offset ) , % construct a list of variables of length 'offset'
length(Ys,Count) , % construct a list of variables of length 'count'
append(Prefix,Suffix,Xs) , % strip the first first 'offset' items from the source list ,
append(Ys,_,Suffix) % extract the first 'count' items from what's left.
. % easy!
That's one approach, letting Prolog's built-ins do the work for you.
Here's another approach that doesn't use any built-ins. This one uses one helper predicate that simply splits a list into a prefix of the specified length, and a suffix, consisting of whatever is left over.
sublist( Xs , Offset , Count , Ys ) :-
split(Xs,Offset,_,X1) , % extract the first 'offset' items from the lsit and toss them
split(X1,Count,Ys,_) % extract the first 'count' items from the remainder to get the result.
.
split( [] , 0 , [] , [] ) . % splitting a zero-length prefix from an empty list yields a zero-length prefix and a zero length suffix.
split( [X|Xs] , 0 , [] , [X|Xs] ) . % splitting a zero-length prefix from a non-empty list yields a zero-length prefix and the non-empty list.
split( [X|Xs] , N , [X|Ps] , Qs ) :- % Otherwise...
N > 0 , % - if the count is positive
N1 is N-1 , % - we decrement count
split( Xs , N1 , Ps , Qs ) % - and recurse down, prepending the head of the source list to the prefix
. % Easy!