ac-library-hs-1.5.0.0: src/AtCoder/String.hs
-- | It contains string algorithms.
--
-- Let \(s\) be a string. We denote the substring of \(s\) between the \(a\)-th and \(b - 1\)-th
-- character by \(s[a..b)\).
--
-- ==== __Examples__
--
-- ===== Suffix Array and LCP Array
--
-- >>> import AtCoder.String qualified as S
-- >>> import Data.ByteString.Char8 qualified as BS
-- >>> let s = BS.pack "aab"
-- >>> let sa = S.suffixArrayBS s
-- >>> S.lcpArrayBS s sa
-- [1,0]
--
-- ===== Z Algorithm
--
-- >>> import AtCoder.String qualified as S
-- >>> import Data.ByteString.Char8 qualified as BS
-- >>> let s = BS.pack "abab"
-- >>> S.zAlgorithmBS s
-- [4,0,2,0]
--
-- @since 1.0.0.0
module AtCoder.String
( -- * Suffix array
suffixArray,
suffixArrayBS,
suffixArrayOrd,
-- * LCP array
lcpArray,
lcpArrayBS,
-- * Z algorithm
zAlgorithm,
zAlgorithmBS,
)
where
import AtCoder.Internal.Assert qualified as ACIA
import AtCoder.Internal.String qualified as ACIS
import Control.Monad.ST (runST)
import Data.ByteString.Char8 qualified as BS
import Data.Char (ord)
import Data.Vector.Algorithms.Intro qualified as VAI
import Data.Vector.Generic qualified as VG
import Data.Vector.Generic.Mutable qualified as VGM
import Data.Vector.Unboxed qualified as VU
import Data.Vector.Unboxed.Mutable qualified as VUM
import GHC.Stack (HasCallStack)
-- TODO: document `chr`
-- | Calculates suffix array for a `Int` vector.
--
-- Given a string \(s\) of length \(n\), it returns the suffix array of \(s\). Here, the suffix array
-- \(\mathrm{sa}\) of \(s\) is a permutation of \(0, \cdots, n-1\) such that \(s[\mathrm{sa}[i]..n) < s[\mathrm{sa}[i+1]..n)\)
-- holds for all \(i = 0,1, \cdots ,n-2\).
--
-- ==== Constraints
-- - \(0 \leq n\)
-- - \(0 \leq \mathrm{upper} \leq 10^8\)
-- - \(0 \leq x \leq \mathrm{upper}\) for all elements \(x\) of \(s\).
--
-- ==== Complexity
-- - \(O(n + \mathrm{upper})\)-time
--
-- @since 1.0.0.0
{-# INLINE suffixArray #-}
suffixArray :: (HasCallStack) => VU.Vector Int -> Int -> VU.Vector Int
suffixArray s upper =
let !_ = ACIA.runtimeAssert (0 <= upper) $ "AtCoder.String.suffixArray: given negative `upper`: " ++ show upper
!_ = ACIA.runtimeAssert (VU.all (\d -> 0 <= d && d <= upper) s) "AtCoder.String.suffixArray: some input out of bounds"
in ACIS.saIs s upper
-- | Calculates suffix array for a @ByteString@.
--
-- ==== Constraints
-- - \(0 \leq n\)
--
-- ==== Complexity
-- - \(O(n)\)-time
--
-- @since 1.0.0.0
{-# INLINE suffixArrayBS #-}
suffixArrayBS :: (HasCallStack) => BS.ByteString -> VU.Vector Int
suffixArrayBS s = do
let n = BS.length s
s2 = VU.map ord $ VU.fromListN n (BS.unpack s)
in ACIS.saIs s2 255
-- | Calculates suffix array for a `Ord` type vector.
--
-- ==== Constraints
-- - \(0 \leq n\)
--
-- ==== Complexity
-- - \(O(n \log n)\)-time
-- - \(O(n)\)-space
--
-- @since 1.0.0.0
{-# INLINEABLE suffixArrayOrd #-}
suffixArrayOrd :: (HasCallStack, Ord a, VU.Unbox a) => VU.Vector a -> VU.Vector Int
suffixArrayOrd s =
let n = VU.length s
(!upper, !s2) = runST $ do
let f i j = compare (s VG.! i) (s VG.! j)
-- modify + generate should fuse
let idx = VU.modify (VAI.sortBy f) $ VU.generate n id
vec <- VUM.unsafeNew n
upper_ <-
VU.foldM'
( \now i -> do
let now' =
if i > 0 && s VG.! (idx VG.! (i - 1)) /= s VG.! (idx VG.! i)
then now + 1
else now
VGM.write vec (idx VG.! i) now'
pure now'
)
(0 :: Int)
(VU.generate n id)
(upper_,) <$> VU.unsafeFreeze vec
in ACIS.saIs s2 upper
-- | Given a string \(s\) of length \(n\), it returns the LCP array of \(s\). Here, the LCP array of
-- \(s\) is the array of length \(n-1\), such that the \(i\)-th element is the length of the LCP
-- (Longest Common Prefix) of \(s[\mathrm{sa}[i]..n)\) and \(s[\mathrm{sa}[i+1]..n)\).
--
-- ==== Constraints
-- - The second argument is the suffix array of \(s\).
-- - \(1 \leq n\)
--
-- ==== Complexity
-- - \(O(n)\)
--
-- @since 1.0.0.0
{-# INLINEABLE lcpArray #-}
lcpArray ::
(HasCallStack, Ord a, VU.Unbox a) =>
-- | A vector representing a string
VU.Vector a ->
-- | Suffix array
VU.Vector Int ->
-- | LCP array
VU.Vector Int
lcpArray s sa =
let n = VU.length s
!_ = ACIA.runtimeAssert (n >= 1) "AtCoder.String.lcpArray: given empty input"
rnk = VU.create $ do
rnkVec <- VUM.unsafeNew @_ @Int n
VU.iforM_ sa $ \i saI -> do
VGM.write rnkVec saI i
pure rnkVec
in VU.create $ do
lcp <- VUM.unsafeNew (n - 1)
VU.ifoldM'_
( \ !h_ i rnkI -> do
let h = if h_ > 0 then h_ - 1 else h_
if rnkI == 0
then pure h
else do
let j = sa VG.! (rnkI - 1)
let inner !h'
| not $ j + h' < n && i + h' < n = h'
| s VG.! (j + h') /= s VG.! (i + h') = h'
| otherwise = inner $ h' + 1
let !h' = inner h
VGM.write lcp (rnkI - 1) h'
pure h'
)
(0 :: Int)
rnk
pure lcp
-- | @ByteString@ variant of `lcpArray`.
--
-- ==== Constraints
-- - The second argument is the suffix array of \(s\).
-- - \(1 \leq n\)
--
-- ==== Complexity
-- - \(O(n)\)
--
-- @since 1.0.0.0
{-# INLINE lcpArrayBS #-}
lcpArrayBS ::
(HasCallStack) =>
-- | String
BS.ByteString ->
-- | Suffix array
VU.Vector Int ->
-- | LCP array
VU.Vector Int
lcpArrayBS s sa =
let n = BS.length s
s2 = VU.map ord . VU.fromListN n $ BS.unpack s
in lcpArray s2 sa
-- | Given a `Ord` vector of length \(n\), it returns the array of length \(n\), such that the
-- \(i\)-th element is the length of the LCP (Longest Common Prefix) of \(s[0..n)\) and \(s[i..n)\).
--
-- ==== Constraints
-- - \(n \leq n\)
--
-- ==== Complexity
-- - \(O(n)\)
--
-- @since 1.0.0.0
{-# INLINEABLE zAlgorithm #-}
zAlgorithm :: (Ord a, VU.Unbox a) => VU.Vector a -> VU.Vector Int
zAlgorithm s
| n == 0 = VU.empty
| otherwise = VU.create $ do
z <- VUM.unsafeNew @_ @Int n
VGM.write z 0 0
VU.foldM'_
( \j i -> do
zj <- VGM.read z j
k0 <-
if j + zj <= i
then pure 0
else do
zij <- VGM.read z (i - j)
pure $ min (j + zj - i) zij
let loop k
| i + k < n && s VG.! k == s VG.! (i + k) = loop (k + 1)
| otherwise = k
let k = loop k0
VGM.write z i k
pure $
if j + zj < i + k
then i
else j
)
(0 :: Int)
(VU.generate (n - 1) (+ 1))
VGM.write z 0 n
pure z
where
n = VU.length s
-- | Given a string of length \(n\), it returns the array of length \(n\), such that the \(i\)-th
-- element is the length of the LCP (Longest Common Prefix) of \(s[0..n)\) and \(s[i..n)\).
--
-- ==== Constraints
-- - \(n \leq n\)
--
-- ==== Complexity
-- - \(O(n)\)
--
-- @since 1.0.0.0
{-# INLINE zAlgorithmBS #-}
zAlgorithmBS :: BS.ByteString -> VU.Vector Int
zAlgorithmBS s = zAlgorithm $ VU.fromListN (BS.length s) (BS.unpack s)