union([H|T],[],[H|T]).
union([],[H|T],[H|T]).
union([H|T], SET2, RESULT) :- member(H,SET2), union(T,SET2,RESULT).
union([H|T], SET2, [H|RESULT]) :- not(member(H,SET2)), union(T,SET2,RESULT).
我能够理解它正在遍历第一个列表并根据元素是否是第二个列表的成员添加。我得到了逻辑。但是,工作流程对我来说是神秘的,它会在第一个列表耗尽后将“第二个列表”的元素添加到结果中。
请有人可以采用union([1,2], [2,3], Result)
之类的简单示例,并解释工作流程。
答案 0 :(得分:7)
我假设您正在调用union / 3,并实例化第一个和第二个参数。第三个参数可以在调用时未实例化,并在返回时与两个列表的并集统一,或者如果它已经实例化,则可以用它来检查它是否与前两个列表的(有序)并集匹配。
第一个子句指出,如果第二个参数是空列表和,则第一个列表至少有一个元素,那么union就是第一个列表。 同样,第二个子句声明如果第一个参数是空列表和,则第二个列表至少有一个元素,那么union就是第二个列表。
第三个子句递归第一个列表并检查第二个列表以查看该项是否已存在。在这种情况下,它只是用第一个列表的尾部调用自己。
第四个子句测试第一个列表的头部,检查它是否包含在第二个列表中,并用尾部递归调用(就像第三个子句一样)。但是在返回递归时,它会将项目添加到第三个列表的头部,从而将项目添加到联合。
请注意,在您的实现中,两个空集的并集始终会失败。您可以通过修改第一个或第二个子句以允许空列表来修复此问题,或者为此案例添加另一个子句。 E.g。
union([],[],[]).
现在让我们看看当我们致电union([1,2],[2,3], Result)
时会发生什么:
前两个子句将无法匹配,因为它们都不是空列表。
我们输入第三个子句并检查元素1是否不是第二个列表的成员,因此失败。
我们现在尝试第四个子句并测试元素1我不在第二个列表中,因此我们调用union([2], [2,3], Result)
,我们标记此执行点( * 1 )。
同样,两个第一个子句将无法匹配,因此我们输入第三个子句。在这里,我们测试确实元素2包含在第二个列表中,因此我们调用union([], [2,3], Result)
,我们标记此执行点( * 2 )
现在第一个子句失败,因为第一个参数是空列表。 我们现在输入第二个子句,用第二个列表([2,3])统一第三个参数。
此时我们返回到(* 2)现在结果用[2,3]实例化了。这个条款在那里结束,所以我们用[1,2,3]绑定第三个参数,然后返回( * 1 )。
我们现在在( * 1 ),其中Result,因此第三个参数用[1,2,3]实例化。
这为我们提供了第一个结果 [1,2,3] 。
然而,当我们在( * 2 )成功时,我们选择了一个选择点,所以如果我们要求Prolog寻找另一个答案,那么仍然需要尝试第四个联合条款([2], [2,3],结果)。
所以我们输入第四个子句来测试2是不是[2,3]的成员而失败了,所以Prolog会告诉我们没有其他答案。
答案 1 :(得分:1)
您没有检查SET2
,因此假设其中没有重复项,那么基本递归可以是单个谓词
union([], U, U).
@gusbro已经解释过,当H
不属于SET2时,在递归调用成功后,它将被置于结果的(本地)前面。
解释应该清除您的疑虑,但请注意not
/ 1基本上重复已在先前(失败)调用中执行的相同测试。那么更好的代码可能是
union([H|T], SET2, RESULT) :-
member(H,SET2), !, % note the commit
union(T,SET2,RESULT).
union([H|T], SET2, [H|RESULT]) :-
% useless now
% not(member(H,SET2)),
union(T,SET2,RESULT).
这相当于您的代码,假设member
/ 2是副作用免费(并且是)。
not
/ 1可以使用!
/ 0实现。
答案 2 :(得分:1)
在my answer回答相关问题“Intersection and union of 2 lists”时,我提出了一个逻辑上纯粹的交集和联合实现。
与您的问题的其他答案不同,纯变体是单调的,因此在与非基础术语一起使用时保持逻辑上的声音。