HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Next lexicographic permutation of a list

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
permutationlistlexicographicnext

Problem

I began learning Haskell last week. I wrote the following to get the next lexicographic permutation of a list. It works perfectly but I feel like I'm just writing a c# method. My long list of where commands just feels like an imperative approach. I would appreciate any feedback if this solution is indeed the way most people would write something like this in haskell:

-- Finds the end of a list that is monotonically decreasing (swap)
-- Pivot is the index just before it. (pivot)
-- exchanges the smallest value > pivot in swap with pivot
-- reverses the swap with the pivot in it, and concatenates the new permutation
nextPerm :: (Ord a,Num a) => [a] -> [a]
nextPerm [] = []
nextPerm a = if pivotIndex == (-1) then a 
             else prePivot ++ [swapVal] ++ swapWithPivot
             where
                 swap = foldl (\acc x -> if x if x > pivotVal then x else acc) swap
                 swapWithPivot = insert pivotVal $ delete swapVal $ reverse swap

Solution

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 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.

Code Snippets

nextPerm :: (Ord a, Num a) => [a] -> [a]
nextPerm [] = []
nextPerm a
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
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
(sort . permutations $ [1,2,3,4,5]) == (take 120 $ iterate nextPerm [1,2,3,4,5])

Context

StackExchange Code Review Q#159546, answer score: 2

Revisions (0)

No revisions yet.