两个变量列表的交集

Intersection of two lists of variables

如何在 ISO Prolog 中为在 线性 时间内运行的两个变量列表的交集定义一个(元逻辑)谓词?变量可以以任何确定的顺序出现。没有像变量 "age" 这样的依赖于实现的属性必须影响结果。

类似于library(ordsets),我们称关系为varset_intersection(As, Bs, As_cap_Bs).

?- varset_intersection([A,B], [C,D], []).
true.

?-varset_intersection([A,B], [B,A], []).
false.

?- varset_intersection([A,B,C], [C,A,D], Inter).
Inter = [A,C].
or
Inter = [C,A].

?- varset_intersection([A,B],[A,B],[A,C]).
B = C
or
A = B, A = C

?- varset_intersection([A,B,C],[A,B],[A,C]).
idem

也就是说,第三个参数是一个输出参数,它与前两个参数的交集相统一。

看到这个 list of the built-ins from the current ISO standard (ISO/IEC 13211-1:1995 including Cor.2)。

(请注意,我在几年前的另一个问题中确实回答过这个问题。但是,它仍然隐藏并且 Google 看不到。)

如果 unify_with_occurs_check(Var, ListOfVars) 在恒定时间内失败或成功,此实现应在线性时间内产生答案:

filter_vars([], _, Acc, Acc).
filter_vars([A|As], Bs, Acc, As_cap_Bs):-
    (
        \+ unify_with_occurs_check(A, Bs)
      ->
        filter_vars(As, Bs, [A|Acc], As_cap_Bs)
      ;
        filter_vars(As, Bs, Acc, As_cap_Bs)
    ).

varset_intersection(As, Bs, As_cap_Bs):-
    filter_vars(As, Bs, [], Inter),
    permutation(Inter, As_cap_Bs).

当给定列表包含重复项时,此实现会出现问题:

?- varset_intersection1([A,A,A,A,B], [B,A], L).
L = [B, A, A, A, A] ;

?- varset_intersection1([B,A], [A,A,A,A,B], L).
L = [A, B] ;

已编辑 : 由于@false 在下面评论中的观察,将 bagof/3 更改为手动编写的过滤器。

一个可能的解决方案是使用 Bloom filter。如果发生碰撞,结果可能是错误的,但存在更好的抗碰撞功能。这是一个使用单个哈希函数的实现。

sum_codes([], _, Sum, Sum).
sum_codes([Head|Tail], K, Acc,Sum):-
    Acc1 is Head * (256 ** K) + Acc,
    K1 is (K + 1) mod 4,
    sum_codes(Tail, K1, Acc1, Sum).

hash_func(Var, HashValue):-
    with_output_to(atom(A), write(Var)),
    atom_codes(A, Codes),
    sum_codes(Codes, 0, 0, Sum),
    HashValue is Sum mod 1024.

add_to_bitarray(Var, BAIn, BAOut):-
    hash_func(Var, HashValue),
    BAOut is BAIn \/ (1 << HashValue).

bitarray_contains(BA, Var):-
    hash_func(Var, HashValue),
    R is BA /\ (1 << HashValue),
    R > 0.

varset_intersection(As, Bs, As_cap_Bs):-
    foldl(add_to_bitarray, As, 0, BA),
    include(bitarray_contains(BA), Bs, As_cap_Bs).

我知道 foldl/4include/3 不是 ISO,但它们的实现很容易。

如果 term_variables/2 的工作时间与其第一个参数的大小成线性关系,那么这可能会工作:

varset_intersection(As, Bs, As_cap_Bs):-
    term_variables([As, Bs], As_and_Bs),
    term_variables(As, SetAs),
    append(SetAs, OnlyBs, As_and_Bs),
    term_variables([OnlyBs, Bs], SetBs),
    append(OnlyBs, As_cap_Bs, SetBs).

无论在给定的两个列表中出现多少次,每个公共变量在结果列表中只出现一次。

?- varset_intersection2([A,_C,A,A,A], [A,_B,A,A,A], L).
L = [A].

此外,它可能会给出奇怪的结果,如本例所示:

?- varset_intersection([A,_X,B,C], [B,C,_Y,A], [C, A, B]).
A = B, B = C.

permutation/2 在这里可能会有帮助)。

如果copy_term/2使用线性时间,我相信以下工作:

varset_intersection(As, Bs, Cs) :-
    copy_term(As-Bs, CopyAs-CopyBs),
    ground_list(CopyAs),
    select_grounded(CopyBs, Bs, Cs).

ground_list([]).
ground_list([a|Xs]) :-
    ground_list(Xs).

select_grounded([], [], []).
select_grounded([X|Xs], [_|Bs], Cs) :-
    var(X),
    !,
    select_grounded(Xs, Bs, Cs).
select_grounded([_|Xs], [B|Bs], [B|Cs]) :-
    select_grounded(Xs, Bs, Cs).

想法是在对 copy_term/2 的一次调用中复制两个列表以保留它们之间的共享变量,然后将第一个副本的变量接地,然后挑选出第二个列表对应的原始变量第二个副本的接地位置。