Prolog创建列表的列表

4

我在prolog中使用嵌套列表遇到了一些(或是很多)问题。

我有一个数字列表,比如输入为 [5,6,1,3]。 输出应该是 [[5,25],[6,36],[1,1],[3,9]]。

我已经有一个谓词可以返回2项列表(请记住我需要改变 get_squared_pair 函数来获取其他相关值):

get_squared_pair(Number, Result) :-
get_squared_value(Number, SquareValue),
Result = [Number, SquareValue].

get_squared_value(Number, Result) :-
Result is Number * Number.

到这里还算合乎逻辑。现在我需要一个谓词,它可以递归地遍历列表,将平方的对加入到一个新列表中,然后返回这个列表的列表。目前我拥有的是:

return_list([], 0).
return_list([Head | Tail], Result) :-
get_squared_pair(Head, Add),
append(Add,Result),
return_list(Tail, Result).

由于我似乎无法弄清楚递归如何处理列表,更别说列表的列表了,所以这种方法有许多问题。此外,当前它运行的顺序也是错误的,并没有起到帮助作用。

我知道这可能有点模糊,但我已经尝试通过谷歌摆脱这个问题,但似乎无法将我找到的内容很好地转化为自己的问题。

如果有任何帮助,将不胜感激!


2
Prolog 没有定义操作的 函数。它有定义关系的 谓词。它们不是同一件事情。 - lurker
1
非常好的观点,我对Prolog仍然很陌生,请原谅我的无知。我从一开始就在这篇文章上放了一个谓词标签,但是我忘记在实际文本中也反映这一点...太沉迷于旧习惯了! - freefall
2个回答

4
让我们先看一下你的`get_squared_pair/2`。虽然它可以工作,但它还可以更整洁一些,这也有助于理解Prolog的工作原理。 Prolog的主要机制是“统一”,它与其他语言中发生的“赋值”不同。在“统一”中,Prolog检查两个术语,并尝试通过实例化一个或两个术语中的变量来使它们匹配。 在Prolog中有一些谓词,例如`is/2`,用于评估一个参数中的表达式,然后将第一个参数与该结果统一。
因此,你编写的第一个谓词如下:
get_squared_pair(Number, Result) :-
    get_squared_value(Number, SquareValue),
    Result = [Number, SquareValue].

get_squared_value(Number, Result) :-
    Result is Number * Number.

这段文本可以通过两种方式简化。首先,您可以合并 get_squared_value/2,因为它只有一行代码,实际上不需要创建自己的谓词。其次,我们将重命名谓词,使其不带命令语气。

square_pair(Number, Square) :-
    S is Number * Number,     % Square the number
    Square = [Number, S].     % Unify Square with the pair

Prolog可以在子句的头部中统一术语,因此您可以避免冗余的统一。因此,这就是您所需要的:

square_pair(Number, [Number, Square]) :-
    Square is Number * Number.

接下来是主要谓词return_list/2,我们将其重命名为square_pairs。在对列表递归时,最常见的模式是继续缩小列表直到为空,然后基准情况处理空列表。你的实现做到了这一点,但基准情况有点混乱,因为第二个参数是整数而不是列表:

square_pairs([], 0).

这应该是:

square_pairs([], []).

你的主要谓语从句没有正确使用 append/2。在SWI Prolog中有两种形式的 append: append/2append/3。你可以在SWI Prolog在线文档中查找它们的作用。我可以告诉你,在Prolog中,一旦一个变量被实例化,你不能在谓语从句中改变它的值,除非通过回溯。例如,看看下面可能出现在谓语从句中的序列:
X = a,    % Unify X with the atom 'a'
X = b,    % Unify X with the atom 'b'

在这种情况下,第二个表达式将总是失败,因为X已经被统一并且不能再次统一。但是,如果我有以下内容:
foo(X),    % Call foo, which unifies X with a value that makes 'foo' succeed
bar(X, Y), % Call bar, which might fail based upon the value of 'X'

在上面的例子中,如果bar(X, Y)失败,那么Prolog会回溯到foo(X)调用并寻找使foo(X)成功的另一个X值。如果找到了,则会使用新的X值再次调用bar(X, Y),以此类推。
因此,append(Add, Result)不会将Add附加到Result并产生Result的新值。事实上,带有两个参数的append表示第二个列表参数是第一个列表的所有元素的连接,假设第一个参数是列表的列表,因此append/2的定义不匹配。
在考虑递归时,请意识到参数列表之间是一一对应的。结果列表的头是第一个参数列表的“平方对”的头。然后,递归地,第二个参数的尾部是第一个参数尾部的“平方对”列表。您只需要在Prolog中表达出来。我们也可以使用我上面描述的在条款头部内部进行统一的技术。
square_pairs([Head | Tail], [SqPair | SqTail]) :-
    square_pair(Head, SqPair),
    square_pairs(Tail, SqTail).
square_pairs([], []).

现在我们还可以进行另一种简化,即完全消除辅助谓词 square_pair/2
square_pairs([Head | Tail], [[Head, SqHead] | SqTail]) :-
    SqHead is Head * Head,
    square_pairs(Tail, SqTail).
square_pairs([], []).

在Prolog中有一个方便的谓词叫做maplist,可以用于定义两个列表之间并行运行的关系,这正是我们所需要的场景。我们可以重新引入square_pair/2谓词并使用maplist

square_pairs(Numbers, SquarePairs) :-
    maplist(square_pair, Numbers, SquarePairs).

非常感谢您详细而描述性的回答!我从中学到了很多东西。它确实在我的脑海中澄清了一些Prolog的概念。不过,我还需要再读几遍才能完全理解它 ;) 再次感谢! - freefall

2

所以你想将列表转换为另一个列表,使每个元素(一个数字)都变成一个两个元素的列表,第一个元素是数字本身,第二个元素是数字的平方。

你只需要告诉Prolog如何做。首先,实现第二个目标:

turn_into_two(Num, [A,B]):-

A是什么?

    A is Num,

什么是B?我们也告诉Prolog:

                B is ... * ... .

现在,让我们来看一下列表。Prolog中的列表[A|B]由其头元素A和尾部B组成,除非它是一个空列表[]。列表的元素是什么并不重要;一个列表就是一个列表。

我们需要考虑所有情况,否则我们所说的就不是列表而是其他东西:

turn_list([], Res):-

那么,如果列表为空,我们的结果应该也是空的,对吗?

    Res = ... .

在另一种情况下,
turn_list([A|B], Res):-

我们的结果不会为空,所以它将有其头部和尾部,对吗?
    Res = [C|D],

接下来我们来谈谈关于头部(heads)的相关知识:输入列表的头部会变成我们上面描述过的那个由两个元素组成的列表,对吗?
    turn_into_two(A,C),

然后我们来谈谈尾部。但是我们对尾部了解多少呢?我们知道一个尾部是另一个尾部的转换结果,就像整个列表一样:

    turn_list( ... , ...) .

就是这样。顺便说一下,我们所描述的遵循了映射的范例。我们可以用任何其他谓词替换turn_into_two/2,并且将为输入列表中的每个元素以及相应结果列表中的元素调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接