on master help sign in
Add lazy break, get rid of lazy splitTimes and splitTimesEnd, and add tests.

--HG-- extra : convert_revision : bf4feefea2f57e8777338a471eba1de81e5ac69b

commit77d1877dcddc9902913e107f5f54cee8ba7b3cfa authorBryan O'Sullivan <bos@serpentine.com> dateWed 7 Oct 2009, 03:39 (17 years ago) parent3a9f06b
3 files changed+53−55
Data/Text/Lazy.hs+41−55 Data/Text/Lazy/Search.hs+2 tests/Properties.hs+10
Data/Text/Lazy.hs+41−55
@@ -118,6 +118,7 @@ module Data.Text.Lazy
118118 , stripEnd
119119 , splitAt
120120 , spanBy
121+ , break
121122 , breakBy
122123 , group
123124 , groupBy
@@ -127,8 +128,6 @@ module Data.Text.Lazy
127128 -- ** Breaking into many substrings
128129 -- $split
129130 , split
130− , splitTimes
131− , splitTimesEnd
132131 , splitBy
133132 , chunksOf
134133 -- , breakSubstring
@@ -166,7 +165,7 @@ module Data.Text.Lazy
166165
167166import Prelude (Char, Bool(..), Int, Maybe(..), String,
168167 Eq(..), Ord(..), Read(..), Show(..),
169− (&&), (||), (+), (-), (.), ($), (++),
168+ (&&), (+), (-), (.), ($), (++),
170169 div, flip, fromIntegral, not, otherwise)
171170import qualified Prelude as P
172171import Data.Int (Int64)
@@ -175,11 +174,13 @@ import Data.Char (isSpace)
175174import Data.Monoid (Monoid(..))
176175import Data.String (IsString(..))
177176import qualified Data.Text as T
177+import qualified Data.Text.Internal as T
178178import qualified Data.Text.Fusion.Common as S
179179import qualified Data.Text.Unsafe as T
180180import qualified Data.Text.Lazy.Fusion as S
181181import Data.Text.Lazy.Fusion (stream, unstream)
182−import Data.Text.Lazy.Internal
182+import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldlChunks, foldrChunks)
183+import Data.Text.Internal (textP)
183184import Data.Text.Lazy.Search (indices)
184185
185186instance Eq Text where
@@ -813,6 +814,42 @@ splitAt = loop
813814 in (Chunk t ts', ts'')
814815 where len = fromIntegral (T.length t)
815816
817+-- | /O(n+m)/ Find the first instance of @needle@ (which must be
818+-- non-'null') in @haystack@. The first element of the returned tuple
819+-- is the prefix of @haystack@ before @needle@ is matched. The second
820+-- is the remainder of @haystack@, starting with the match.
821+--
822+-- Examples:
823+--
824+-- > break "::" "a::b::c" ==> ("a", "::b::c")
825+-- > break "/" "foobar" ==> ("foobar", "")
826+--
827+-- Laws:
828+--
829+-- > append prefix match == haystack
830+-- > where (prefix, match) = break needle haystack
831+--
832+-- If you need to break a string by a substring repeatedly (e.g. you
833+-- want to break on every instance of a substring), use 'find'
834+-- instead, as it has lower startup overhead.
835+--
836+-- In (unlikely) bad cases, this function's time complexity degrades
837+-- towards /O(n*m)/.
838+break :: Text -> Text -> (Text, Text)
839+break pat src
840+ | null pat = emptyError "break"
841+ | otherwise = case indices pat src of
842+ [] -> (src, empty)
843+ (x:_) -> go x src
844+ where
845+ go _ Empty = (empty, empty)
846+ go x (Chunk c@(T.Text arr off len) cs)
847+ | y >= len = let (h,t) = go (x-fromIntegral len) cs
848+ in (Chunk c h, t)
849+ | otherwise = (chunk (textP arr off y) empty,
850+ chunk (textP arr (off+y) (len-y)) cs)
851+ where y = fromIntegral x
852+
816853-- | /O(n)/ 'breakBy' is like 'spanBy', but the prefix returned is over
817854-- elements that fail the predicate @p@.
818855breakBy :: (Char -> Bool) -> Text -> (Text, Text)
@@ -913,57 +950,6 @@ split pat src0
913950 split (singleton c) t = splitBy (==c) t
914951 #-}
915952
916−-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times,
917−-- treating the first 'Text' argument as the delimiter to break on,
918−-- and consuming the delimiter. The last element of the list contains
919−-- the remaining text after the number of times to split has been
920−-- reached. A value of zero or less for @k@ causes no splitting to
921−-- occur. An empty delimiter is invalid, and will cause an error to
922−-- be raised.
923−--
924−-- Examples:
925−--
926−-- > splitTimes 0 "//" "a//b//c" == ["a//b//c"]
927−-- > splitTimes 2 ":" "a:b:c:d:e" == ["a","b","c:d:e"]
928−-- > splitTimes 100 "???" "a????b" == ["a","?b"]
929−--
930−-- and
931−--
932−-- > intercalate s . splitTimes k s == id
933−splitTimes :: Int64 -- ^ Maximum number of times to split
934− -> Text -- ^ Text to split on
935− -> Text -- ^ Input text
936− -> [Text]
937−splitTimes k pat src0
938− | k <= 0 = [src0]
939− | l == 0 = emptyError "splitTimes"
940− | otherwise = go k src0
941− where
942− l = length pat
943− go !i src = search 0 src
944− where
945− search !n !s
946− | i == 0 || null s = [src] -- not found or limit reached
947− | pat `isPrefixOf` s = take n src : go (i-1) (drop l s)
948− | otherwise = search (n+1) (tail s)
949−{-# INLINE splitTimes #-}
950−
951−-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times, like
952−-- 'splitTimes', but start from the end of the input and work towards
953−-- the start.
954−--
955−-- Examples:
956−--
957−-- > splitTimes 2 "::" "a::b::c::d::e" == ["a","b","c::d::e"]
958−-- > splitTimesEnd 2 "::" "a::b::c::d::e" == ["a::b::c","d","e"]
959−splitTimesEnd :: Int64 -- ^ Maximum number of times to split
960− -> Text -- ^ Text to split on
961− -> Text -- ^ Input text
962− -> [Text]
963−splitTimesEnd k pat src =
964− L.reverse . L.map reverse $ splitTimes k (reverse pat) (reverse src)
965−{-# INLINE splitTimesEnd #-}
966−
967953-- | /O(n)/ Splits a 'Text' into components delimited by separators,
968954-- where the predicate returns True for a separator element. The
969955-- resulting components do not contain the separators. Two adjacent
Data/Text/Lazy/Search.hs+2
@@ -13,6 +13,8 @@
1313-- Fast substring search for lazy 'Text', based on work by Boyer,
1414-- Moore, Horspool, Sunday, and Lundh. Adapted from the strict
1515-- implementation.
16+--
17+-- /Note/: this is currently too strict!
1618
1719module Data.Text.Lazy.Search
1820 (
tests/Properties.hs+10
@@ -390,6 +390,13 @@ tl_spanBy p = L.span p `eqP` (unpack2 . TL.spanBy p)
390390t_break_id s = squid `eq` (uncurry T.append . T.break s)
391391 where squid t | T.null s = error "empty"
392392 | otherwise = t
393+tl_break_id s = squid `eq` (uncurry TL.append . TL.break s)
394+ where squid t | TL.null s = error "empty"
395+ | otherwise = t
396+t_break_start (NotEmpty s) t = let (_,m) = T.break s t
397+ in T.null m || s `T.isPrefixOf` m
398+tl_break_start (NotEmpty s) t = let (_,m) = TL.break s t
399+ in TL.null m || s `TL.isPrefixOf` m
393400t_breakBy p = L.break p `eqP` (unpack2 . T.breakBy p)
394401tl_breakBy p = L.break p `eqP` (unpack2 . TL.breakBy p)
395402t_group = L.group `eqP` (map unpackS . T.group)
@@ -770,6 +777,9 @@ tests = [
770777 testProperty "t_spanBy" t_spanBy,
771778 testProperty "tl_spanBy" tl_spanBy,
772779 testProperty "t_break_id" t_break_id,
780+ testProperty "tl_break_id" tl_break_id,
781+ testProperty "t_break_start" t_break_start,
782+ testProperty "tl_break_start" tl_break_start,
773783 testProperty "t_breakBy" t_breakBy,
774784 testProperty "tl_breakBy" tl_breakBy,
775785 testProperty "t_group" t_group,