Z-Data-1.0.0.0: Z/Data/Text/Search.hs
{-|
Module : Z.Data.Text.Search
Description : Searching text
Copyright : (c) Dong Han, 2017-2018
License : BSD
Maintainer : winterland1989@gmail.com
Stability : experimental
Portability : non-portable
-}
module Z.Data.Text.Search (
-- * element-wise search
elem, notElem
-- * Searching by equality
, findIndices
, findBytesIndices
, find, findR
, findIndex
, findIndexR
, findBytesIndex
, findBytesIndexR
, filter, partition
) where
import Control.Monad.ST
import Data.Word
import Prelude hiding (elem, notElem, filter)
import Z.Data.Array
import Z.Data.Text.Base
import Z.Data.Text.UTF8Codec
import qualified Z.Data.Vector.Base as V
-- | find all char index matching the predicate.
findIndices :: (Char -> Bool) -> Text -> [Int]
{-# INLINABLE findIndices #-}
findIndices f (Text (V.PrimVector arr s l)) = go 0 s
where
!end = s + l
go !i !p | p >= end = []
| f x = i : go (i+1) (p+off)
| otherwise = go (i+1) (p+off)
where (# x, off #) = decodeChar arr p
-- | find all char's byte index matching the predicate.
findBytesIndices :: (Char -> Bool) -> Text -> [Int]
{-# INLINABLE findBytesIndices #-}
findBytesIndices f (Text (V.PrimVector arr s l)) = go s
where
!end = s + l
go !p | p >= end = []
| f x = (p-s) : go (p+off)
| otherwise = go (p+off)
where (# x, off #) = decodeChar arr p
-- | /O(n)/ find the first char matching the predicate in a text
-- from left to right, if there isn't one, return the text length.
find :: (Char -> Bool)
-> Text
-> (Int, Maybe Char) -- ^ (char index, matching char)
{-# INLINABLE find #-}
find f (Text (V.PrimVector arr s l)) = go 0 s
where
!end = s + l
go !i !j | j >= end = (i, Nothing)
| otherwise =
let (# x, off #) = decodeChar arr j
in if f x
then (i, Just x)
else go (i+1) (j+off)
-- | /O(n)/ find the first char matching the predicate in a text
-- from right to left.
--
findR :: (Char -> Bool)
-> Text
-> (Int, Maybe Char) -- ^ (char index(counting backwards), matching char)
{-# INLINABLE findR #-}
findR f (Text (V.PrimVector arr s l)) = go 0 (s+l-1)
where
go !i !j | j < s = (i, Nothing)
| otherwise =
let (# x, off #) = decodeCharReverse arr j
in if f x
then (i, Just x)
else go (i+1) (j-off)
--------------------------------------------------------------------------------
-- | /O(n)/ find the char index.
findIndex :: (Char -> Bool) -> Text -> Int
{-# INLINABLE findIndex #-}
findIndex f t = case find f t of (i, _) -> i
-- | /O(n)/ find the char index in reverse order.
findIndexR :: (Char -> Bool) -> Text -> Int
{-# INLINABLE findIndexR #-}
findIndexR f t = case findR f t of (i, _) -> i
-- | /O(n)/ find the char's byte slice index.
findBytesIndex :: (Char -> Bool) -> Text -> Int
{-# INLINABLE findBytesIndex #-}
findBytesIndex f (Text (V.PrimVector arr s l)) = go s
where
!end = s + l
go !j | j >= end = j-s
| otherwise =
let (# x, off #) = decodeChar arr j
in if f x
then j-s
else go (j+off)
-- | /O(n)/ find the char's byte slice index in reverse order(pointing to the right char's first byte).
findBytesIndexR :: (Char -> Bool) -> Text -> Int
{-# INLINABLE findBytesIndexR #-}
findBytesIndexR f (Text (V.PrimVector arr s l)) = go (s+l-1)
where
go !j | j < s = j-s+1
| otherwise =
let (# x, off #) = decodeCharReverse arr j
in if f x
then j-s+1
else go (j-off)
-- | /O(n)/ 'filter', applied to a predicate and a text,
-- returns a text containing those chars that satisfy the
-- predicate.
filter :: (Char -> Bool) -> Text -> Text
{-# INLINABLE filter #-}
filter f (Text (V.PrimVector arr s l)) = Text (V.createN l (go s 0))
where
!end = s + l
go :: Int -> Int -> MutablePrimArray s Word8 -> ST s Int
go !i !j marr
| i >= end = return j
| otherwise =
let (# x, off #) = decodeChar arr i
in if f x
then do
copyChar off marr j arr i
go (i+off) (j+off) marr
else go (i+off) j marr
-- | /O(n)/ The 'partition' function takes a predicate, a text, returns
-- a pair of text with codepoints which do and do not satisfy the
-- predicate, respectively; i.e.,
--
-- > partition p txt == (filter p txt, filter (not . p) txt)
partition :: (Char -> Bool) -> Text -> (Text, Text)
{-# INLINABLE partition #-}
partition f (Text (V.PrimVector arr s l))
| l == 0 = (empty, empty)
| otherwise = let !(bs1, bs2) = V.createN2 l l (go 0 0 s) in (Text bs1, Text bs2)
where
!end = s + l
go :: Int -> Int -> Int -> MutablePrimArray s Word8 -> MutablePrimArray s Word8 -> ST s (Int, Int)
go !i !j !p !mba0 !mba1
| p >= end = return (i, j)
| otherwise =
let (# x, off #) = decodeChar arr p
in if f x
then copyChar off mba0 i arr p >> go (i+off) j (p+off) mba0 mba1
else copyChar off mba1 j arr p >> go i (j+off) (p+off) mba0 mba1
--------------------------------------------------------------------------------
-- Searching by equality
-- | /O(n)/ 'elem' test if given char is in given text.
elem :: Char -> Text -> Bool
{-# INLINABLE elem #-}
elem x t = case find (x==) t of (_,Nothing) -> False
_ -> True
-- | /O(n)/ @not . elem@
notElem :: Char -> Text -> Bool
{-# INLINABLE notElem #-}
notElem x = not . elem x