【发布时间】:2019-06-21 18:10:38
【问题描述】:
这是一个游乐场示例,灵感来自我曾经做过的一个真实任务(要复杂得多)。基本流程是从顺序文件中读取记录。一些记录包含需要检查先前记录以计算值的命令。
这个解决方案的缺点是它需要一个额外的列表,因此需要额外的重复存储。 该额外列表在以下代码中称为 REMEMBER。 这个例子有一个简单的记录结构,只包含一个数据值,所以复制 REMEMBER 列表中的所有内容并不是一个真正的问题。 但请假设实际任务涉及更复杂的记录结构,因此复制 REMEMBER 列表中的所有内容是非常不可取的。
我倾向于使用双向链表,但是根据此链接的讨论 Doubly Linked List in Prolog 似乎这不是 Prolog 做事的方式。 因此,我很想看看 Prolog 的做事方式应该是什么。
/*
A file contains sequential records.
There are two types of record.
A data record provides a data value.
An average record provides a count and is a request for an average of the last count data values.
The parse dcg below parses a list from the data file.
The report dcg uses that list to generate a report.
After parse the list looks like this:
[value=5.9,value=4.7,value=7.5,average=3,value=9.0,value=1.1,value=8.3,average=5,value=7.1,value=1.3,value=6.7,value=9.9,value=0.5,value=0.3,value=1.5,value=0.2,average=7,value=2.2,value=7.8,value=2.5,value=4.5,value=2.4,value=9.7,average=4,value=5.2,value=8.5,value=2.2,value=8.0,value=0.7].
An example report looks like this:
[[count=3,total=18.1,average=6.033333333333333],[count=5,total=30.599999999999998,average=6.12],[count=7,total=20.400000000000002,average=2.9142857142857146],[count=4,total=19.1,average=4.775]].
*/
:- use_module(library(dcg/basics)).
:- use_module(library(readutil)).
:- use_module(library(clpfd)).
:- use_module(library(clpr)).
dospy
:-
spy(report),
spy(average),
leash(-all).
:- initialization main.
report(LIST)
-->
% the report starts with nothing to REMEMBER.
report(LIST,[]).
report([value=VALUE|LIST],REMEMBER)
-->
% value in the LIST goes into REMEMBER.
report(LIST,[value=VALUE|REMEMBER]).
report([average=COUNT|LIST],REMEMBER)
-->
% request for average in the LIST.
average(REMEMBER,COUNT),
report(LIST,REMEMBER).
report([],_REMEMBER)
% the LIST is empty so the report is done.
--> [].
average(REMEMBER,COUNT)
-->
% the average starts at 0 then accumulates for COUNT values from REMEMBER.
average(REMEMBER,COUNT,0,0.0).
average([value=VALUE|REMEMBER],COUNT,AT,TOTAL)
-->
% found needed value in the REMEMBER.
clpfd( AT #\= COUNT ),
clpfd( AT_NEXT #= AT + 1 ),
clpr( TOTAL_NEXT = TOTAL + VALUE ),
average(REMEMBER,COUNT,AT_NEXT,TOTAL_NEXT).
average(_REMEMBER,COUNT,COUNT,TOTAL)
-->
% all of the needed value have been seen so calculate and add to report.
clpr( AVERAGE = TOTAL / COUNT ),
[[count=COUNT,total=TOTAL,average=AVERAGE]].
% now the part that does the parse of the data file.
parse(LIST) --> parse(data,LIST).
parse(LIST) --> parse(average,LIST).
parse(LIST) --> parse(end,LIST).
parse(data,[value=FLOAT|LIST])
-->
"data", whites, float(FLOAT), blanks, !,
parse(LIST).
parse(average,[average=COUNT|LIST])
-->
"average", whites, integer(COUNT), blanks, !,
parse(LIST).
parse(end,[])
-->
[].
clpr( CLPR )
-->
{ clpr:{ CLPR } }.
clpfd( CLPFD )
-->
{ CLPFD }.
main :-
system:( read_file_to_codes("doubly_motivation_data.txt",CODES,[]) ),
prolog:( phrase(parse(LIST),CODES) ),
system:( writeq(LIST),writeln(.) ),
prolog:( phrase(report(LIST),REPORT) ),
system:( writeq(REPORT),writeln(.) ).
/* doubly_motivation_data.txt
data 5.9
data 4.7
data 7.5
average 3
data 9.0
data 1.1
data 8.3
average 5
data 7.1
data 1.3
data 6.7
data 9.9
data 0.5
data 0.3
data 1.5
data 0.2
average 7
data 2.2
data 7.8
data 2.5
data 4.5
data 2.4
data 9.7
average 4
data 5.2
data 8.5
data 2.2
data 8.0
data 0.7
*/
【问题讨论】:
-
感谢@GuyCoder 的编辑。我应该提到我正在使用 SWI-Prolog 我认为库(dcg/basics)是特定于 SWI-Prolog 的。
-
有什么原因你不能从头到尾向后处理文件?
-
不允许使用key-value pair 吗?目前我看到您的问题的方式是您需要动态访问记录,而 Prolog 喜欢处理列表,但不需要数据在列表中,因此我得到了您对双向链表的渴望。此外,我从未在 Prolog 中使用过键值对,但如果这个答案需要它,那么我认为没有理由避免它们。
-
感兴趣的:dicts - 从未使用过这个,但有一个 dicts_slice/3 可能有用。
-
如果您的文件(比如说)有 10,000 行长,有什么东西可以阻止最后一行成为“平均 10000”并迫使您重新访问到目前为止所看到的所有数据吗?如果没有,我认为地球上没有一种力量可以使您不必在整个处理过程中保留文件的全部内容。