简体   繁体   中英

Create a sublist from a list given an index and a number of elements. Prolog

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM