module MergeSort ( msortBy ) where infixl 7 :% infixr 6 :& data LenList a = LL {-# UNPACK #-} !Int Bool [a] data LenListAnd a b = {-# UNPACK #-} !(LenList a) :% b data Stack a = End | {-# UNPACK #-} !(LenList a) :& (Stack a) msortBy :: (a -> a -> Ordering) -> [a] -> [a] msortBy cmp = mergeSplit End where splitAsc n _ _ _ | n `seq` False = undefined splitAsc n as _ [] = LL n True as :% [] splitAsc n as a bs@(b:bs') = case cmp a b of GT -> LL n False as :% bs _ -> splitAsc (n + 1) as b bs' splitDesc n _ _ _ | n `seq` False = undefined splitDesc n rs a [] = LL n True (a:rs) :% [] splitDesc n rs a bs@(b:bs') = case cmp a b of GT -> splitDesc (n + 1) (a:rs) b bs' _ -> LL n True (a:rs) :% bs mergeLL (LL na fa as) (LL nb fb bs) = LL (na + nb) True $ mergeLs na as nb bs where mergeLs nx _ ny _ | nx `seq` ny `seq` False = undefined mergeLs 0 _ ny ys = if fb then ys else take ny ys mergeLs _ [] ny ys = if fb then ys else take ny ys mergeLs nx xs 0 _ = if fa then xs else take nx xs mergeLs nx xs _ [] = if fa then xs else take nx xs mergeLs nx xs@(x:xs') ny ys@(y:ys') = case cmp x y of GT -> y:mergeLs nx xs (ny - 1) ys' _ -> x:mergeLs (nx - 1) xs' ny ys push ssx px@(LL nx _ _) = case ssx of End -> px :% ssx py@(LL ny _ _) :& ssy -> case ssy of End | nx >= ny -> mergeLL py px :% ssy pz@(LL nz _ _) :& ssz | nx >= ny || nx + ny >= nz -> case nx > nz of False -> push ssy $ mergeLL py px _ -> case push ssz $ mergeLL pz py of pz' :% ssz' -> push (pz' :& ssz') px _ -> px :% ssx mergeAll _ px | px `seq` False = undefined mergeAll ssx px@(LL nx _ xs) = case ssx of End -> xs py@(LL _ _ _) :& ssy -> case ssy of End -> case mergeLL py px of LL _ _ xys -> xys pz@(LL nz _ _) :& ssz -> case nx > nz of False -> mergeAll ssy $ mergeLL py px _ -> case push ssz $ mergeLL pz py of pz' :% ssz' -> mergeAll (pz' :& ssz') px mergeSplit ss _ | ss `seq` False = undefined mergeSplit ss [] = case ss of End -> [] px :& ss' -> mergeAll ss' px mergeSplit ss as@(a:as') = case as' of [] -> mergeAll ss $ LL 1 True as b:bs -> case cmp a b of GT -> case splitDesc 2 [a] b bs of px :% rs -> case push ss px of px' :% ss' -> mergeSplit (px' :& ss') rs _ -> case splitAsc 2 as b bs of px :% rs -> case push ss px of px' :% ss' -> mergeSplit (px' :& ss') rs {-# INLINABLE mergeSplit #-}