Reputation: 41
puzzling over a problem of trying to return the second to last element in a list written in Prolog. This language is interesting to use but I'm having trouble getting my head wrapped around it. Here is what I have:
secondLast([X], X).
secondLast(X, [Y], X) :- secondLast(Y, K).
secondLast(X, [Y|Z], K) :- secondLast(Y, Z, K).
secondLast([X|Z], Ans) :- secondLast(X, Z, Ans).
so calling secondLast([a, b, c, d], X).
X
should equal c
.
Any ideas?
Thanks!
Upvotes: 2
Views: 5985
Reputation: 58234
Sergey and CapelliC have offered two nice solutions to the problem. Let's have a look to see what's wrong with the original:
1) secondLast([X], X).
2) secondLast(X, [Y], X) :- secondLast(Y, K).
3) secondLast(X, [Y|Z], K) :- secondLast(Y, Z, K).
4) secondLast([X|Z], Ans) :- secondLast(X, Z, Ans).
In Prolog, since it is about defining relations between entities with predicates, not defining functions, it helps to describe what a predicate means in terms of, "Something is true if some other things are true". The if in Prolog is expressed as :-
.
We'll look at clause #4 since this appears to be your main clause. This one says, Ans
is the second to last element of [X|Z]
if Ans
is the second to last element of Z
with X
as the head. It's unclear what this 3-argument version of secondLast
means. However, if the list is 3 or more elements, it seems clear that X
will become irrelevant (as will be seen in clauses 2 and 3).
Clause #1 says, X
is the second to last element in list [X]
. However, the element X
is the last and only element in the list [X]
. So this clause is logically incorrect.
Clauses #2 is a bit confusing. It introduces a variable in the clause, K
, which is only used once and not defined or used anywhere else in the clause. It also ignores X
because, as described above, it has become irrelevant since it's no longer a candidate for second-to-last element. Prolog has given you a warning about singleton elements K
and X
, which is similar to the warning in C that a variable is "defined but never used" or "is assigned a value that is never used". Clause #3 has the same issue.
In all that, I think I can see what you were trying to do, which is to say that, Ans
is second to last element of [X|Z]
if there's one more element after X
, which would be true, but would be limited to being correct if the list [X|Z]
is a 2-element list. In other words, your main clause almost assumes that the answer is ultimately X
. If it isn't, it attempts to introduce a new candidate, Y
in clauses 2 and 3, but this candidate has no way to "make it back" to the original, main clause.
I'll go back now to CapelliC's solution and describe how one comes to it:
1) secondLast([X,_], X).
2) secondLast([_|T], X) :- secondLast(T, X).
The first clause says, X
is the second to last element in the 2-element list, [X,_]
which is true. And we don't care what the last element is so we just call it, _
. We could have called it Y
([X,Y]
), but then Prolog would warn about a singleton variable since we don't need or use Y
.
The second clause says, X
is the second to last element of list [_|T]
if X
is the second to last element of the tail, T
. This is also true of any list that is 3 or more elements. That's fine since the base case, clause one, takes care of the 2-element list. Clause two will, recursively, reduce down to clause one and finally succeed with the right answer. In this second clause, if X
is taken from T
, then we don't care what the head of the list is since it has become irrelevant, so we use _
as the head in this case (this corresponds to the X
in your original clause #4).
In Sergey's answer:
secondLast(L, X) :-
append(_, [X, _], L).
This says, X
is second to last element in list L
if L
is a two element list with X
as the first element ([X,_]
) appended to the end of some other list (_
). Note again that we're using _
for the variables which will have values but we don't care what those values are in this case. So, for example: 2
is the second to last element of [1,2,3]
if [1,2,3]
is [2,_]
appended to some other list and it is: if you append [2,3]
to [1]
you get [1,2,3]
.
Upvotes: 4
Reputation: 60004
you should apply pattern matching:
secondLast([X,_], X).
secondLast([_|T], X) :- secondLast(T, X).
Upvotes: 7