4

I am working on a longer problem that has me duplicate an element N times in list form, and I believe that using append is the right way to go for this. The tiny predicate should theoretically act like this:

?- repl(x,5,L).
L = [x, x, x, x, x] ;
false.

I cannot seem to find any tips for this online, the replication of a single element, but I believe we need to use append, but no recursive solution. I come from more of a Haskell background, where this problem would be much easier to perform. Can someone help get me started on this? :)

Mine so far:

repl(E, N, R) :-
    N > 0, append([E], [], R), writeln(R), repl(E, N-1, R), fail.

Which gives me:

?- repl(x,5,L).
[x]
[x]
[x]
[x]
[x]
false.

Close but not quite!

Paulo Moura
  • 18,373
  • 3
  • 23
  • 33
user3290526
  • 139
  • 3
  • 11
  • Your solution uses `append` *and* it's recursive, so I assume it wouldn't be valid even if it worked? Or is the "no recursion" rule self-imposed? Are you allowed to use `maplist`? – lurker Apr 19 '14 at 23:45
  • Oh no, I was just assuming that what I was doing with recursion was wrong, there are no restrictions on this predicate for me, it's not homework or anything. I was merely saying that I believe that recursion was not the way to go for this problem. – user3290526 Apr 19 '14 at 23:49

3 Answers3

11

A recursive approach would be straight-forward and would work. I recommend figuring that one out. But here's a fun alternative:

repl(X, N, L) :-
    length(L, N),
    maplist(=(X), L).

If N is instantiated, then length(L, N) will generate a list of length N of just "blanks" (don't care terms). Then maplist(=(X), L) will unify each element of L with the variable X.

This gives a nice, relational approach and yields sensible results in the general case:

| ?- repl(X, N, L).

L = []
N = 0 ? ;

L = [X]
N = 1 ? ;

L = [X,X]
N = 2 ? ;
| ?- repl(X, N, [x,x,x]).

N = 3
X = x

yes
...

To figure out a recursive case, think about what your base case looks like (it would be repl with a count of 0 - what does the list look like then?). In the recursive case, think in terms of:

repl(X, N, [X|T]) :- ...

Meaning: The list [X|T] is the element X repeated N times if.... Figure out if what? If your base case is length 0, then your recursion is probably going to describe the repl of a list of length N in terms of the repl of a list of length N-1. Don't forget in this recursive rule to ensure N > 0 to avoid infinite recursion on backtracking. If you don't need the predicate to be purely relational and assume N is instantiated, then it can be fairly simple.

If you make a simple recursive version, you can "wrap" it in this predicate to make it work with variable N:

repl(X, N, L) :-
    length(L, N),
    simple_recursive_repl(X, N, L).

...

Because length/2 is relational, it is much more useful than just providing the length o a given list. When N and L are not instantiated, it becomes a generator of variable lists, starting at length 0. Type, length(L, N). at the Prolog prompt and see what happens.

lurker
  • 56,987
  • 9
  • 69
  • 103
  • 2
    More esoteric, but more portable non-recursive variant: `repl(X, N, L) :- length(L, N), union(L, [X], [X]).` – Sergii Dymchenko Apr 20 '14 at 00:21
  • @SergeyDymchenko nice option, but why is it more portable? Is `union` more commonly available than `maplist`? – lurker Apr 20 '14 at 00:24
  • I have B-Prolog, ECLiPSe and SWI-Prolog installed. All of them have `union`. B-Prolog doesn't have `maplist`, and ECliPSe has 2 `maplist`s in 2 different libraries. – Sergii Dymchenko Apr 20 '14 at 00:27
  • Ah... I usually use GNU Prolog and SWI Prolog. They both have `maplist`, but GNU doesn't have `union`. :) – lurker Apr 20 '14 at 00:29
  • I should have GNU Prolog installed. – Sergii Dymchenko Apr 20 '14 at 00:30
  • @SergeyDymchenko I often use GNU, but for no particular reason other than it's the one I "cut my teeth on". So I have a warm spot in my heart for it. :) – lurker Apr 20 '14 at 00:34
  • @lurker: I meant this as a positive comment! It's good to have this error. Also, infinite lists are better handled in your version than in many other versions. Brief: your code is perfect. – false Apr 20 '14 at 15:29
  • @false thanks! :o) I apologize for misunderstanding your comment. – lurker Apr 20 '14 at 15:33
