Prolog:显示每个子列表的每个元素

3

我有一个由许多小子列表构成的大列表,例如:

 [ [005,Chester,100],[001,Bob,99],[002,Andy,77] ]

我试图编写一个能够遍历整个列表并展示出如下结果的“函数”:
 No.1: ID="005", Name="Chester", Grade=100
 No.2: ID="001", Name="Bob", Grade=99
 No.3: ID="002", Name="Andy", Grade=77

在Prolog中思考已经很难了,但我正在苦苦挣扎于递归思维。非常感谢您的任何帮助!

4个回答

5

我认为,仅使用递归处理数据结构是Prolog的主要问题。

我们知道这些数据结构的适当形式,但缺乏表达能力,这促使了扩展(如B-Prolog中的循环)和语言 - 如Picat

无论如何,纯Prolog足够强大,可以解决您的问题:

show_records(L) :-
    forall(nth1(N, L, E), format('No.~d:ID="~s",Name="~s",Grade="~d"~n', [N|E])).

产生
?- show_records([ ['005','Chester',100],['001','Bob',99],['002','Andy',77] ]).
No.1:ID="005",Name="Chester",Grade="100"
No.2:ID="001",Name="Bob",Grade="99"
No.3:ID="002",Name="Andy",Grade="77"
true.

1
那是一个非常棒的小解决方案。 - Shon
1
这是一个非常好的小解决方案 :) - gniourf_gniourf
1
谢谢。我不得不将“s”更改为“a”,以便它能够在Unix中与我的字符串一起正常工作。在SWI-Prolog上,它完美地工作了。猜想这是因为我的字符串存储在单引号中的缘故? - GeorgeCostanza
@GeorgeCostanza:抱歉,我听不懂。我正在使用Ubuntu和最新的SWI-Prolog...我也尝试了~a和双引号,比如“005”,似乎所有组合都可以工作... - CapelliC
很酷,它运行良好,所以没什么大不了的。只是给了我一个“非法参数格式序列s”的错误,所以我把它改成了a。再次感谢。 - GeorgeCostanza

1
我的回答是以SWI-Prolog为主,我提到的任何内置谓词的详细信息都可以通过搜索文档获得。但是,我认为我在这里说的一切都适用于其他重要的Prolog实现。如果我错了,请纠正我。
在Prolog中,我们使用谓词而不是函数(或者只使用返回真值的函数)。我将使用以下谓词来引用您的示例列表中的Prolog查询:
ex([ [005,"Chester",100],[001,"Bob",99],[002,"Andy",77] ]).

为了将格式化文本写入标准输出流,我们将使用谓词format/2。它的签名是format(+Format, :Arguments),其中Format是一个包含特殊序列以便插值的原子。转义序列以波浪线~开头,完整菜单可以在SWI-Prolo 关于format/2的文档中找到。Arguments是用于替换Format中序列的Prolog术语列表。在这个例子中,我们只需要~w,它会写入一个参数和~n,它会写入一个新行。作为一个通用的例子:例如,

?- format('Write ~w, then ~w, followed by ~w~n~n', ['this', 'that', 'the other']).
Write this, then that, followed by the other

true.

你将一个人表示为[ID,姓名,成绩]的列表形式,因此我们只需要编写一个谓词,让我们能够添加“No.N”中的N,然后我们可以使用这个人员列表作为format/2的其余参数。
display_person(N, Person) :-
    format('No.~w: ID="~w", Name="~w", Grade=~w~n', [N|Person]).

format/2的第二个参数中,N作为参数列表的头部前缀。我们可以这样测试谓词:
?- ex([P|Ps]), display_person(1, P).
No.1: ID="5", Name="Chester", Grade=100
P = [5, "Chester", 100],
Ps = [[1, "Bob", 99], [2, "Andy", 77]].

这样就完成了每个人员列表的显示。现在我们只需要遍历您的人员列表并显示每个成员。我们需要一个双位谓词来跟踪“No.N”部分显示的增加值,但我们只需要一个单位谓词将列表传递给显示谓词。这个要求导致我们使用常见的模式,即使用由辅助谓词支持的前端谓词:

display_people(People) :- display_people(1, People). %% 使用起始值1初始化对display_people/2的调用

display_people(_, []). display_people(N, [Person|People]) :- display_person(N, Person), M is N+1, display_people(M, People).

使用ex/1中的列表运行测试:

?- ex(Ps), display_people(Ps).
No.1: ID="5", Name="Chester", Grade=100
No.2: ID="1", Name="Bob", Grade=99
No.3: ID="2", Name="Andy", Grade=77
Ps = [[5, "Chester", 100], [1, "Bob", 99], [2, "Andy", 77]] ;
false.

然而,模式p([X|Xs]) :- q(X), p(Xs)正是maplist/n谓词所用的,因此我们可以节省一些打字,并通过编写以下内容来使程序的声明性含义更加清晰:
display_people(People) :-
    length(People, NumPeople),
    numlist(1, NumPeople, Nums),
    maplist(display_person, Nums, People).

numlist(Low, High, Nums) 返回一个列表 Nums,其中包含从 LowHigh 的值。在上述定义中,maplist(display_person, Nums, People) 会基本上为每一对 NNums 中以及 PPeople 中调用 display_person(N, P)


1
这个回答仅仅是回答一个Bathologist有关使用foldl的问题。我完整地复制了他的display_person/2谓词:
display_person(N, Person) :-
    format('No.~w: ID="~w", Name="~w", Grade=~w~n', [N|Person]).

然后是一个小适配器,它将在先前的谓词中递增N(并按正确顺序放置参数):

display_person(Person,N,NextN) :-
    display_person(N,Person),
    NextN is N+1.

使用 foldl:
display_people(People) :-
    foldl(display_person,People,1,_).

如果
`People=[P1,P2,...,Pn]`

那么 foldl(display_person,People,1,Rn) 等同于:

display_person(People,1,R1),
display_person(People,R1,R2),
...
display_person(People,R(n-1),Rn).

在这种方式中使用foldl可以避免通过列表来获取其长度。但是,另一方面,您需要一个适配器谓词。
无论如何,在这种情况下更好或更差并不真正重要!foldl只是另一个很好知道的技巧,有时可以使您的代码更清晰。

1
非常有帮助。我认为CapelliC的答案在简洁和效率方面是最好的,但我非常感激使用foldl的教程,因为它(终于!)让我清楚地了解了它的操作。我一直完全忽略了foldl的最后两个参数应该如何工作,但现在我明白了!正如你所说,这是另一个(我认为非常重要的?)技巧。非常感谢! - Shon

0
你不需要递归。首先,编写一个谓词,使用Prolog的format谓词显示每个条目,这是printf的等效物,并直接将列表作为参数传递。其次,使用maplist将第一个谓词映射到列表上。

1
maplist/2 可能不太适合,因为 OP 想要在每行前面加上一个 No.X: 字段。所以使用 foldl(将行号作为累加器)可能更合适。 - gniourf_gniourf
@gniourf_gniourf,我很尴尬地说,我从未真正掌握过foldl的实际用途。您能否友好地给我一个例子,说明在这种情况下如何使用foldl?谢谢! - Shon
1
@aBathologist,我为您专门制作了一个答案! - gniourf_gniourf

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