Прочитайте список записей и выполните текущие вычисления предыдущих записей, используя Prolog - PullRequest
0 голосов
/ 28 января 2019

Это пример игровой площадки, вдохновленный реальной задачей (гораздо более сложной), которую я когда-то делал.Основной поток состоит в том, что происходит чтение записей из последовательного файла.Некоторые записи содержат команды, которые требуют изучения предыдущих записей для вычисления значения.

Нежелательно в этом решении то, что оно требует дополнительного списка и, следовательно, дополнительного дублированного хранилища.Этот дополнительный список называется ПОМНИТЕ в следующем коде.Этот пример имеет простую структуру записей, содержащую только одно значение данных, поэтому дублирование всего в списке REMEMBER не является реальной проблемой.Но, пожалуйста, предположите, что реальная задача заключается в гораздо более сложной структуре записей, так что дублирование всего в списке ПОМНИТЕ очень нежелательно.

Я склонен использовать двусвязный список, однако для обсуждения по этой ссылке ВдвойнеСвязанный список в Прологе кажется, что Пролог не способ делать вещи.Поэтому мне интересно посмотреть, каким должен быть способ пролога.

/*
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
*/

Ответы [ 3 ]

0 голосов
/ 29 января 2019

Я бы попытался использовать полуконтекст DCG, как объяснено на странице metalevel.at .Пример OT, я думаю, легко понять, это этот ответ (решение зебра-подобной головоломки в DCG).

hint1 -->
  kind(brad, K), {dif(K, wheat)}, topping(brad, plain), size(walt, small).
hint2 -->
  size(P1, medium), size(P2, medium), {P1 \= P2},
  flavor(P1, hazelnut), topping(P2, peanut_butter).
...

Подсказки обращаются к разделению контекста «по волшебству»:

kind(P, K) --> state([P, K, _, _, _]).
topping(P, T) --> state([P, _, T, _, _]).
...

DCG должен вызываться таким образом, обеспечивая соответствующее начальное состояние:

bagels(Sol):- Sol =
    [[brad,_,_,_,_],
     [walt,_,_,_,_],
    ...],
  phrase((hint1, hint2, hint3, hint4, hint5, hint6), [state(Sol)], _).

Теперь для вашего аппликативного случая это почти бесполезно (вы уже решили,просто многословно).Для начала я не понимаю, почему вы выполняете алгоритм 2 прохода.Подумайте, насколько лаконичным может быть код, который дает те же результаты, которые вы опубликовали (просто отображали по-разному), за один проход, используя библиотеку (агрегат) для выполнения арифметики.Кстати, почему clpfd, clpr тоже могут рассчитывать ... Вы действительно заинтересованы в услугах CLP для такой простой задачи?

cc_main :-
    %system:( read_file_to_codes("doubly_motivation_data.txt",CODES,[]) ),
    codes(CODES),
    tokenize_atom(CODES, Tks),
    phrase(cc_report([],Rep), Tks),
    maplist(writeln, Rep).

cc_report(_,[]) --> [].
cc_report(R,Re) -->
    [data,N],
    cc_report([N|R],Re).
cc_report(R,[ave(Ave)=sum(Sum)/C|Re]) -->
    [average,C],
    {aggregate_all((sum(V),count),(
         % no need for doubly linked lists, just peek from stack...
         nth1(I,R,V),I=<C
       ),(Sum,Count)),Ave is Sum/Count},
    cc_report(R,Re).

приводит к:

?- cc_main.
ave(6.033333333333334)=sum(18.1)/3
ave(6.119999999999999)=sum(30.599999999999998)/5
ave(2.9142857142857146)=sum(20.400000000000002)/7
ave(4.775)=sum(19.1)/4
true .

В любом случае, аддоны swipl предлагают некоторые полезные материалы.Например, edgc , расширение для обработки многих нескольких аккумуляторов при выполнении нескольких посещений конкретного синтаксического дерева компилятора Acquarius Prolog - разработанного еще в 90-х годах.

0 голосов
/ 30 января 2019

Итак, для начала я заметил, что вы можете строить результаты в любом направлении.Другими словами, классическая грамматика "int list" выглядит примерно так:

intlist([]) --> [].
intlist([X|Xs]) --> integer(X), whites, intlist(Xs).

Это работает примерно так:

?- phrase(intlist(X), "1 23 45 9").
X = [1, 23, 45, 9] ;

Но вы можете перевернуть ее, чтобы она анализировала списокв обратном направлении, например:

rintlist([]) --> [].
rintlist([X|Xs]) --> rintlist(Xs), whites, integer(X).