3

Determinism

You give the following example of the predicate you envision:

?- repl(x,5,L).
L = [x, x, x, x, x] ;
false.

Notice that the ; is not very productive here. If you want to repeat x 5 times, then this can be done in exactly one way. I would therefore specify this predicate as deterministic not nondeterministic as you are doing.

Repeating list

Your code is actually quite far off a working solution, despite the output looking quite close in spirit to the envisioned result. You try to define the base case and the recursive case at the same time, which will not work.

Here is a simple (but less fun than @lurker gave :-)) implementation of the base and recursive case:

repeating_list(_, 0, []):- !.
repeating_list(H, Reps1, [H|T]):-
  Reps2 is Reps1 - 1,
  repeating_list(H, Reps2, T).

In a sense @lurker's implementation is simpler, and it is surely shorter.

Some extensions

In real-world/production code you would like to catch type errors and treat different instantiations with the same predicate. The second clause checks whether a given list consists of repeating elements (and if so, which one and how many occurrences there are).

%! repeating_list(+Term:term, +Repeats:integer, -List:list(term)) is det.
%! repeating_list(?Term:term, ?Repeats:integer, +List:list(term)) is det.

repeating_list(_, 0, []):- !.
% The term and number of repetitions are known given the list.
repeating_list(H, Reps, L):-
  nonvar(L), !,
  L = [H|T],
  forall(
    member(X, T),
    % ==/2, since `[a,X]` does not contain 2 repetitions of `a`.
    X == H
  ),
  length([H|T], Reps).
% Repetitions is given, then we generate the list.
repeating_list(H, Reps1, [H|T]):-
  must_be(nonneg, Reps1), !,
  Reps2 is Reps1 - 1,
  repeating_list(H, Reps2, T).
% Repetitions is not `nonneg`.
repeating_list(_, Reps, _):-
  domain_error(nonneg, Reps).

Notice that I throw a domain error in case the number of repetitions is negative. This uses library error in SWI-Prolog. If your Prolog does not support this feature, then you may leave the last clause out.

PS: Comparison to Haskell

The combination of your statement that you do not know how to solve this problem in Prolog and your statement that this problem can be solved much easier in Haskell seems a little strange to me. I think you can only compare the difficulty of two implementations once you know how both of them look like.

Wouter Beek
  • 3,307
  • 16
  • 29
  • 4
    I understand your intentions to have reusable and library-grade code, but your 'less fun' code could really scare people who want to try Prolog. Isn't it over-engineering and feature-creeping? The 'customer' (OP) described only one simple use-case, he didn't ask for most of what your code does. – Sergii Dymchenko Apr 20 '14 at 00:38
  • @SergeyDymchenko Thanks for the feedback to my question. I have now added a simple implementation first and then show my personal implementation with some added bells and whistles. – Wouter Beek Apr 20 '14 at 01:06
  • Effectively, *any* language with list comprehension will solve easily the problem. Haskell should feature the shortest code, I think... – CapelliC Apr 20 '14 at 02:27
  • `repeating_list(_, -1,L)` loops, not library-grade. – false Apr 20 '14 at 14:55
  • Your 2nd version: `repeating_list(X,N,[E|Es]).` silently fails. – false Apr 20 '14 at 15:02
1

I do prefer findall/3 to build lists, and between/3 to work with ranges:

repl(E, N, L) :- findall(E, between(1, N, _), L).
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • It works great if `E` and `N` are both instantiated. But if `X` is variable (*e.g.*, `repl(X, 5, L)`), it yields a list of `N` different variables. – lurker Apr 20 '14 at 01:07
  • @lurker: you're right, it's kind of a dumb answer... your one is much better (+1). And Sergey' comment is also interesting ! – CapelliC Apr 20 '14 at 01:24
  • I wouldn't say it's dumb. I think it's a good answer under particular circumstances. :) And, yes, I also think the `union/3` approach is cool. :) – lurker Apr 20 '14 at 01:33