Prolog is a personal favourite of mine, but I'll readily admit that it
doesn't do very well performance benchmarks:
http://shootout.alioth.debian.org/gp4/benchmark.php?test=all&lang=ghc&lang2=
swiprolog&box=1
I think my first and naive Prolog attempt is pretty elegant, and seems to
run faster and more efficiently than my recollection of Mark's results.
On len = 1000000 / Nth = 40 it ran in 53 seconds (including all the I/O),
and used less than 100MB RAM. With aggressive garbage collection, it runs
with less than 32MB RAM in 105 seconds.
Prolog source appears below.
How it works:
The key with my Prolog solution is that I convert the input into a
difference list (this seems to be a Prolog equivalent of the goals of
circular programming). A diff list allows "queue" operations to be performed
in O(1) time.
The algorithm works by treating the diff list as a queue, and dequeueing the
head until it is empty:
- if the head should be killed, then put it in the death list.
- if the head should be saved (for now), then put it back at the end of the
queue.
This naive approach means that solve(List, N, .., ..) runs in about
O(length(List) * N) time.
The code may be confusing if you're unfamiliar with the difference list
idiom; but I think it should be straightforward to Prolog programmers who've
had some experience with it.
-Ben
%---------------------
% "Friendly" interface
solve(Input, Nth, DeathList, Survivor) :-
% Convert the input into a difference list (i.e. a Queue)
append(Input, End, InputDiff),
% Then run the solver...
!, take(Nth, Nth, InputDiff-End, DeathList, Survivor).
%---------------------
% Base case: only one left - it's the survivor
take(_, _, [Survivor|Rest]-End, [Survivor], Survivor) :-
Rest == End, !.
% Found the person to kill: add to death list, reset the counter
take(N, 1, [H|Alive]-End, [H|Dead], Survivor) :-
!, take(N, N, Alive-End, Dead, Survivor).
% Not yet at person to kill: add to the end of the queue, decrement counter
take(N, C, [H|Alive]-[H|NewEnd], Dead, Survivor) :-
C2 is C - 1,
!, take(N, C2, Alive-NewEnd, Dead, Survivor).
%---------------------
> So, I attempted the Josephus Flavius Problem in Prolog (SWI-Prolog).
Thanks Ben, thats very cool.
The only problem is I can't figure out how to compile it :-). I've
installed SWI-Prolong version 5.6.59 on an Ubuntu Linux system and
I've read the man page for swipl but I still can't figure it out.
Clues?
Cheers,
Erik
--
----------------------------------------------------------------------
Erik de Castro Lopo
http://www.mega-nerd.com/
> the performance i gathered was using ghci, compiling gives it a 3
> second execution time, even runghc gives 3 seconds, so there must be
> something horribly inefficient happening in ghci.
Did you compile with -O?
GHCi is an *interpreter* with very little optimisation as it isn't
intended for "production" runs of any code. It also maintains data
structures for source level debugging. There is no point in
benchmarking ghci code.
> and when compiled with heap profiling it uses less than 30M with a
> nice mountain peak at 0.5 seconds and a slow decline from 25M - 20M
> until dropping at the end.
For any meaningful benchmarking, you need to compile with -O or -O2.
> 10 million input list
> ---------------
> Program -> Time -> Memory
> ghc -> 30s (150s with profiling) -> max 400M (total 33G - this value
> lines up with ghci's memory usage so maybe they are related?)
If you compiled without -O, then they are related by both *lacking*
any significant static optimisations. (However, the compiled code is
at least not interpreted.)
Manuel