Мой предыдущий ответ был «быстрый и грязный», и вскоре показывает его ограничения по мере роста количества фильмов. Вот лучший способ найти лучшее соответствие и сравнение с предыдущим ответом (переформулировано в соответствии с требованиями теста).
Ключ для оптимизации предлагается тегом рюкзак , который справедливо использовал Аксель при публикации вопроса. Я искал в 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.