Это работает, ish:

?- phrase(rintlist(X), "1 23 45 9").
X = [9, 45, 23, 1] 

Проблема с этим заключается в том, что сначала помещается рекурсивный вызов, за которым следует что-то вроде «пробелов», которые могутСоответствие пустым спискам - это рецепт взрыва стека.Но вы также можете анализировать вещи задом наперед, передав «предыдущее» состояние через саму DCG:

rintlist(L) --> rintlist([], L).
rintlist(Prev, Prev) --> [].
rintlist(Prev, Last) --> integer(X), whites, rintlist([X|Prev], Last).

?- phrase(rintlist(X), "1 23 45 9").
X = [9, 45, 23, 1] .

Теперь, я думаю, мы можем решить вашу проблему просто из этого;Я написал свое решение и теперь вижу, что оно очень похоже на вышеприведенное @ PauloMoura, но здесь оно в любом случае:

commands(Report) --> record(data(V)), blanks, commands([V], _, Report).

commands(Prev, Prev, []) --> [].
commands(Prev, Last, Report) -->
    record(data(V)),
    blanks,
    commands([V|Prev], Last, Report).
commands(Prev, Last, [report(Count, Total, Avg)|Report]) -->
    record(average(N)),
    blanks,
    { calculate_average(N, Prev, Count, Total, Avg) },
    commands(Prev, Last, Report).

calculate_average(N, Prev, Count, Total, Avg) :-
    length(L, N),
    append(L, _, Prev),
    sumlist(L, Total),
    Avg is Total / N,
    Count = N.

Это похоже на вывод, аналогичный вашему примеру:

?- phrase_from_file(commands(C), 'mdata.txt'), write_canonical(C).
[report(3,18.1,6.033333333333334),
 report(5,30.599999999999998,6.119999999999999),
 report(7,20.400000000000002,2.9142857142857146),
 report(4,19.1,4.775)]

Теперь, расширив его до двусвязного списка, давайте сначала посмотрим, что нам нужно сделать для обработки грамматики «int list» двусвязным способом.Так же, как и этот, мы должны передать предыдущую ссылку вперед в рекурсивный вызов, но, делая его немного хуже, чем этот, нам нужно заполнить «следующую» ссылку в предыдущей переменной, которую мы получаем, с текущим узлом.Но поскольку эта ссылка будет nil в первый раз, мы должны иметь немного условной логики, чтобы игнорировать эту.И я не мог придумать разумного пустого двусвязного списка, поэтому я изменил базовый вариант на [X] вместо [].Так что это становится немного неопрятным.

% entry point (nil meaning there is no previous)
dlist(X) --> dlist(nil, X).

% base case: last integer
dlist(Prev, node(X, Prev, nil)) --> integer(X).
dlist(Prev, Last) -->
    integer(X), whites,
    {
     Prev = node(PV, PP, Cur)
     -> 
         Cur = node(X, node(PV, PP, Cur), _)
     ;
         Cur = node(X, Prev, _)
    },
    dlist(Cur, Last).

Обратите внимание на самоссылку в Cur = node(..., node(..., Cur), ...).Это объединение как бы «связывает узел» между предыдущей ссылкой и этой ссылкой.Давайте попробуем это:

?- phrase(dlist(L), "1 23 45 9").
L = node(9, _S2, nil), % where
    _S1 = node(1, nil, node(23, _S1, _S2)),
    _S2 = node(45, node(23, _S1, _S2), _71658) 

Немного сложно прочитать, но в основном, от 9 до 45 баллов против 23 до 1 балла. Мы проанализировали его задом наперед и навели указатели в обоих направлениях.

На этом этапе остается только изменить синтаксический анализатор, чтобы вместо этого выдавать записи с этими указателями, и написать усреднитель, который работает таким образом.Я не мог добиться того, чтобы получить среднее значение на месте, поэтому я написал помощника, который дал бы мне «до N предыдущих» из двусвязного списка:

take_upto(N, DL, Result) :- take_upto(N, 0, DL, [], Result).
take_upto(N, N, _, Result, Result).
take_upto(_, _, nil, Result, Result).
take_upto(N, I, node(V, Prev, _), Rest, Result) :-
    I < N,
    succ(I, I1),
    take_upto(N, I1, Prev, [V|Rest], Result).

Это работает так:

?- phrase(dlist(X), "1 2 3 4 5 6 7 8 9 10"), take_upto(5, X, L).
X = node(10, _S2, nil), % where
   ... [trimmed]
