-- | Permutations. See:
--   Donald E. Knuth: The Art of Computer Programming, vol 4, pre-fascicle 2B.
--
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Math.Combinat.Permutations where

import Data.List
import Data.Array

import Math.Combinat.Helper

-------------------------------------------------------
{-
-- * Types

-- | Standard notation for permutations
newtype Permutation = Permutation (Array Int Int) deriving (Eq,Ord,Show,Read)

-- | Disjoint cycle notation for permutations
newtype DisjCycles  = DisjCycles [[Int]] deriving (Eq,Ord,Show,Read)
-}

-------------------------------------------------------
-- * Permutations of distinct elements

-- | Permutations of [1..n] in lexicographic order, naive algorithm.
_permutations :: Int -> [[Int]]  
_permutations 0 = [[]]
_permutations 1 = [[1]]
_permutations n = helper [1..n] where
  helper [] = [[]]
  helper xs = [ i : ys | i <- xs , ys <- helper (xs `minus` i) ]
  minus [] _ = []
  minus (x:xs) i = if x < i then x : minus xs i else xs

{-
permutations :: Int -> [Permutation]
permutations n = map toPermutationUnsafe $ _permutations n 
-}

-- | # = n!
countPermutations :: Int -> Integer
countPermutations = factorial

-------------------------------------------------------
-- * Permutations of a multiset

-- | Generates all permutations of a multiset. 
--   The order is lexicographic.  
permute :: (Eq a, Ord a) => [a] -> [[a]] 
permute = fasc2B_algorithm_L

-- | # = \\frac { (\sum_i n_i) ! } { \\prod_i (n_i !) }    
countPermute :: (Eq a, Ord a) => [a] -> Integer
countPermute xs = factorial n `div` product [ factorial (length z) | z <- group ys ] 
  where
    ys = sort xs
    n = length xs
  
-- | Generates all permutations of a multiset 
--   (based on \"algorithm L\" in Knuth; somewhat less efficient). 
--   The order is lexicographic.  
fasc2B_algorithm_L :: (Eq a, Ord a) => [a] -> [[a]] 
fasc2B_algorithm_L xs = unfold1 next (sort xs) where
  -- next :: [a] -> Maybe [a]
  next xs = case findj (reverse xs,[]) of 
    Nothing -> Nothing
    Just ( (l:ls) , rs) -> Just $ inc l ls (reverse rs,[]) 
    Just ( [] , _ ) -> error "permute: should not happen"

  -- we use simple list zippers: (left,right)
  -- findj :: ([a],[a]) -> Maybe ([a],[a])   
  findj ( xxs@(x:xs) , yys@(y:_) ) = if x >= y 
    then findj ( xs , x : yys )
    else Just ( xxs , yys )
  findj ( x:xs , [] ) = findj ( xs , [x] )  
  findj ( [] , _ ) = Nothing
  
  -- inc :: a -> [a] -> ([a],[a]) -> [a]
  inc u us ( (x:xs) , yys ) = if u >= x
    then inc u us ( xs , x : yys ) 
    else reverse (x:us)  ++ reverse (u:yys) ++ xs
  inc _ _ ( [] , _ ) = error "permute: should not happen"
      
-------------------------------------------------------