At a recent phone interview I was asked the following dynamic programming problem but couldn't come up with an algorithm for it:
Suppose there is a path with n positions. Consider the set S = {A,B,C}. Each position on the path has an associated non-empty subset of S. For each position on the path, we can choose one element from its associated subset. For a given position i on the path, its “value” is determined by the total number of distinct elements from the positions it has access to. The positions it has access to is given by the set {i-1, i, i+1} (for i=1 it is just {0,1} and for i=n it is just {n, n-1}). We want to maximize the sum of the “value” of all positions.
So for example, if I had n=5 and the following subsets for each position 1…5:
S1 = {A,C}, S2={A, B}, S3={A,B,C}, S4={A,C}, S5={A,B,C}
Then one such possible arrangement to maximize the sum would be [A, B, C, A, B], which would be 2 + 3 + 3 + 3 + 2 = 13.
I'd like to write an algorithm that, given a list of subsets (where the nth subset corresponds to the nth position), returns the maximum sum of the value of all positions. It should be bounded by a polynomial function of n.
Example Input: [['A', 'C'], ['A', 'B'], ['A', 'B', 'C'], ['A', 'C'], ['A', 'B', 'C']]
Expected Output: 13
Given that my phone interview is already over with and that I've still been unable to solve this problem after giving it more thought, I'd rather just see a working solution at this point. Thanks!
The key to solving the problem is to realize that, given an arrangement A with a certain score, the new score of A after appending an element z depends only on the final two elements of A.
Given an array ending with the (not necessarily distinct) elements x and y, the increase in score after appending an element z is:
1 // from z on itself
+ 1 if (z != y and z != x) else 0 // from y gaining z as neighbor
+ 1 if (z != y) else 0 // from z gaining y as neighbor
For your example, there are 4 possible arrangements with the first two positions:
Subsets:
S1 = {A, C}, S2 = {A, B},
S3 = {A, B, C}, S4 = {A, C}, S5 = {A, B, C}
After placing the first two elements:
[A, A] max score = 2
[A, B] max score = 4
[C, A] max score = 4
[C, B] max score = 4
after appending a third element (from S3), all possible 'last two' elements and the maximum score of any arrangement with those 'last two' elements:
After S3 = {A, B, C}
[A, A] max score = 5
[A, B] max score = 7
[A, C] max score = 6
[B, A] max score = 7
[B, B] max score = 5
[B, C] max score = 7
Here, for instance, the unique maximal score arrangement ending in A, A is [C, A, A], although we only care about the last two values and the score.
After all five subsets, the feasible 'last two elements' of arrangements and the maximum score of any corresponding arrangement will be:
[A, A] max score = 11
[A, B] max score = 13
[A, C] max score = 12
[C, A] max score = 13
[C, B] max score = 13
[C, C] max score = 11
so we can return 13. With extra bookkeeping throughout the algorithm, we can also reconstruct the full arrangement.
Here's the three-variable Dynamic Programming (DP) formula:
DP(index, y, z) is defined as the
maximum score for an arrangement on PathArray[0, 1, ..., index]
with final two elements [y, z], for any y in Subsets[index-1]
and z in Subsets[index]
DP(index, y, z) = max over all x in Subsets[index-2] of
(DP(index-1, x, y) + AddedScore(x, y, z))
The answer to your question is the maximum value of DP(n-1, a, b) for any valid a and b.
I've excluded the base case when the path has length 2: you should directly solve for the score of the one and two element cases.
With one element: the score is always 1.
With two elements: the score is 4 if the elements are not equal, otherwise, the score is 2.
A Python implementation:
def dp_solve(subsets):
if len(subsets) == 1:
return 1
def added_value(grandparent, parent, child) -> int:
return (1
+ (1 if child != grandparent and child != parent else 0)
+ (1 if child != parent else 0))
pair_dict = collections.Counter()
for x, y in itertools.product(subsets[0], subsets[1]):
pair_dict[x, y] = 4 if x != y else 2
for subset in subsets[2:]:
new_dict = collections.Counter()
for old_key, old_value in pair_dict.items():
grandparent, parent = old_key
for child in subset:
new_value = old_value + added_value(grandparent,
parent,
child)
new_dict[parent, child] = max(new_dict[parent, child],
new_value)
pair_dict = new_dict
return max(pair_dict.values())
my_lists = [['A', 'C'], ['A', 'B'], ['A', 'B', 'C'], ['A', 'C'], ['A', 'B', 'C']]
print(dp_solve(my_lists)) # Prints 13
I'm relatively certain this iterative version always produces the max, though I can't prove it.
Note: this assumes each set doesn't contain duplicates, if that's the case needs slight modification
if only one position in path, select any value from it's set
else:
starting from first position on path
While (not at last element of path) {
if position set only has 1 value, select it
else if position set has unique value not in neighbors' sets (or single neighbor for ends), select it
else select value that's not the same as prior position, prioritizing a value that's in (next position + 1)'s set (assuming that position isn't out of bounds)
outputArray[position] = value
position++
}
//at last position
if only 1 value select it
else select a value different from previous
outputArray[position] = value
outputArray should now contain values from each set that maximize distinctness among neighbors
Related
I've been scratching my head about this for two days now and I cannot come up with a solution. What I'm looking for is a function f(s, n) such that it returns a set containing all subsets of s where the length of each subset is n.
Demo:
s={a, b, c, d}
f(s, 4)
{{a, b, c, d}}
f(s, 3)
{{a, b, c}, {a, b, d}, {a, c, d}, {b, c, d}}
f(s, 2)
{{a, b}, {a, c}, {a, d}, {b, c}, {b, d}, {c, d}}
f(s, 1)
{{a}, {b}, {c}, {d}}
I have a feeling that recursion is the way to go here. I've been fiddling with something like
f(S, n):
for s in S:
t = f( S-{s}, n-1 )
...
But this does not seem to do the trick. I did notice that len(f(s,n)) seems to be the binomial coefficient bin(len(s), n). I guess this could be utilized somehow.
Can you help me please?
Let us call n the size of the array and k the number of elements to be out in a subarray.
Let us consider the first element A[0] of the array A.
If this element is put in the subset, the problem becomes a (n-1, k-1) similar problem.
If not, it becomes a (n-1, k) problem.
This can be simply implemented in a recursive function.
We just have to pay attention to deal with the extreme cases k == 0 or k > n.
During the process, we also have to keep trace of:
n: the number of remaining elements of A to consider
k: the number of elements that remain to be put in the current subset
index: the index of the next element of A to consider
The current_subset array that memorizes the elements already selected.
Here is a simple code in c++ to illustrate the algorithm
Output
For 5 elements and subsets of size 3:
3 4 5
2 4 5
2 3 5
2 3 4
1 4 5
1 3 5
1 3 4
1 2 5
1 2 4
1 2 3
#include <iostream>
#include <vector>
void print (const std::vector<std::vector<int>>& subsets) {
for (auto &v: subsets) {
for (auto &x: v) {
std::cout << x << " ";
}
std::cout << "\n";
}
}
// n: number of remaining elements of A to consider
// k: number of elements that remain to be put in the current subset
// index: index of next element of A to consider
void Get_subset_rec (std::vector<std::vector<int>>& subsets, int n, int k, int index, std::vector<int>& A, std::vector<int>& current_subset) {
if (n < k) return;
if (k == 0) {
subsets.push_back (current_subset);
return;
}
Get_subset_rec (subsets, n-1, k, index+1, A, current_subset);
current_subset.push_back(A[index]);
Get_subset_rec (subsets, n-1, k-1, index+1, A, current_subset);
current_subset.pop_back(); // remove last element
return;
}
void Get_subset (std::vector<std::vector<int>>& subsets, int subset_length, std::vector<int>& A) {
std::vector<int> current_subset;
Get_subset_rec (subsets, A.size(), subset_length, 0, A, current_subset);
}
int main () {
int subset_length = 3; // subset size
std::vector A = {1, 2, 3, 4, 5};
int size = A.size();
std::vector<std::vector<int>> subsets;
Get_subset (subsets, subset_length, A);
std::cout << subsets.size() << "\n";
print (subsets);
}
Live demo
One way to solve this is by backtracking. Here's a possible algorithm in pseudo code:
def backtrack(input_set, idx, partial_res, res, n):
if len(partial_res == n):
res.append(partial_res[:])
return
for i in range(idx, len(input_set)):
partial_res.append(input_set[i])
backtrack(input_set, idx+1, partial_res, res, n) # path with input_set[i]
partial_res.pop()
backtrack(input_set, idx+1, partial_res, res, n) # path without input_set[i]
Time complexity of this approach is O(2^len(input_set)) since we make 2 branches at each element of input_set, regardless of whether the path leads to a valid result or not. The space complexity is O(len(input_set) choose n) since this is the number of valid subsets you get, as you correctly pointed out in your question.
Now, there is a way to optimize the above algorithm to reduce the time complexity to O(len(input_set) choose n) by pruning the recursive tree to paths that can lead to valid results only.
If n - len(partial_res) < len(input_set) - idx + 1, we are sure that even if we took every remaining element in input_set[idx:] we are still short at least one to reach n. So we can employ this as a base case and return and prune.
Also, if n - len(partial_res) == len(input_set) - idx + 1, this means that we need each and every element in input_set[idx:] to get the required n length result. Thus, we can't skip any elements and so the second branch of our recursive call becomes redundant.
backtrack(input_set, idx+1, partial_res, res, n) # path without input_set[i]
We can skip this branch with a conditional check.
Implementing these base cases correctly, reduces the time complexity of the algorithm to O(len(input_set) choose k), which is a hard limit because that's the number of subsets that there are.
subseqs 0 _ = [[]]
subseqs k [] = []
subseqs k (x:xs) = map (x:) (subseqs (k-1) xs) ++ subseqs k xs
Live demo
The function looks for subsequences of (non-negative) length k in a given sequence. There are three cases:
If the length is 0: there is a single empty subsequence in any sequence.
Otherwise, if the sequence is empty: there are no subsequences of any (positive) length k.
Otherwise, there is a non-empty sequence that starts with x and continues with xs, and a positive length k. All our subsequences are of two kinds: those that contain x (they are subsequences of xs of length k-1, with x stuck at the front of each one), and those that do not contain x (they are just subsequences of xs of length k).
The algorithm is a more or less literal translation of these notes to Haskell. Notation cheat sheet:
[] an empty list
[w] a list with a single element w
x:xs a list with a head of x and a tail of xs
(x:) a function that sticks an x in front of any list
++ list concatenation
f a b c a function f applied to arguments a b and c
Here is a non-recursive python function that takes a list superset and returns a generator that produces all subsets of size k.
def subsets_k(superset, k):
if k > len(superset):
return
if k == 0:
yield []
return
indices = list(range(k))
while True:
yield [superset[i] for i in indices]
i = k - 1
while indices[i] == len(superset) - k + i:
i -= 1
if i == -1:
return
indices[i] += 1
for j in range(i + 1, k):
indices[j] = indices[i] + j - i
Testing it:
for s in subsets_k(['a', 'b', 'c', 'd', 'e'], 3):
print(s)
Output:
['a', 'b', 'c']
['a', 'b', 'd']
['a', 'b', 'e']
['a', 'c', 'd']
['a', 'c', 'e']
['a', 'd', 'e']
['b', 'c', 'd']
['b', 'c', 'e']
['b', 'd', 'e']
['c', 'd', 'e']
I have a function f that takes a list of items as it's single parameter and returns true if the ordering of the items is accepted or false if the ordering of the items is not accepted.
There exists at least one or more permutations of list l which f(l) returns true.
Function f is a black box (we don't have it's source code) and the type of the elements held by list l are also unknown or generic.
p is a permutation of list l according to user preferences. The most preferred item has index 0 the least preferred item has index l.size()-1
list p will always contain all elements of list l.
The goal is to find a permutation of l let's call it p_accepted where f(p_accepted) returns true and preference p is maximized.
Here's an example
given l = [a, b, c, d, e, f]
given p = [c, a, f, b, e, d]
given f([ a, b, c, d, e, f ]) = false
given f([ c, a, f, b, e, d ]) = false
given f([ d, e, b, f, a, c ]) = true
given f([ f, e, d, c, b, a ]) = true
given f([ c, b, f, a, d, e ]) = true
given f([ a, c, f, b, e, d ]) = true
given f([ anything else ]) = false
the expected output for p_accepted is [c, b, f, a, d, e]
it is accepted because f(p_accepted) returns true and no other permutation of l ranks the item 'c' as high. item 'c' is the most preferred by the user since it has index 0
Implementations in pseudo code or any language are accepted.
[EDIT]
Clarifications
list p will always contain all elements of list l
list l items can only be compared by identity, i.e.: by reference
so an item in list p can be found in list l by l[i] == p[j]
list l items cannot always be compared like in the example where a compare function c might determine that a < b i.e.: c('a', 'b') = 1.
[EDIT 2]
To understand preferences better
Imagine Alice and Bob being forced to do 4 tasks together at the same time in order. [task a, task b, task c, task d].
Alice has one preferred order for doing the tasks [a,b,c,d]. Bob has two preferred orders for doing the tasks [a,c,b,d], [a,d,b,c]. If you are Alice, the function f would return true only for [a,c,b,d] and [a,d,b,c] which are Bob's preferences, since both like to do task a first p_accepted should start with a.
Note that this is an analogy function f does not accept permutations based on multiple user's order preference.
I'm new to constraint-programming (coming from c#) and I'm trying to solve this problem. Unfortunately I don't have a name for this kind of puzzle so I'm not sure what to search for. The closest examples I can find are Nonogram and Tomography puzzles.
Puzzle description:
The player is given an empty game board (varying size) that they must fill with n-colors, using clue patterns for the rows and columns. Each clue pattern is the sequence of colors in that row/col but with consecutive duplicates removed.
Here is an example easy small 4x4 grid with 3 colors:
rbg,rbr,grb,bgbg <- (top-to-bottom column constraints)
_,_,_,_ rgb <- (row constraints)
_,_,_,_ brg
_,_,_,_ b
_,_,_,_ grbg
Solutions (2):
r,r,g,b
b,?,r,g
b,b,b,b
g,r,b,g
? Can be either red or blue but not green.
Pattern examples below.
Examples given 6-length sequences to pattern:
aaaaaa -> a
aabbcc -> abc
abbbbc -> abc
cabbbc -> cabc
bbbaac -> bac
abbaab -> abab
abcabc -> abcabc
Examples given pattern to potential solution sequences:
abc -> abc (3 length solution)
abc -> abcc, abbc, aabc (4 length solutions)
abc -> abccc, abbcc, abbbc, aabbc, aaabc (5 length solutions)
I've tried to solve it in C# or-tools and MiniZinc but the biggest problem I have is building the constraints. I can generate the patterns from a sequence (in c# imperative way) but then how to turn that into a constraint?
How I'm thinking about it: generate all potential sequences from each clue pattern. Then make a constraint for the corresponding row/col that says it must be one of those sequences.
Example from top row in above puzzle: rgb to [4-length sequences] -> rgbb, rggb, rrgb, and then add a constraint for that row: must equal one of these sequences.
Am I thinking about this right? Any smarter ways to do it?
Thanks for any advice.
=====================================
Edit after some progress:
This MiniZinc correctly solves the top row for the pattern abc which has 3 solutions of 4 length: aabc, abbc, abcc.
include "globals.mzn";
array [1..4, 1..4] of var 1..3: colors;
constraint regular(row(colors, 1), 4, 3,
[|
% a, b, c
2,0,0| % accept 'a'
2,3,0| % accept 'a' or 'b' ?
0,3,4| % accept 'b' or 'c' ?
0,0,4| % accept 'c'
|], 1, {4});
% Don't care about rest of grid for now.
constraint forall(i,j in 1..4 where i > 1) (row(colors, i)[j] = 1);
solve satisfy;
output [show(colors)];
However I'm not sure how to handle larger grids with many patterns other than hardcoding everything like this. I will experiment a bit more.
The constraints you are talking about seem to be easily represented as regular expressions. For example your abc example with varying length can be caught using the regular expression abc.*, which requires one a then one b, and then one c, it will accept anything else afterwards.
In MiniZinc these kinds of constraints are expressed using the regular predicate. The regular predicate simulates an automaton with accepting states. By providing the allowed state-transitions the model is constraint.
The example expression abc.* would be enforced by the following constraint item:
% variables considered, nr states, input values
constraint regular(VARS, 4, 1..3, [|
% a, b, c
2,0,0| % accept 'a'
0,3,0| % accept 'b'
0,0,4| % accept 'c'
4,4,4| % accept all
|], 1, {4}); % initial state, accepting states
In Prolog(language), I use DCG form to describe such problems. It is extended BNF form.
So I suggest finding approach with Extended BNF Form in your environment.
SWI-Prolog example:
color_chunk_list(Encoded,Decoded):-
phrase(chunk_list(Encoded),Decoded),
chk_continuity(Encoded).
chunk_list([])-->[].
chunk_list([First|Rest])-->colorrow(First),chunk_list(Rest).
colorrow(Color)-->[Color],colorrow(Color).
colorrow(Color)-->[Color].
chk_continuity([First,Second|Rest]):-First \= Second,chk_continuity([Second|Rest]).
chk_continuity([_]).
In this program, encodings and decodings are bidirectional.
Tests:
?- length(L,4),color_chunk_list([r,g],L).
L = [r, r, r, g] ;
L = [r, r, g, g] ;
L = [r, g, g, g] ;
false.
?- length(L,6),color_chunk_list([a,b,c],L).
L = [a, a, a, a, b, c] ;
L = [a, a, a, b, b, c] ;
L = [a, a, a, b, c, c] ;
L = [a, a, b, b, b, c] ;
L = [a, a, b, b, c, c] ;
L = [a, a, b, c, c, c] ;
L = [a, b, b, b, b, c] ;
L = [a, b, b, b, c, c] ;
L = [a, b, b, c, c, c] ;
L = [a, b, c, c, c, c] ;
false.
?- color_chunk_list(L,[a,a,b,b,c,c]).
L = [a, b, c] ;
false.
?- color_chunk_list(L,[b,r,b,r,r,g,g,b]).
L = [b, r, b, r, g, b] ;
false.
In ECLiPSe, which is prolog based CLP system (not IDE one),
above predicate(color_chunk_list) can be turned into clp constraint
with propia mechanism and can genarate clp propagation.
I have completed a haskell code to compute the delaunay triangulation of a given point set. However, now i am stuck as to how and what method needs to be completed in prolog
Haskell:
-- The type for a single point.
type Point a = (a,a)
-- The type for a pair of points.
type Pair a = (Point a, Point a)
-- The type for a triple of points.
type Triple a = (Point a, Point a, Point a)
-- Predicate for a triple of 3 points is in CCW order or not
isCCW :: Real a => Triple a -> Bool
isCCW ((x1, y1), (x2, y2), (x3, y3)) = (x2-x1)*(y3-y1)-(x3-x1)*(y2-y1) > 0
-- Convert a triple to a CCW triple
toCCW :: Real a => Triple a -> Triple a
toCCW (p1, p2, p3) = if (isCCW ((( p1, p2, p3 )))) then (p1, p2, p3)
else (p1, p3, p2)
-- Generate all pairs of points from a list of points.
-- Each pair should appear exactly once in the result.
pairsFromPoints :: Real a => [Point a] -> [Pair a]
pairsFromPoints [] = []
pairsFromPoints (x:xs) = map makePair xs ++ (pairsFromPoints xs)
where makePair y = (x,y)
-- Generate all unique CCW triples of points from a list of points
-- Each triple should appear exactly once in the result and be
-- CCW ordered.
triplesFromPoints :: Real a => [Point a] -> [Triple a]
triplesFromPoints [] = []
triplesFromPoints (x:xs) = map makeTriple (pairsFromPoints xs) ++ (triplesFromPoints xs)
where makeTriple (y,z) = toCCW(x,y,z)
And this is the Prolog code that I'm stuck on.
Prolog:
% concatenate(L1, L1, T) is true if and only if T is equal to the concatenation
% of lists L1 and L2.
%
concatenate(L1, L2, T).
% singletons(P, Q) is true if and only if Q is equivalent to the list obtained
% from P if each item in P is wrapped in "[" and "]" to create a singleton list.
%
singletons(P, Q).
% prefix_all(I, P, Q) is true if and only if P is a list of lists and Q is the
% list obtained by prepending I to each element in P.
%
prefix_all(I, P, Q).
% pairs_all(I, P, Q) is true if and only if Q is the list obtained by pairing I
% with each item in P.
%
pairs_all(I, P, Q).
% Predicate to test if three points are in counter-clockwise orientation.
%
is_ccw([[X1,Y1],[X2,Y2],[X3,Y3]]) :- (X2-X1)*(Y3-Y1)-(X3-X1)*(Y2-Y1) > 0.
% ccw(T, U) is true if and only if T and U are triples containing the same
% points and U is in counter-clockwise orientation.
%
ccw(T, U).
% ccw_triples(P, Q) is true if and only if Q is the list containing all the
% triples of points in the list P except arranged in ccw orientation.
%
ccw_triples(P, Q).
% pairs_of_points([H|T], Q) is true if and only if Q is a list containing all of
% the distinct pairs that can be made from the points in the list of points
% [H|T].
%
pairs_of_points([H|T], Q).
% triples_of_points([H|T], Q) is true if and only if Q is a list containing all
% of the distinct triples that can be made from the points in the list of points
% [H|T].
%
triples_of_points([H|T], X).
% is_delaunay_triangle(T, P) is true if and only if no point of the point set P
% is in the circle defined by the triple T (which here you may assume is in CCW
% orientation). This predicate is undefined if P is empty.
%
is_delaunay_triangle(T, P).
% delaunay_triangles(T, P, X) is true if and only if X is the subset of
% triangles from T that are Delaunay triangles for the point set P.
%
% HINT: Define this recursively on the list of triangles T.
%
delaunay_triangles(T, P, X).
% delaunay_triangulation(P, X) is true if and only if X is the list of Delaunay
% triangles for the point list P.
% HINT: Create temporary variables to describe all triples from P as well as all
% CCW triples from P. Use the predicates you've already defined above!
%
delaunay_triangulation(P, X).
I am not exactly sure what exactly the first four methods exactly mean, if someone could give me that as a start i would be content I'm not asking you to do my assignment either but any help would be greatly appreciated!
concatenate(L1, L1, T) is true if and only if T is equal to the concatenation of lists L1 and L2.
This should read concatenate(L1, L2, T) and is the standard append/3 predicate. It corresponds to Haskell's (++) function for list concatenation. For example, it should behave as follows:
?- concatenate([], [1, 2, 3], T).
T = [1, 2, 3].
?- concatenate([1, 2], [3, 4], T).
T = [1, 2, 3, 4].
singletons(P, Q) is true if and only if Q is equivalent to the list obtained from P if each item in P is wrapped in "[" and "]" to create a singleton list.
It looks like this should behave as follows:
?- singletons([foo, bar, baz, 42], Singletons).
Singletons = [[foo], [bar], [baz], [42]].
You may find this easier to do if you first define an auxiliary predicate that only wraps a single term in a list:
?- singleton(foo, Q).
Q = [foo].
?- singleton(foo, [foo]).
true.
(You do not need to use this in your definition of singletons/2, writing it might just clarify part of the problem.)
prefix_all(I, P, Q) is true if and only if P is a list of lists and Q is the list obtained by prepending I to each element in P.
The meaning of this depends on what "prepending" is supposed to mean, but the word prefix suggests that I is to be interpreted as a list that will be the prefix of any list in Q. So something like:
?- prefix_all([pre, fix], [[1, 2], [], [foo, bar, baz]], Q).
Q = [[pre, fix, 1, 2], [pre, fix], [pre, fix, foo, bar, baz]].
Once again, it may help to think about what it means to be the prefix of one list:
?- prefix_one([pre, fix], [1, 2], Xs).
Xs = [pre, fix, 1, 2].
Do not define this predicate! Think about what it means in terms of what you already know.
pairs_all(I, P, Q) is true if and only if Q is the list obtained by pairing I with each item in P.
This looks like it's meant to behave something like this:
?- pairs_all(foo, [1, 2, three, 4], Pairs).
Pairs = [ (foo, 1), (foo, 2), (foo, three), (foo, 4)].
Again, it may help to first define an auxiliary that constructs a single pair:
?- pair(foo, 5, Pair).
Pair = (foo, 5).
The question require me to write a predicate seqList(N, L), which is satisfied when L is the list [f0, . . . , fN].
Where the fN = fN-1 + fN-2 + fN-3
My code is to compare the head of a list given, and will return true or false when compared.
seqList(_,[]).
seqList(N,[H|T]) :-
N1 is N - 1,
seq(N,H),
seqList(N1,T).
However, it only valid when the value is reversed,
e.g. seqList(3,[1,1,0,0]) will return true, but the list should return me true for
seqList(3,[0,0,1,1]). Is there any way for me to reverse the list and verifies it correctly?
It seems that you want to generate N elements of a sequence f such that f(N) = f(N-1) + f(N-2) + f(N-3) where f(X) is the X-th element of the sequence list, 0-based. The three starting elements must be pre-set as part of the specification as well. You seem to be starting with [0,0,1, ...].
Using the approach from Lazy lists in Prolog?:
seqList(N,L):- N >= 3, !,
L=[0,0,1|X], N3 is N-3, take(N3, seq(0,0,1), X-[], _).
next( seq(A,B,C), D, seq(B,C,D) ):- D is A+B+C.
Now all these functions can be fused and inlined, to arrive at one recursive definition.
But you can do it directly. You just need to write down the question, to get the solution back.
question(N,L):-
Since you start with 0,0,1, ... write it down:
L = [0, 0, 1 | X],
since the three elements are given, we only need to find out N-3 more. Write it down:
N3 is N-3,
you've now reduced the problem somewhat. You now need to find N-3 elements and put them into the X list. Use a worker predicate for that. It also must know the three preceding numbers at each step:
worker( N3, 0, 0, 1, X).
So just write down what the worker must know:
worker(N, A, B, C, X):-
if N is 0, we must stop. X then is an empty list. Write it down.
N = 0, X = [] .
Add another clause, for when N is greater than 0.
worker(N, A, B, C, X):-
N > 0,
We know that the next element is the sum of the three preceding numbers. Write that down.
D is A + B + C,
the next element in the list is the top element of our argument list (the last parameter). Write it down:
X = [D | X2 ],
now there are one less elements to add. Write it down:
N2 is N - 1,
To find the rest of the list, the three last numbers are B, C, and D. Then the rest is found by worker in exactly the same way:
worker( N2, B, C, D, X2).
That's it. The question predicate is your solution. Rename it to your liking.