L = [6, 7, 8, 9, 10] .


?- phrase(dlist(X), "1 2 3 4 5 6 7"), take_upto(15, X, L).
X = node(7, _S2, nil), % where
   ... [trimmed]
L = [1, 2, 3, 4, 5, 6, 7] .

С помощью этой утилиты мы можем закончить это:

commandsdl(Report) --> commandsdl(nil, _, Report).
commandsdl(Prev, Prev, []) --> [].
commandsdl(Prev, Last, Report) -->
    record(data(V)),
    blanks,
    {
     Prev = node(PV, PP, Cur)
     ->
         Cur = node(V, node(PV, PP, Cur), _)
     ;
         Cur = node(V, Prev, _)
    },
    commandsdl(Cur, Last, Report).
commandsdl(Prev, Last, [report(Count, Total, Avg)|Report]) -->
    record(average(N)),
    blanks,
    {
       calculate_average_dl(N, Prev, Count, Total, Avg)
    },
    commandsdl(Prev, Last, Report).

calculate_average_dl(N, Prev, Count, Total, Avg) :-
    take_upto(N, Prev, L),
    length(L, Count),
    sumlist(L, Total),
    Avg is Total / Count.

В целом, я рад, что смог сделать эту работу, но в этой формулировке вы действительно не понимаетеМне не нужны «следующие» указатели в вашем двусвязном списке, так что я склонен просто перейти к реализации списка выше (или, возможно, реализации Пауло, если бы я смотрел на Logtalk).Надеюсь, это иллюстрирует, как вы могли бы сделать это с помощью двусвязных списков, если ваша реальная проблема требует этого, несмотря на то, что ваша модель на самом деле не нуждается в этом.Надеюсь, это поможет!

0 голосов
/ 28 января 2019

Используется решение Logtalk + SWI-Prolog, которое не требует материализации двойных списков.Требуется только стек, тривиально реализованный с использованием списка:

------------ reports.lgt ------------
% load the required modules
:- use_module(library(dcg/basics), []).

% ensure desired interpretation of double-quoted text
:- set_prolog_flag(double_quotes, codes).

% optimize the generated code
:- set_logtalk_flag(optimize, on). 


:- object(reports).

    :- public(process/2).

    :- uses(list, [take/3]).
    :- uses(numberlist, [sum/2]).
    :- uses(reader, [file_to_codes/2]).

    :- use_module(dcg_basics, [blanks//0, whites//0, integer//1, float//1]).

    process(File, Report) :-
        file_to_codes(File, Codes),
        phrase(report(Report), Codes).

    report(Report) -->
        data(Value),
        report(Report, [Value]).

    report([], _) -->
        eos.
    report([Record| Records], Stack) -->
        average(Count),
        {compute_record(Count, Stack, Record)},
        report(Records, Stack).
    report(Records, Stack) -->
        data(Value),
        report(Records, [Value| Stack]).

    average(Count) -->
        "average", whites, integer(Count), blanks.

    data(Value) -->
        "data", whites, float(Value), blanks.

    compute_record(Count, Stack, r(Count,Total,Average)) :-
        take(Count, Stack, Values),
        sum(Values, Total),
        Average is Total / Count.

:- end_object.
-------------------------------------

Пример вызова с использованием файла данных в вопросе:

?- {library(types_loader), library(reader_loader), reports}.
...

?- reports::process('doubly_motivation_data.txt', Report).
Report = [r(3, 18.1, 6.033333333333334), r(5, 30.599999999999998, 6.119999999999999), r(7, 20.400000000000002, 2.9142857142857146), r(4, 19.1, 4.775)] .

Как вы заметили, я использую более разумное представлениедля отчета, чем список списков.Немного более эффективное решение можно кодировать, объединяя вызовы take/3 и sum/2 в пользовательский предикат, избегая двойного обхода префикса стека длиной Count и создавая временный список значений.Например:

compute_record(Count, Stack, r(Count,Total,Average)) :-
    compute_record(0, Count, Stack, 0, Total),
    Average is Total / Count.

compute_record(Count, Count, _, Total, Total) :-
    !.
compute_record(Count0, Count, [Value| Stack], Total0, Total) :-
    Count1 is Count0 + 1,
    Total1 is Total0 + Value,
    compute_record(Count1, Count, Stack, Total1, Total).

Из примера файла данных кажется, что файл может завершиться запросом на вычисление среднего значения всех значений в файле.Таким образом, нетерминал report//2 должен сохранять весь стек до тех пор, пока не будет обработан весь файл данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...