【问题标题】:Extract Facts as Matrix in Prolog在 Prolog 中将事实提取为矩阵
【发布时间】:2017-01-05 21:01:24
【问题描述】:

假设我们有以下内容:

edge(a, 1, 10).
edge(b, 2, 20).
edge(c, 3, 30).
edge(d, 4, 40).

我想提取这些事实的矩阵表示 (M),这样

M = [[a,b,c,d],[1,2,3,4],[10,20,30,40]]

这是一个简单的解决方案:

edgeMatrix(M) :-
  findall(A, edge(A, _, _), As),
  findall(B, edge(_, B, _), Bs),
  findall(C, edge(_, _, C), Cs),
  M = [As, Bs, Cs].

这种方法存在一些问题,但是,

  1. 我们遍历数据库n次,其中n是参数的个数;和
  2. 这不能很好地推广到任意 n

所以问题是:在 Prolog 中实现这一点最惯用的方法是什么?

【问题讨论】:

    标签: matrix prolog prolog-findall


    【解决方案1】:

    怎么样:

    edgeMatrix(M) :-
        findall([A,B,C],edge(A,B,C),Trans),
        transpose(Trans,M).
    

    现在您可以简单地从 clpfd 模块中导入 transpose/2 矩阵,或者像在 this answer 中那样自己实现一个矩阵(是的,我知道这很懒,但是重新发明轮子有什么意义呢?)。

    如果我在 swipl 中运行它,我会得到:

    ?- edgeMatrix(M).
    M = [[a, b, c, d], [1, 2, 3, 4], [10, 20, 30, 40]].
    

    看起来和你想要的完全一样。

    您当然可以说计算transpose/2 仍然存在一些计算开销,但收集阶段只完成一次(如果这些不仅仅是事实,而是子句的答案),这可能很昂贵同样,而且我认为模块无论如何都会非常有效地实现子句。

    【讨论】:

    • 这非常巧妙……我喜欢它 ;-) 虽然它可能与我提到的由于transpose/2 相同的性能问题。
    • @HugoSerenoFerreira 您可以查看transpose/2 的定义,但我认为它与列表列表作为矩阵表示一样有效。总而言之,在纯 Prolog 中没有直接、通用的方法来处理矩阵(据我所知)。您也许可以避免使用列表列表开始,但您的问题没有提供足够的细节。
    【解决方案2】:

    我认为您不会找到既完全通用又效率最高的解决方案。这是 N = 3 的简单解决方案:

    edges(Edges) :-
        Goal = edge(_A, _B, _C),
        findall(Goal, Goal, Edges).
    
    edges_abcs_([], [], [], []).
    edges_abcs_([edge(A,B,C)|Edges], [A|As], [B|Bs], [C|Cs]) :-
        edges_abcs_(Edges, As, Bs, Cs).
    
    edges_abcs([As, Bs, Cs]) :-
        edges(Edges),
        edges_abcs_(Edges, As, Bs, Cs).
    

    添加 100,000 个额外的 edge/3 事实后,执行如下:

    ?- time(edges_abcs(M)).
    % 200,021 inferences, 0.063 CPU in 0.065 seconds (97% CPU, 3176913 Lips)
    M = [[a, b, c, d, 1, 2, 3, 4|...], [1, 2, 3, 4, 1, 2, 3|...], [10, 20, 30, 40, 1, 2|...]].
    

    为了比较,这里是对问题实施的测量:

    ?- time(edgeMatrix_orig(M)).
    % 300,043 inferences, 0.061 CPU in 0.061 seconds (100% CPU, 4896149 Lips)
    M = [[a, b, c, d, 1, 2, 3, 4|...], [1, 2, 3, 4, 1, 2, 3|...], [10, 20, 30, 40, 1, 2|...]].
    

    下面是 Willem 基于transpose/2 的更通用的解决方案:

    ?- time(edgeMatrix_transpose(M)).
    % 700,051 inferences, 0.098 CPU in 0.098 seconds (100% CPU, 7142196 Lips)
    M = [[a, b, c, d, 1, 2, 3, 4|...], [1, 2, 3, 4, 1, 2, 3|...], [10, 20, 30, 40, 1, 2|...]].
    

    因此,就推理数量而言,我的解决方案似乎是最好的:findall/3 的推理次数为 100,000 次,遍历列表的推理次数为 100,000 次。该问题的解决方案对每个 findall/3 有 100,000 个推论,但仅此而已。但是,它稍微快一些,因为它更节省内存:分配的所有内容最终都在最终解决方案中,而我的程序分配了 100,000 个edge/3 术语的列表,然后必须对其进行垃圾收集。 (在 SWI-Prolog 中,如果您打开分析器和/或 GC 跟踪,您可以看到垃圾回收。)

    如果我真的需要它尽可能快并且可以推广到许多不同的 N 值,我会编写一个扩展为类似的宏问题中的解决方案。

    编辑:如果取消“惯用”要求,我将求助于将edge 数据库作为列表存储在 SWI-Prolog 全局变量中。在这种情况下,我的单通道实现可以在没有findall/3 开销的情况下工作,并且不会产生中间垃圾。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多