【问题标题】:Easily replicate an element in Prolog :)在 Prolog 中轻松复制元素 :)
【发布时间】:2014-04-19 23:40:24
【问题描述】:

我正在研究一个更长的问题,我需要以列表形式重复一个元素 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.

关闭但不完全!

【问题讨论】:

  • 您的解决方案使用append 并且它是递归的,所以我认为即使它有效也不会有效?还是“无递归”规则是自己强加的?你可以使用maplist吗?
  • 哦不,我只是假设我对递归所做的事情是错误的,对我来说这个谓词没有任何限制,它不是作业或任何东西。我只是说我相信递归不是解决这个问题的方法。

标签: list prolog duplicates replicate


【解决方案1】:

递归方法将是直截了当的并且会起作用。我建议弄清楚这一点。但这里有一个有趣的选择:

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,那么您的递归可能会根据replN 来描述长度为N 的列表长度列表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).,看看会发生什么。

【讨论】:

  • 更深奥但更便携的非递归变体:repl(X, N, L) :- length(L, N), union(L, [X], [X]).
  • @SergeyDymchenko 不错的选择,但为什么它更便携? unionmaplist 更常用吗?
  • 我安装了 B-Prolog、ECLiPSe 和 SWI-Prolog。他们都有union。 B-Prolog 没有 maplist,而 ECliPSe 在 2 个不同的库中有 2 个 maplists。
  • 啊...我通常使用 GNU Prolog 和 SWI Prolog。它们都有maplist,但GNU 没有union。 :)
  • 我应该安装 GNU Prolog。
【解决方案2】:

决定论

你给出了你设想的谓词的以下例子:

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

请注意,; 在这里效率不高。如果你想重复x 5 次,那么这可以通过一种方式完成。因此,我会将这个谓词指定为 deterministic 而不是 nondeterministic,就像您正在做的那样。

重复列表

您的代码实际上与可行的解决方案相差甚远,尽管输出看起来与设想的结果非常接近。您尝试同时定义基本情况和递归情况,这是行不通的。

这是一个简单的(但不如@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 中更容易解决这个问题的陈述对我来说似乎有点奇怪。我认为只有了解两种实现的外观后,才能比较两种实现的难度。

【讨论】:

  • 我理解你想要拥有可重用和库级代码的意图,但你的“不那么有趣”的代码真的会吓到想要尝试 Prolog 的人。这不是过度设计和功能爬行吗? “客户” (OP) 只描述了一个简单的用例,他并没有询问您的代码的大部分功能。
  • @SergeyDymchenko 感谢您对我的问题的反馈。我现在首先添加了一个简单的实现,然后展示了我个人的实现,并添加了一些花里胡哨。
  • 有效地,任何 具有列表理解的语言都可以轻松解决问题。我认为 Haskell 应该具有最短的代码......
  • repeating_list(_, -1,L) 循环,不是库级。
  • 您的第二个版本:repeating_list(X,N,[E|Es]). 静默失败。
【解决方案3】:

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

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

【讨论】:

  • 如果EN 都被实例化,效果会很好。但如果 X 是可变的(例如repl(X, 5, L)),它会产生一个N 不同变量的列表。
  • @lurker:你是对的,这是一个愚蠢的答案......你的答案要好得多(+1)。并且 Sergey 的评论也很有趣!
  • 我不会说这很愚蠢。我认为在特定情况下这是一个很好的答案。 :) 而且,是的,我也认为union/3 方法很酷。 :)
猜你喜欢
  • 2012-03-02
  • 1970-01-01
  • 1970-01-01
  • 2011-07-13
  • 2010-11-19
  • 2011-06-19
  • 1970-01-01
  • 1970-01-01
  • 2012-08-20
相关资源
最近更新 更多