在Prolog中轻松复制元素:)

时间:2014-04-19 23:40:25

标签: list prolog duplicates replicate

我正在处理一个更长的问题,我在列表表单中复制了N次元素,我相信使用append是正确的方法。这个小谓词理论上应该是这样的:

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

我似乎无法在网上找到任何提示,复制单个元素,但我相信我们需要使用append,但没有递归解决方案。我来自更多的Haskell背景,这个问题会更容易执行。有人可以帮我开始吗? :)

到目前为止:

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

这给了我:

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

关闭但不完全!

3 个答案:

答案 0 :(得分:11)

递归方法是直截了当的,可行。我建议把它搞清楚。但这是一个有趣的选择:

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

如果N被实例化,那么length(L, N)将生成一个长度为N的列表,其中只有"空白" (不要关心条款)。然后,maplist(=(X), L)会将L的每个元素与变量X统一起来。

这给出了一个很好的关系方法,并在一般情况下产生了明智的结果:

| ?- 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
...

要想出一个递归的情况,请考虑你的基本情况是什么样的(repl计数为0 - 那么列表是什么样的呢?)。在递归的情况下,请考虑:

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

含义:如果...... ,则列表[X|T]是重复X次的元素N。弄清楚如果是什么?如果你的基本情况是0长度,那么你的递归可能会描述长度为repl的{​​{1}}列表N长度为repl的列表的N-1。不要忘记这个递归规则,以确保N > 0避免回溯无限递归。如果你不需要谓词是纯粹的关系,并假设N被实例化,那么它可以相当简单。

如果你制作一个简单的递归版本,你可以"包装"它在这个谓词中使它适用于变量N

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

...

因为length/2是关系型的,所以它比仅提供给定列表的长度更有用。当NL未实例化时,它将成为变量列表的生成器,从0开始。在Prolog提示符下键入length(L, N).,看看会发生什么。

答案 1 :(得分:3)

确定性

您给出了您想象的谓词的以下示例:

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

请注意;在这里效率不高。如果你想重复x 5次,那么这可以用一种方式完成。因此,我会将此谓词指定为确定性而不是非确定性

重复列表

尽管输出结果与预想的结果非常接近,但您的代码实际上距离工作解决方案还很远。您尝试同时定义基本案例和递归案例,这将无效。

这是一个简单的(但不如@lurker给出的那样有趣:-))基本和递归情况的实现:

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

从某种意义上说,@ lurker的实现更简单,而且肯定更短。

一些扩展

在实际/生产代码中,您希望捕获类型错误并使用相同的谓词处理不同的实例。第二个子句检查给定列表是否包含重复元素(如果是,则包含哪一个以及出现的次数)。

%! 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).

请注意,如果重复次数为负,则会抛出域错误。这使用SWI-Prolog中的库error。如果您的Prolog不支持此功能,那么您可以退出最后一个条款。

PS:与Haskell的比较

你不知道如何在Prolog中解决这个问题的声明和你在Haskell中解决这个问题的说法的组合对我来说似乎有点奇怪。我想你只能比较两种实现的难度,只能比较两种实现的难度。

答案 2 :(得分:1)

我更喜欢findall / 3来构建列表,between / 3来处理范围:

repl(E, N, L) :- findall(E, between(1, N, _), L).