packages feed

smawk-0: src/Data/Smawk.hs

{-# options_ghc -Wno-incomplete-uni-patterns #-}

-- | This module implements a generalized version of the SMAWK algorithm
-- for computing row minima in totally ordered matrices.
--
-- I do not require rows or column numbers to be actual numbers, or even ordered,
-- instead comparing columns using occurrence order.
--
-- Unlike @Map@-based implementations, the runtime of this is actually linear.
module Data.Smawk
  ( smawk
  , smawk1
  ) where

import Control.Monad.Trans.State.Strict
import qualified Data.Foldable as Foldable
import Data.Maybe (fromJust)
import Data.Semigroup (Min(..),Arg(..))
import Data.Semigroup.Foldable (Foldable1)
import Data.List.NonEmpty (nonEmpty)
import Data.Primitive.Array (indexArray)
import GHC.Exts as Exts

-- |
-- >>> collate "abcde"
-- ("ace","bd")
collate :: [a] -> ([a],[a])
collate = Prelude.foldr (\a ~(x,y) -> (a:y,x)) mempty

-- |
-- @'uncurry' 'interleave' . 'collate' = id@
--
-- >>> interleave "ace" "bd"
-- "abcde"
interleave :: [a] -> [a] -> [a]
interleave (a:as) bs = a:interleave bs as
interleave [] bs = bs

-- |
-- /O(|rows| + |cols|)/.
--
-- Computes __row__ minima in totally monotone matrices using the SMAWK algorithm.
--
-- Returns 'Nothing' if we have no columns.
smawk
  :: (Traversable f, Foldable g, Ord a)
  => f r -- ^ rows (in any desired ascending order)
  -> g c -- ^ columns (in any desired ascending order)
  -> (r -> c -> a) -- ^ a monotone matrix
  -> Maybe (f c) -- ^ each of the row minima
smawk rs cs0 m = (\cs -> smawk1 rs cs m) <$> nonEmpty (Foldable.toList cs0)
{-# inline smawk #-}

-- |
-- /O(|rows| + |cols|)/.
--
-- Computes __row__ minima in totally monotone matrices using the SMAWK algorithm.
smawk1
  :: (Traversable f, Foldable1 g, Ord a)
  => f r -- ^ rows (in any desired ascending order)
  -> g c -- ^ columns (in any desired ascending order)
  -> (r -> c -> a) -- ^ a monotone matrix
  -> f c -- ^ each of the row minima
smawk1 rs0 cs0 m = evalState (traverse refill rs0) $ go (Foldable.toList rs0) [0..length raws-1] where
  raws = Exts.fromList $ Foldable.toList cs0
  refill _ = state $ \ ~(x:xs) -> (indexArray raws x,xs)
  go [] _ = []
  go rs cs = interleave broken minima where
    m' r c = m r (indexArray raws c)
    broken = zipWith skim es $ path cs' (minima <> repeat l)
    -- skim a bs = snd $ minimum $ fmap (\i -> (m i a, i)) bs
    skim a bs = case getMin $ fromJust $ foldMap (\i -> Just $ Min $ Arg (m' a i) i) bs of Arg _ i -> i
    path xs ~(y:ys) = case span (<=y) xs of
      (as, bs) -> as:path (y:bs) ys
    rs' = Exts.fromList rs -- a zipper yields same complexity, worst constants
    n = length rs'
    minima = go os cs'
    (es,os) = collate rs
    rcs' = reduce cs [] 0
    cs' = reverse rcs'
    l = head rcs'
    reduce [] ys _ = ys
    reduce (x:xs) [] _ = reduce xs [x] 1
    reduce xxs@(x:xs) yys@(y:ys) t
      | ri <- indexArray rs' (t-1), m' ri y > m' ri y = reduce xxs ys (t-1)
      | t /= n = reduce xs (x:yys) (t+1)
      | otherwise = reduce xs yys t
{-# inlinable smawk1 #-}