подходят фильмы на dvd, работают, но вопросы по стилю / коду

Я сделал программу пролога, чтобы показать мне, как лучше всего размещать материалы на DVD. Вопросы находятся в комментариях к коду для справки, который я вставлю ниже, но все сводится к следующему:

  1. Is there a sort of inverted cut operator to make it search for more although it already matches? See fitexact, something like fitexact(Size,Sum,L,L):-Sum

  2. What's the best way to keep track of already processed movies? I retract them but wonder how to do it without that.

  3. fitfuzzy использует конструкцию if then. Я не уверен, что о них думать, это странно в прологе. Однако попытка сделать его рекурсивным оставила меня ужасно сбитым с толку :)


    % given a list of movies and sizes, try to fit them all onto dvd's
    % wasting as little space as possible.

    % set the dvd size
    dvdsize(4812).

    % sum of all movies in the db
    movies_size(Size) :- findall(S, movie(_,S), LS), sum_list(LS,Size).

    % count of all movies in the db
    movies_count(Count) :- findall(S, movie(_,S), LS), length(LS,Count).

    % see which ones fit exactly
    % this is where i got into trouble, the original idea was to make
    % it like the fuzzy search below but i don't understand how i can 
    % express 'when there are no more movies which make an exact fit,
    % and the sum is smaller then the dvdsize the list is ok too'. 
    fitexact(Movies) :- dvdsize(Size), fitexact(Size, 0, [], Movies).

    % Stop when there's a perfect fit
    % so here i tried Size,Sum and Sum<Size in the body. That obviously
    % doesn't work since it instantly matches. 
    fitexact(Size, Size, Movies, Movies).
    % since otherwise the same movies show up on different dvd's i 
    % thought it would be best to delete them after they fitted. 
    % if I don't want to do that, what's the best way to make sure once
    % a movie is processed it won't show up again? Should it have an extra
    % flag like processed(movie(name,size,processed) or should i assert 
    % done dvd's and see if they're already in them? I wonder how long this
    % all would take since it's already quite slow as is.
    %% :-
    %%  forall(member(Movie,Movies), retract(movie(Movie,_))). %%, !.

    % Otherwise keep filling
    fitexact(Size, Sum, Acc, Movies) :-
      movie(Movie, MovieSize),
      \+ member(Movie, Acc), % no doubles!
      NewSum is Sum + MovieSize,
      NewSum =< Size,
      fitexact(Size, NewSum, [Movie|Acc], Movies). 

    removedvd(DVD) :- 
      forall(member(Movie,DVD),retract(movie(Movie,_))).

    % do a fuzzy fit, try exact fits with decreasing size when
    % there are no exact fits.
    fitfuzzy(DVD) :- dvdsize(Size), fitfuzzy(DVD,Size,0).
    fitfuzzy(_,Size,Size) :- movies_size(Size), !.
    fitfuzzy(_,Size,Size) :- dvdsize(Size), !.
    fitfuzzy(DVD,Size,Wasted) :-
      CheckSize is Size - Wasted,
    % this feels like a horrible way to do this. I very much like suggestions
    % about how to make it recursive or better in general.
      ( fitexact(CheckSize, 0, [], DVD)
      -> removedvd(DVD) 
      ;  NewWasted is Wasted + 1, 
         write('fitfuzzy: Increasing wasted space to '), write(NewWasted), nl,
         fitfuzzy(DVD,Size,NewWasted)
      ).

    status :-
      movies_count(MoviesLeft),
      movies_size(MoviesSize), 
      write('Movies left: '), write(MoviesLeft), nl,
      write('Size left  : '), write(MoviesSize), nl.

    burnloop :-
     movies_count(C), C>0,
     fitfuzzy(DVD),
     status,
     write('DVD = '), print(DVD),nl, nl,
     burnloop.

    % movies.db contains a list of movie(Name,Size). statements. It also
    % must have :- dynamic(movie/2). on top for retract to work.
    go :-
     ['movies.db'],
     burnloop.


person Community    schedule 03.04.2012    source источник


Ответы (4)


Просто общий комментарий: вместо того, чтобы отслеживать обработанные фильмы, я считаю более естественным сначала получить (например, через findall / 3) список фильмов, которые все еще нужно < / em> обработать, а затем просто отработать этот список. Итак, у вас есть burn_dvd(List0, DVD, List), который принимает список фильмов (возможно, в сочетании с их размерами, скажем, в форме movie_size(Name, Size)) в качестве своего первого аргумента, создает один DVD (выбирая столько фильмов из List0, сколько помещается на одном DVD. (например, после сортировки списка по размеру и т. д.), а третий аргумент - это список оставшихся фильмов. Затем у вас есть естественное расширение burn_dvds (List, DVDs), которое просто создает DVD-диски до тех пор, пока больше не останется фильмов:

burn_dvds([], []) :- !.
burn_dvds(Movies0, [DVD|DVDs]) :-
    burn_dvd(Movies0, DVD, Movies),
    burn_dvds(Movies, DVDs).

Для этого не требуется assert / 1 или retract / 1. Возможны несколько решений, если burn_dvd / 3 недетерминированно создает один DVD, что вам может понадобиться и что тоже кажется естественным.

Использование if-then-else совершенно нормально, однако все, что может быть выражено сопоставлением с образцом, должно быть выражено сопоставлением с образцом, поскольку оно обычно дает более общий и более эффективный код.

format / 2 также может помочь вам с выводом: Вместо:

write('Movies left: '), write(MoviesLeft), nl

ты можешь написать:

format("Movies left: ~w\n", [MoviesLeft])

В общем, ручной вывод требуется редко, поскольку вы всегда можете предоставить решения для печати верхнего уровня за вас. В нашем случае burn_dvds / 2 естественно выдает список DVD в качестве ответа, когда вы его запрашиваете.

person mat    schedule 03.04.2012
comment
Хорошо, я должен составить список из них, спасибо, что указали на это. Что касается нескольких решений, вы имеете в виду перестановки созданного DVD? Я бы хотел избежать этого, если возможно, поэтому я отказался от них. - person ; 04.04.2012
comment
Не (только) перестановки, но очевидно, что существует множество способов создания одиночных DVD-дисков, которые по-прежнему приводят к неизоморфным решениям. Например, если два фильма имеют одинаковый размер, вы можете разместить один или другой на любом из двух DVD-дисков, на котором еще достаточно места. Использование побочных эффектов, таких как assert / 1 и retract / 1, мешает хорошим декларативным решениям, которые позволяют легко исследовать такие различные варианты, используя стратегию естественного поиска Prolog. - person mat; 04.04.2012
comment
да, я думаю, что заметил эти побочные эффекты при использовании retract в том же функторе, см. прокомментированный оператор forall в fitexact. Однако размещение его вне этого значительно ускоряет процесс, поскольку вначале есть много фильмов, которые подходят, так что есть лихорадка, которую нужно проверить при следующем вызове. Забавно видеть, что при работе с операторами вывода вы увидите, что сначала fitfuzzy очень медленно считает, затем увеличивается до тех пор, пока не останется около 30 трудно вписываемых (›4000 МБ), а затем снова замедляется для последних. Ну, не сам подсчет, а время, необходимое для поиска наилучшего варианта. - person ; 04.04.2012
comment
Я сделал версию, которая теперь использует списки, хотя она все еще использует несколько неуклюжий функтор removevd для доступа к оставшимся. Проблема в том, что они не все подходят, поэтому я остаюсь с парочкой, которой нужно отрегулировать доступное пространство. - person ; 09.04.2012
comment
и мне трудно сохранить оставшийся список в параметрах функтора fitexact, чтобы избавиться от этого. Я имею в виду, пытаясь найти совпадение с остановкой рекурсии. Так или иначе. Я буду продолжать играть с ним, но, как известно, он работает быстрее, чем указано выше, и я скомпилировал его в собственный код, чтобы распечатать мне список DVD. Пролог действительно классный :) - person ; 09.04.2012
comment
Это скорректированный код. Мне нравится иметь в fitall оператор формата для отладки и отслеживания прогресса, но другой способ тоже работает нормально. Еще немного работы, пока я проверяю другие примеры. Спасибо, народ, молодец. axel3.home.xs4all.nl/fit.pro - person ; 09.04.2012
comment
слишком много комбинаций для правильной работы, поэтому моя проблема должна была заключаться в том, чтобы найти следующее лучшее совпадение, а не идеальное. Я смотрю на код clpfd в поисках лучшего решения, чем 39 DVD примерно за 10 секунд. Со 150 предметами он использует много таранов, когда я не убираю между ними иначе. - person ; 09.04.2012

  1. Если вы хотите, чтобы все происходило так, как если бы вы продолжали просить другое решение после того, как каждое из них было предоставлено, но собираете их все в список, findall - это то, что вам нужно.

  2. Если все это происходит в рамках одного запроса, вы можете передать список использованных фильмов. Например, цикл записи будет принимать в качестве аргумента список уже использованных фильмов; fitfuzzy возьмет этот список и заполнит новую версию фильмами для этого DVD, и вы передадите этот новый список в burnloop. Или, поскольку DVD содержит новые фильмы, напишите новый предикат, чтобы добавить фильмы на DVD в старый список, чтобы создать новый.

  3. Что, если вы позволите fitexact продолжить работу, как сейчас, но при этом сохраните список фильмов, которые наиболее близки к размеру DVD, чтобы вместо сбоя, когда DVD не заполняется точно, он выдавал этот список?

person Scott Hunter    schedule 03.04.2012
comment
спасибо, интересно как собирать вещи. Я должен читать lpn вместо того, чтобы бегло просматривать его ... вместо findall я думаю, что select и setof - это аккуратно. Я думаю, что это даже исправит мою первую программу пролога, которая была головоломкой порядка меню xkcd. - person ; 04.04.2012
comment
как, например, комбинирование терминов в findall; findall([_,Size], (movie(_,Size), Size > 4000), R) - person ; 08.04.2012

«Правило наилучшей практики» Пролога гласит, что следует избегать утверждения / отзыва, за исключением случаев, когда это абсолютно необходимо (т.е. когда нет декларативного подхода).

Здесь программа, использующая select / 3 для генерации всех комбинаций

movie(a, 10).
movie(b, 3).
movie(c, 5).
movie(d, 6).
movie(e, 10).

dvdsize(20).

burn(Best) :-
    findall(N-S, movie(N,S), L),
    dvdsize(Max),
    setof(Wasted-Sequence, fitmax(L, Max, Wasted, Sequence), All),
    All = [Best|_],
    maplist(writeln, All).

fitmax(L, AvailableRoom, WastedSpace, [Title|Others]) :-
    select(Title - MovieSize, L, R),
    MovieSize =< AvailableRoom,
    RoomAfterMovie is AvailableRoom - MovieSize,
    fitmax(R, RoomAfterMovie, WastedSpace, Others).

fitmax(_, WastedSpace, WastedSpace, []).

вывод:

?- burn(X).
0-[a,e]
0-[e,a]
1-[a,b,d]
1-[a,d,b]
1-[b,a,d]
1-[b,d,a]
1-[b,d,e]
1-[b,e,d]
1-[d,a,b]
1-[d,b,a]
1-[d,b,e]
1-[d,e,b]
1-[e,b,d]
1-[e,d,b]
2-[a,b,c]
2-[a,c,b]
2-[b,a,c]
2-[b,c,a]
2-[b,c,e]
2-[b,e,c]
2-[c,a,b]
2-[c,b,a]
2-[c,b,e]
2-[c,e,b]
2-[e,b,c]
2-[e,c,b]
4-[a,d]
4-[d,a]
4-[d,e]
4-[e,d]
5-[a,c]
5-[c,a]
5-[c,e]
5-[e,c]
6-[b,c,d]
6-[b,d,c]
6-[c,b,d]
6-[c,d,b]
6-[d,b,c]
6-[d,c,b]
7-[a,b]
7-[b,a]
7-[b,e]
7-[e,b]
9-[c,d]
9-[d,c]
10-[a]
10-[e]
11-[b,d]
11-[d,b]
12-[b,c]
12-[c,b]
14-[d]
15-[c]
17-[b]
20-[]
X = 0-[a, e].
person CapelliC    schedule 03.04.2012
comment
хорошо, я думаю, я могу это решить. Я должен иметь в виду, что когда вы используете findall(N-S, movie(N,S), L), ..., пролог не вычисляет N-S. Я только начинаю с этого, и это меня смущает. Также я никогда не слышал о select и maplist, так что буду читать больше о них и других. Спасибо! - person ; 04.04.2012

Мой предыдущий ответ был «быстро и грязно», и вскоре он показывает свои пределы, поскольку количество фильмов растет. Вот лучший способ найти наилучшее соответствие и сравнение с предыдущим ответом (переформулированным в соответствии с требованиями теста).

Ключ к оптимизации предлагается тегом рюкзак, который Аксель справедливо использовал при публикации вопроса. Я искал в поддержке CLP (FD) подходящий способ решения проблемы, вот он:

:- [library(clpfd)].

%%  use CLP(FD) to find best fit
%
burn_knapsack(Best, Wasted) :-
    dvdsize(Max),
    findall(Title - Size, movie(Title, Size), Movies),
    knaps(Movies, Max, Best, Wasted).

knaps(Movies, Max, Best, Wasted) :-
    findall([Flag, Title, Size],
        (Flag in 0..1, member(Title - Size, Movies)), AllMovies),
    transpose(AllMovies, [ToBurn, Titles, Sizes]),

    Actual #=< Max,
    scalar_product(Sizes, ToBurn, #=, Actual),
    labeling([max(Actual)], [Actual|ToBurn]),
    findall(Title, (nth1(I, ToBurn, 1),
            nth1(I, Titles, Title)), Best),
    Wasted is Max - Actual.

%%  compute all combinations of movies that fit on a dvd
%   it's a poor man clpfd:scalar_product/4
%
burn_naive(Best, Wasted) :-
    dvdsize(Max),
    findall(Title - Size, movie(Title, Size), Movies),
    naive(Movies, Max, Best, Wasted).

naive(Movies, Max, Best, Wasted) :-
    setof(Wasted-Sequence, fitmax(Movies, Max, Wasted, Sequence), [Wasted-Best|_]).

fitmax(L, AvailableRoom, WastedSpace, [Title|Others]) :-
    select(Title - MovieSize, L, R),
    MovieSize =< AvailableRoom,
    RoomAfterMovie is AvailableRoom - MovieSize,
    fitmax(R, RoomAfterMovie, WastedSpace, Others).
fitmax(_, WastedSpace, WastedSpace, []).

%%  run test with random generated list
%
%   From,To are num.of.movies
%   SzMin, SzMax min/max+1 of each movie size
%
test_performance(From, To, DvdSize, SzMin, SzMax) :-
    forall((between(From, To, NumMovies),
        gen_movies(NumMovies, SzMin, SzMax, Movies)
           ),
           (   (   NumMovies < 11
           ->  test(naive, Movies, DvdSize)
           ;   true
           ),
           test(knaps, Movies, DvdSize)
           )).
test(Method, Movies, DvdSize) :-
    time(once(call(Method, Movies, DvdSize, Best, Wasted))),
    writeln((Method, Best, Wasted)).

gen_movies(NumMovies, SzMin, SzMax, Movies) :-
    findall(Title-Size,
        (   between(1, NumMovies, Title),
            random(SzMin, SzMax, Size)
        ), Movies).

Я ограничил тест на наивность менее чем 11 фильмами, чтобы избежать переполнения стека.

?- test_performance(8,20, 30, 3,7).
% 93,155 inferences, 0,140 CPU in 0,140 seconds (100% CPU, 665697 Lips)
naive,[1,2,3,5,6],0
% 235,027 inferences, 0,159 CPU in 0,159 seconds (100% CPU, 1481504 Lips)
knaps,[2,3,5,6,8],0
% 521,369 inferences, 0,782 CPU in 0,783 seconds (100% CPU, 666694 Lips)
naive,[1,2,3,4,5,6],0
% 163,858 inferences, 0,130 CPU in 0,131 seconds (100% CPU, 1255878 Lips)
knaps,[3,4,5,6,7,9],0
% 1,607,675 inferences, 2,338 CPU in 2,341 seconds (100% CPU, 687669 Lips)
naive,[1,2,3,4,7,8],0
% 184,056 inferences, 0,179 CPU in 0,180 seconds (100% CPU, 1027411 Lips)
knaps,[5,6,7,8,9,10],0
% 227,510 inferences, 0,156 CPU in 0,156 seconds (100% CPU, 1462548 Lips)
knaps,[5,6,8,9,10,11],0
% 224,621 inferences, 0,155 CPU in 0,155 seconds (100% CPU, 1451470 Lips)
knaps,[6,7,8,9,10,11,12],0
% 227,591 inferences, 0,159 CPU in 0,159 seconds (100% CPU, 1434836 Lips)
knaps,[5,7,9,10,11,12,13],0
% 389,764 inferences, 0,263 CPU in 0,263 seconds (100% CPU, 1482017 Lips)
knaps,[5,8,9,10,12,13,14],0
% 285,944 inferences, 0,197 CPU in 0,198 seconds (100% CPU, 1448888 Lips)
knaps,[8,9,10,12,13,14,15],0
% 312,936 inferences, 0,217 CPU in 0,217 seconds (100% CPU, 1443891 Lips)
knaps,[10,11,12,14,15,16],0
% 343,612 inferences, 0,238 CPU in 0,238 seconds (100% CPU, 1445670 Lips)
knaps,[12,13,14,15,16,17],0
% 403,782 inferences, 0,277 CPU in 0,278 seconds (100% CPU, 1456345 Lips)
knaps,[11,12,13,15,16,17],0
% 433,078 inferences, 0,298 CPU in 0,298 seconds (100% CPU, 1455607 Lips)
knaps,[14,15,16,17,18,19],0
% 473,792 inferences, 0,326 CPU in 0,327 seconds (100% CPU, 1451672 Lips)
knaps,[14,15,16,17,18,19,20],0
true.
person CapelliC    schedule 04.04.2012
comment
да, я заметил, что мой файл movie.db содержит 156 фильмов, и тестирование с ним происходит очень медленно. Я тоже боялся использования памяти, но, похоже, оно очень медленно растет. Поэтому откажитесь снова, но теперь я знаю, что лучше этого не делать. Мне понадобится время, чтобы изучить ваши примеры .. - person ; 04.04.2012
comment
Я должен добавить, я использую gprolog и знаю о его различиях в отношении библиотек и clpfd, хотя я думаю, что могу справиться. Однако у меня нет транспонирования. Я знаю, что есть совместимость, поэтому я посмотрю, сработает ли это, или, может быть, мне нужно скомпилировать swi? - person ; 05.04.2012
comment
транспонировать его как аксессуар, и его несложно закодировать: вы можете получить его из SWI-Prolog CLP (FD). см. здесь для scalar_product - person CapelliC; 05.04.2012
comment
Думаю, вы правильно ответили на мой вопрос, поскольку все началось с первого вопроса. Я думаю, что если бы в исходном коде fitfuzzy сохранил прежнюю потерянную ценность и, возможно, использовал бы select вместо доступа к базе данных, это будет намного быстрее, тогда это будет забавная разработка, большое спасибо за ваши примеры и идеи! - person ; 05.04.2012
comment
Я хотел бы добавить, что особенно, если вы новичок в Prolog, SWI, на мой взгляд, является наиболее удобной системой для использования: в ней есть make / 0, графический трассировщик (? - gtrace, Goal) и другие замечательные системы верхнего уровня. особенности (помощь / 1 и т. д.). - person mat; 05.04.2012
comment
@mat: реализация GProlog CLP быстрее, а SWI-Prolog CLP (полностью Prolog) более полная. Поэтому, выбирая между ними, мы должны уравновесить эти факторы ... - person CapelliC; 05.04.2012
comment
Да, я рекомендую начать разработку в SWI, потому что это более удобно, и если это окажется слишком медленным, переключить или делегировать части другим системам. - person mat; 05.04.2012
comment
Я уже несколько раз переписывал его со списками, не глядя на приведенные примеры, так как я только начинаю с пролога, и это забавная проблема, на которой можно учиться. Я все еще застреваю, но я уверен, что в конце концов я с этим справлюсь. Пролог действительно изящный :) Возможно, мне стоит установить emacs, я использовал его раньше как своего рода идеал, держу пари, что у него есть режим пролога. Что касается реализации, то мне пока наплевать на такие вещи, как графика или модули (хотя gprolog реализует это), базовые вещи достаточно сложны, чтобы думать как есть :) - person ; 08.04.2012
comment
Графический трассировщик звучит забавно, а трассировка огромными списками на терминале 80x25 - это неинтересно. Однако это действительно помогает понять, как пролог решает проблемы, и несколько раз показало мне, почему что-то привело к переполнению стека. Давайте действительно установим оба :) - person ; 08.04.2012
comment
Да, в последних версиях Emacs есть действительно отличный режим Prolog, и, начиная с Emacs 24.1., Вы также можете установить ediprolog с M-x list-packages, что дает вам прозрачное взаимодействие с SWI-Prolog из буферов Emacs. - person mat; 10.04.2012