Итак, для начала я заметил, что вы можете строить результаты в любом направлении.Другими словами, классическая грамматика "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).Надеюсь, это иллюстрирует, как вы могли бы сделать это с помощью двусвязных списков, если ваша реальная проблема требует этого, несмотря на то, что ваша модель на самом деле не нуждается в этом.Надеюсь, это поможет!