I'm a beginner at Haskell as well, so take my opinion with a big rock of salt.
nextPerm :: (Ord a, Num a) => [a] -> [a]
nextPerm [] = []
nextPerm a
I wouldn't use a
as a type variable and a variable at the same time.
I like to name lists xs, ys, cs
, etc. I feel this is more descriptive, and it works well with pattern-matching, e.g. (x:xs)
, where x
is the head of the list, xs
is the tail.
swap = foldl (\acc x -> if x <= (last acc) then acc ++ [x] else [x]) [(-1)] a
Here you are going through the list from the front. When you use last
, the program has to go through the whole list, because lists are kind of like linked lists, think [1,2,3] = 1 : 2 : 3 : []
, so accessing an element takes \$O(n) \$ time.
For the same reason, (++)
is expensive, while (:)
is cheap. Try running swap
on a list of \$ 10^5 \$ size; it's slow.
Other than the speed factor, I think your implementation is OK!
If you want to make it faster, first of all you should change swap
. For example, first reverse the list, then work on it - as lists are made to be accessed from the front, not the back. Let me show you an improved version of swap
along with a few other changes.
nextPerm :: Ord a => [a] -> [a]
nextPerm xs
| null xs || pivotIndex == -1 = xs
| otherwise = prePivot ++ swapVal : swapWithPivot
where pivotIndex = length xs - length swap - 1
prePivot = take pivotIndex xs
pivotVal = xs !! pivotIndex
swapVal = foldl1 (\acc x -> if x > pivotVal then x else acc) swap
swapWithPivot = insert pivotVal . delete swapVal . reverse $ swap
swap = reverse . fmap fst
. takeWhile (uncurry (>=))
. (<*>) zip (\ls -> head ls:ls)
. reverse $ xs
As you can see, this way, you don't even need to restrict yourself to the class Num
.
To make it even better, figure out how to use span
instead of take
, and takeWhile
. If you do that, you won't even need indexing anymore.
Use Hoogle, play around in ghci
, look at types using :t <typename>
e.g. :t span
- type signatures (and names) will usually tell you what a function does.
Lists are weak at random access, but kind of good at being sliced up; therefore I'd definitely drop the whole indexing thing.
Anyway, I will include how I'd write it, hopefully it'll give you some new ideas.
nextPerm' :: Ord a => [a] -> [a]
nextPerm' = uncurry (++) . uncurry swap
. (fmap fst *** fmap fst)
. span (uncurry (>=))
. (<*>) zip (\ls -> head ls:ls)
. reverse
where swap rpost [] = ([], rpost)
swap rpost (pivot:rpre) = (reverse rpre, ins . span (<= pivot) $ rpost)
where ins (le,[]) = le ++ [pivot]
ins (le,a:as) = a:le ++ pivot:as
If you want to test an implementation, issue
(sort . permutations $ [1,2,3,4,5]) == (take 120 $ iterate nextPerm [1,2,3,4,5])
If it works, the result will be True
.