在 Prolog 中将事实提取为矩阵
Extract Facts as Matrix in Prolog
假设我们有以下内容:
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].
这种方法存在一些问题,但是,viz:
- 我们遍历数据库n次,其中n是参数的个数;和
- 这不能很好地概括为任意 n。
所以问题是:在 Prolog 中实现此目的最惯用的方法是什么?
怎么样:
edgeMatrix(M) :-
findall([A,B,C],edge(A,B,C),Trans),
transpose(Trans,M).
现在您可以简单地导入 transpose/2
matrix from clpfd
module, or implement one yourself like in this answer(是的,我知道这很懒,但是重新发明轮子有什么意义?)。
如果我在 swipl
中 运行 这个,我得到:
?- edgeMatrix(M).
M = [[a, b, c, d], [1, 2, 3, 4], [10, 20, 30, 40]].
这看起来和你想要的一模一样。
你当然可以说计算 transpose/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 中,如果打开分析器 and/or GC 跟踪,您可以看到垃圾回收。)
如果我真的需要它尽可能快和以推广到许多不同的N值,我会编写一个扩展为类似问题解决方案的宏。
编辑:如果取消了 "idiomatic" 要求,我将求助于将 edge
数据库存储为 SWI-Prolog 全局变量中的列表。在那种情况下,我的单遍实现将在没有 findall/3
开销并且不会产生中间垃圾的情况下工作。
假设我们有以下内容:
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].
这种方法存在一些问题,但是,viz:
- 我们遍历数据库n次,其中n是参数的个数;和
- 这不能很好地概括为任意 n。
所以问题是:在 Prolog 中实现此目的最惯用的方法是什么?
怎么样:
edgeMatrix(M) :-
findall([A,B,C],edge(A,B,C),Trans),
transpose(Trans,M).
现在您可以简单地导入 transpose/2
matrix from clpfd
module, or implement one yourself like in this answer(是的,我知道这很懒,但是重新发明轮子有什么意义?)。
如果我在 swipl
中 运行 这个,我得到:
?- edgeMatrix(M).
M = [[a, b, c, d], [1, 2, 3, 4], [10, 20, 30, 40]].
这看起来和你想要的一模一样。
你当然可以说计算 transpose/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 中,如果打开分析器 and/or GC 跟踪,您可以看到垃圾回收。)
如果我真的需要它尽可能快和以推广到许多不同的N值,我会编写一个扩展为类似问题解决方案的宏。
编辑:如果取消了 "idiomatic" 要求,我将求助于将 edge
数据库存储为 SWI-Prolog 全局变量中的列表。在那种情况下,我的单遍实现将在没有 findall/3
开销并且不会产生中间垃圾的情况下工作。