sdp-0.2: src/SDP/SortM/Tim.hs
{-# LANGUAGE Safe #-}
{- |
Module : SDP.SortM.Tim
Copyright : (c) Andrey Mulik 2019
License : BSD-style
Maintainer : work.a.mulik@gmail.com
Portability : non-portable (requires non-portable modules)
"SDP.SortM.Tim" provides @InsertionSort@ and @TimSort@ algorithms.
-}
module SDP.SortM.Tim
(
-- * TimSort
timSort, timSortBy, timSortOn, minrunTS
)
where
import Prelude ()
import SDP.SafePrelude
import SDP.IndexedM
import SDP.SortM.Insertion
import Data.Bits
default ()
--------------------------------------------------------------------------------
-- | 'timSort' is just synonym for @'timSortBy' 'compare'@.
{-# INLINE timSort #-}
timSort :: (LinearM m v e, BorderedM m v i, Ord e) => v -> m ()
timSort = timSortBy compare
{- |
'timSortOn' is a version of 'timSortBy' that uses a conversion function to
compare elements.
-}
{-# INLINE timSortOn #-}
timSortOn :: (LinearM m v e, BorderedM m v i, Ord o) => (e -> o) -> v -> m ()
timSortOn = timSortBy . comparing
{- |
'timSortBy' is a sorting procedure for mutable random access data structures
using any comparison function and having @O(nlogn)@ complexity in the worst
case.
-}
{-# INLINE timSortBy #-}
timSortBy :: (LinearM m v e, BorderedM m v i) => Compare e -> v -> m ()
timSortBy cmp es = sort' =<< getSizeOf es
where
gt = \ x y -> case cmp x y of {GT -> True; _ -> False}
sort' n
| n < 0 = return ()
| n <= 64 = unsafeInsertionSort cmp es 0 0 (n - 1)
| True = iteratePreN (3 :: Int) 0 >>= go
where
go [sx, sy, sz] = do
nxt <- iteratePreN (1 :: Int) (sx + sy + sz)
if (sx > sy + sz && sy > sz) || sz <= sx
then do merge sx sy sz; go ([sx, sy + sz] ++ nxt)
else do merge 0 sx sy; go ([sx + sy, sz] ++ nxt)
go [sx, sy] = merge 0 sx sy
go _ = return ()
iteratePreN 0 _ = return []
iteratePreN j o = case n - o of
0 -> return []
1 -> return [1]
2 -> do
e0 <- es !#> o
e1 <- es !#> o + 1
when (e0 `gt` e1) $ swapM es o (o + 1)
return [2]
_ -> do
end <- normalized =<< actual
end == 0 ? return [end - o] $ (end - o :) <$> iteratePreN (j - 1) end
where
actual = (es !#> o) >>=<< (es !#> o + 1) $ \ e0 e1 ->
e0 `gt` e1 ? desc e1 (o + 2) $ asc e1 (o + 2)
where
desc p i = do c <- es !#> i; c `gt` p ? rev' o i $ i /= n - 1 ? desc c (i + 1) $ rev' o (i + 1)
asc p i = do c <- es !#> i; p `gt` c ? return i $ i /= n - 1 ? asc c (i + 1) $ return (i + 1)
rev f l = when (f < l) $ do swapM es f l; rev (f + 1) (l - 1)
rev' f l = do rev f (l - 1); return l
normalized s = do
let ex = min n (o + minrunTS n) -- minimal expected ending
when (ex > s) $ unsafeInsertionSort cmp es o (s - 1) (ex - 1)
return (ex `max` s)
merge o sx sy = copied' es o sx >>= mergeGo o 0 (o + sx)
where
mergeGo ic il ir left
| il >= lb = return () -- at least left is empty, merge is completed.
| ir >= rb = copyTo left il es ic (lb - il)
| True = (left !#> il) >>=<< (es !#> ir) $
\ l r -> if r `gt` l
then writeM es ic l >> mergeGo (ic + 1) (il + 1) ir left
else writeM es ic r >> mergeGo (ic + 1) il (ir + 1) left
rb = o + sx + sy
lb = sx
--------------------------------------------------------------------------------
{-# INLINE minrunTS #-}
-- | 'minrunTS' returns @TimSort@ chunk size by given length.
minrunTS :: Int -> Int
minrunTS i = mr i 0 where mr n r = n >= 64 ? mr (shiftR n 1) (n .&. 1) $ n + r