Progblemet #3
Ibland kan det finnas anledning att skapa alla möjliga permutationer av element som finns i en lista.
Listan [0,1,2] har följande möjliga permutationer
[[0,1,2],[0,2,1],[1,0,2],[1,2,0],[2,0,1],[2,1,0]]
Som synes kommer en listan med N element ha N! permutationer.
För att jämföra effektiviteten av olika lösningar så kan vi mäta tiden det tar att generera alla möjliga lösningar. För att språk med s.k. lazy-evaluation inte ska optimera bort hela beräkningen måste man använda resultatet.
Så en körning kan se ut något likt detta (från min Ruby lösning nedan), den lista som är helt utskriven är [0,1,2] och den som bara längden är utskriven på är [0,1,2,3,4,5,6,7,8,9]
[[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
3628800
Elapsed time: 0m32.751s
Språk som stödjer s.k. list comprehension kan få väldigt korta och eleganta lösningar, men kanske inte speciellt effektiva i vissa lägen. Ett sådan exempel är Erlang där man kan lösa detta exempel med väldigt lite kod
% The permutations of an empty list is a list with a single empty list
perms([]) ->
[[]];
% Permutations of a non-empty list is created by concatenate each
% element in the list with every permutation of the rest of the list
% with the element used as head removed.
perms(L) ->
[[Head|Tail] || Head <- L, Tail <- perms(L -- [Head])].
io:fwrite("~w~n", [perms([0,1,2])]).
io:fwrite("~w~n", [length(perms(lists:seq(0,9)))]).
Tiden för att köra beräkningen på en Core i7 2600 körandes Erlang R14B04 på 64-bitars Ubuntu 12.04 är 1.69s
Den andra lösningen är i Ruby som inte stödjer list comprehension men ändå har bra stöd för att jobba med listor via map, select (filter).
def perms(l)
if l.empty?
[[]]
else
l.map{|head| perms(l.select{|e| head!=e}).map{|e|[head]+e}}.flatten(1)
end
end
puts perms((0..2).to_a).to_s
puts perms((0..9).to_a).length
Det blir lite mer komplicerat här då varje map skapar en separat lista så resultatet blir
[ [[0,1,2],[0,2,1]], [[1,0,2], [1,2,0]], ....]
I.e. det yttre map genererar en separat lista där element 0 är första element, en separat lista där element 1 är första element etc. Resultatet blir att det är en nivå för djup nästlade listor här, vilket löses med anropet till flatten(1) som plattar ut listan i exakt en nivå.
Farten är inte alls lika bra som i Erlang, att köra detta på samma maskin som ovan tar 32.8s körandes Ruby 1.9
Har inte hunnit testa detta i språk som C eller C++ och om tiden det tar generera alla permutationer är <1s så lägg till resultatet för det antal element som gör att beräkningen överstiger i alla fall 1s.
Edit: litet förtydligande. En lista/array/vektor eller liknande med ALLA permutationer ska alltså finnas i RAM när perms (eller vad ni kallar den) funktionen retunerar. Så det räcker inte enbart med att köra std::next_permutation i C++ och bara räkna antal varv, varje permutation måste även sparas för att göra det till en så äpplen mot äpplen jämförelse som möjligt mellan språken.
Och den observante har nog redan noterat att mina lösningar var typiska divide-and-conquer så för den som vill vara lite mer avancerad så finns det potential till massor med parallellism för att accelerera detta över flera CPU-kärnor.
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer