在追加到另一个列表时迭代列表

问题描述

我是 Prolog 的新手。下面的谓词应该处理一个字符串列表,每个字符串包含一个用空格分隔的名字和姓氏。它的意思是通过每个元素,用空格将其分隔为一个用于 FirstName 的元素和另一个用于 LastName 的元素,并将这些值添加到 FirstNames 列表和 LastNames 列表中。最后,它应该返回与原始列表顺序相对应的所有名字和姓氏的列表。我正在努力让它发挥作用。

populate([]).
populate([H|T],FirstNames,LastNames) :-
    split_string(H," ",Elems),Elems = [FirstName,LastName],append(FirstNames,[FirstName],FirstNames2),append(LastNames,[LastName],LastNames2),populate(T,LastNames).

例如,如果给定

?- populate(["Peter Parker","Bruce Wayne"],LastNames),

它应该返回

FirstNames = ["Peter","Bruce"]

LastNames = ["Parker","Wayne"].

解决方法

在 SWI-Prolog 中对问题进行编码,具有以下特点:

  • 使用定语从句语法将姓名分成名字姓氏
  • 利用库 dcg/basics,更准确地说是规则 nonblanks//1blank//0 分别处理非空白和单字符空白
  • 处理文本的 SWI-Prolog DCG 假定一个 unicode-codes 列表作为输入(而不是字符列表),因此我们通过使用 atom_codes/2 来拆分和重组文本来兑现这一点-
  • 在整个过程中使用原子 ('John Wayne') 而不是 SWI-Prolog 字符串 ("John Wayne") 以实现一致性。
  • 使用 plunit 单元测试进行说明。

所以:

首先编写一个小谓词,使用 DCG 将“名字原子”分离成“名字原子”和“姓氏原子”:

:- use_module(library(dcg/basics)).

name(First,Last) --> 
   nonblanks(FirstCodes),% collect a list of codes for the first name 
   blank,% then comes a blank
   nonblanks(LastCodes),% collect a list of codes for the last name
   { atom_codes(First,FirstCodes),% fuse the codes into a "first name atom"
     atom_codes(Last,LastCodes) }.   % fuse the codes into a "last name atom"
     
separate(Name,First,Last) :-
   atom_codes(Name,NameCodes),phrase(name(First,Last),NameCodes).

separate/3 我们可以测试一下:

:- begin_tests(separate).

test(1) :-
   separate('Bruce Wayne',assertion(First == 'Bruce'),assertion(Last == 'Wayne').

:- end_tests(separate).

运行测试用例看起来不错:

?- run_tests(separate).
% PL-Unit: separate . done
% test passed
true.

现在我们只需要在名称列表上应用 separate/3。我们可以使用 maplist/3,但让我们使用递归定义:

% separate_all(+ListOfNames,-ListOfFirsts,.ListOfLasts)

separate_all([],[],[]).
separate_all([Name|Ns],[First|Fs],[Last|Ls]) :-
   separate(Name,separate_all(Ns,Fs,Ls).

还有一个测试用例:

:- begin_tests(separate_all).

test(all_1) :-
   separate_all(['Peter Parker','Bruce Wayne'],FirstNames,LastNames),assertion(FirstNames == ['Peter','Bruce']),assertion(LastNames == ['Parker','Wayne']).

:- end_tests(separate_all).

所以:

?- run_tests(separate_all).
% PL-Unit: separate_all . done
% test passed
true.