on master help sign in
Add commonPrefixes functions, and make lazy strip{Pre,Suf}fix faster

The old implemenations of stripPrefix and stripSuffix were placeholders with terrible performance, but Sebastiaan Visser asked for them to be improved ... so they are.

commitfc065c31ef27aee59e7374802adaf60339b2ddd0 authorBryan O'Sullivan <bos@serpentine.com> dateFri 3 Dec 2010, 16:30 (16 years ago) parent3f884e1
3 files changed+98−23
Data/Text.hs+32−6 Data/Text/Lazy.hs+43−16 tests/Properties.hs+23−1
Data/Text.hs+32−6
@@ -158,6 +158,7 @@ module Data.Text
158158 -- ** View patterns
159159 , stripPrefix
160160 , stripSuffix
161+ , commonPrefixes
161162
162163 -- * Searching
163164 , filter
@@ -1473,12 +1474,13 @@ isInfixOf needle haystack
14731474-------------------------------------------------------------------------------
14741475-- * View patterns
14751476
1476−-- | /O(n)/ Returns the suffix of the second string if its prefix
1477−-- matches the first.
1477+-- | /O(n)/ Return the suffix of the second string if its prefix
1478+-- matches the entire first string.
14781479--
14791480-- Examples:
14801481--
14811482-- > stripPrefix "foo" "foobar" == Just "bar"
1483+-- > stripPrefix "" "baz" == Just "baz"
14821484-- > stripPrefix "foo" "quux" == Nothing
14831485--
14841486-- This is particularly useful with the @ViewPatterns@ extension to
@@ -1489,18 +1491,42 @@ isInfixOf needle haystack
14891491-- >
14901492-- > fnordLength :: Text -> Int
14911493-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
1492−-- > fnordLength _ = -1
1494+-- > fnordLength _ = -1
14931495stripPrefix :: Text -> Text -> Maybe Text
14941496stripPrefix p@(Text _arr _off plen) t@(Text arr off len)
14951497 | p `isPrefixOf` t = Just $! textP arr (off+plen) (len-plen)
14961498 | otherwise = Nothing
14971499
1498−-- | /O(n)/ Returns the prefix of the second string if its suffix
1499−-- matches the first.
1500+-- | /O(n)/ Find the longest non-empty common prefix of two strings
1501+-- and return it, along with the suffixes of each string at which they
1502+-- no longer match.
1503+--
1504+-- If the strings do not have a common prefix or either one is empty,
1505+-- this function returns 'Nothing'.
1506+--
1507+-- Examples:
1508+--
1509+-- > commonPrefixes "foobar" "fooquux" == Just ("foo","bar","quux")
1510+-- > commonPrefixes "veeble" "fetzer" == Nothing
1511+-- > commonPrefixes "" "baz" == Nothing
1512+commonPrefixes :: Text -> Text -> Maybe (Text,Text,Text)
1513+commonPrefixes t0@(Text arr0 off0 len0) t1@(Text arr1 off1 len1) = go 0 0
1514+ where
1515+ go !i !j | i < len0 && j < len1 && a == b = go (i+d0) (j+d1)
1516+ | i > 0 = Just (Text arr0 off0 i,
1517+ textP arr0 (off0+i) (len0-i),
1518+ textP arr1 (off1+j) (len1-j))
1519+ | otherwise = Nothing
1520+ where Iter a d0 = iter t0 i
1521+ Iter b d1 = iter t1 j
1522+
1523+-- | /O(n)/ Return the prefix of the second string if its suffix
1524+-- matches the entire first string.
15001525--
15011526-- Examples:
15021527--
15031528-- > stripSuffix "bar" "foobar" == Just "foo"
1529+-- > stripSuffix "" "baz" == Just "baz"
15041530-- > stripSuffix "foo" "quux" == Nothing
15051531--
15061532-- This is particularly useful with the @ViewPatterns@ extension to
@@ -1511,7 +1537,7 @@ stripPrefix p@(Text _arr _off plen) t@(Text arr off len)
15111537-- >
15121538-- > quuxLength :: Text -> Int
15131539-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
1514−-- > quuxLength _ = -1
1540+-- > quuxLength _ = -1
15151541stripSuffix :: Text -> Text -> Maybe Text
15161542stripSuffix p@(Text _arr _off plen) t@(Text arr off len)
15171543 | p `isSuffixOf` t = Just $! textP arr off (len-plen)
Data/Text/Lazy.hs+43−16
@@ -165,6 +165,7 @@ module Data.Text.Lazy
165165 -- ** View patterns
166166 , stripPrefix
167167 , stripSuffix
168+ , commonPrefixes
168169
169170 -- * Searching
170171 , filter
@@ -189,7 +190,7 @@ module Data.Text.Lazy
189190import Prelude (Char, Bool(..), Maybe(..), String,
190191 Eq(..), Ord(..), Ordering(..), Read(..), Show(..),
191192 (&&), (||), (+), (-), (.), ($), (++),
192− div, error, flip, fromIntegral, not, otherwise)
193+ div, error, flip, fmap, fromIntegral, not, otherwise)
193194import qualified Prelude as P
194195#if defined(HAVE_DEEPSEQ)
195196import Control.DeepSeq (NFData(..))
@@ -1325,51 +1326,77 @@ isInfixOf needle haystack
13251326-------------------------------------------------------------------------------
13261327-- * View patterns
13271328
1328−-- | /O(n)/ Returns the suffix of the second string if its prefix
1329−-- matches the first.
1329+-- | /O(n)/ Return the suffix of the second string if its prefix
1330+-- matches the entire first string.
13301331--
13311332-- Examples:
13321333--
13331334-- > stripPrefix "foo" "foobar" == Just "bar"
1335+-- > stripPrefix "" "baz" == Just "baz"
13341336-- > stripPrefix "foo" "quux" == Nothing
13351337--
13361338-- This is particularly useful with the @ViewPatterns@ extension to
13371339-- GHC, as follows:
13381340--
13391341-- > {-# LANGUAGE ViewPatterns #-}
1340−-- > import Data.Text as T
1342+-- > import Data.Text.Lazy as T
13411343-- >
13421344-- > fnordLength :: Text -> Int
13431345-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
1344−-- > fnordLength _ = -1
1346+-- > fnordLength _ = -1
13451347stripPrefix :: Text -> Text -> Maybe Text
1346−-- Yes, this could be much more efficient.
13471348stripPrefix p t
1348− | p `isPrefixOf` t = Just (drop (length p) t)
1349− | otherwise = Nothing
1349+ | null p = Just t
1350+ | otherwise = case commonPrefixes p t of
1351+ Just (_,c,r) | null c -> Just r
1352+ _ -> Nothing
13501353
1351−-- | /O(n)/ Returns the prefix of the second string if its suffix
1352−-- matches the first.
1354+-- | /O(n)/ Find the longest non-empty common prefix of two strings
1355+-- and return it, along with the suffixes of each string at which they
1356+-- no longer match.
1357+--
1358+-- If the strings do not have a common prefix or either one is empty,
1359+-- this function returns 'Nothing'.
1360+--
1361+-- Examples:
1362+--
1363+-- > commonPrefixes "foobar" "fooquux" == Just ("foo","bar","quux")
1364+-- > commonPrefixes "veeble" "fetzer" == Nothing
1365+-- > commonPrefixes "" "baz" == Nothing
1366+commonPrefixes :: Text -> Text -> Maybe (Text,Text,Text)
1367+commonPrefixes Empty _ = Nothing
1368+commonPrefixes _ Empty = Nothing
1369+commonPrefixes a0 b0 = Just (go a0 b0 [])
1370+ where
1371+ go t0@(Chunk x xs) t1@(Chunk y ys) ps
1372+ = case T.commonPrefixes x y of
1373+ Just (p,a,b)
1374+ | T.null a -> go xs (chunk b ys) (p:ps)
1375+ | T.null b -> go (chunk a xs) ys (p:ps)
1376+ | otherwise -> (fromChunks (L.reverse (p:ps)),chunk a xs, chunk b ys)
1377+ Nothing -> (fromChunks (L.reverse ps),t0,t1)
1378+ go t0 t1 ps = (fromChunks (L.reverse ps),t0,t1)
1379+
1380+-- | /O(n)/ Return the prefix of the second string if its suffix
1381+-- matches the entire first string.
13531382--
13541383-- Examples:
13551384--
13561385-- > stripSuffix "bar" "foobar" == Just "foo"
1386+-- > stripSuffix "" "baz" == Just "baz"
13571387-- > stripSuffix "foo" "quux" == Nothing
13581388--
13591389-- This is particularly useful with the @ViewPatterns@ extension to
13601390-- GHC, as follows:
13611391--
13621392-- > {-# LANGUAGE ViewPatterns #-}
1363−-- > import Data.Text as T
1393+-- > import Data.Text.Lazy as T
13641394-- >
13651395-- > quuxLength :: Text -> Int
13661396-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
1367−-- > quuxLength _ = -1
1397+-- > quuxLength _ = -1
13681398stripSuffix :: Text -> Text -> Maybe Text
1369−-- Yes, this could be much more efficient.
1370−stripSuffix p t
1371− | p `isSuffixOf` t = Just (take (length t - length p) t)
1372− | otherwise = Nothing
1399+stripSuffix p t = reverse `fmap` stripPrefix (reverse p) (reverse t)
13731400
13741401-- | /O(n)/ 'filter', applied to a predicate and a 'Text',
13751402-- returns a 'Text' containing those characters that satisfy the
tests/Properties.hs+23−1
@@ -591,6 +591,26 @@ stripSuffix p t = reverse `fmap` L.stripPrefix (reverse p) (reverse t)
591591t_stripSuffix s = (fmap packS . stripSuffix s) `eqP` T.stripSuffix (packS s)
592592tl_stripSuffix s = (fmap packS . stripSuffix s) `eqP` TL.stripSuffix (packS s)
593593
594+commonPrefixes a0@(_:_) b0@(_:_) = Just (go a0 b0 [])
595+ where go (a:as) (b:bs) ps
596+ | a == b = go as bs (a:ps)
597+ go as bs ps = (reverse ps,as,bs)
598+commonPrefixes _ _ = Nothing
599+
600+t_commonPrefixes a b (NonEmpty p)
601+ = commonPrefixes pa pb ==
602+ repack `fmap` T.commonPrefixes (packS pa) (packS pb)
603+ where repack (x,y,z) = (unpackS x,unpackS y,unpackS z)
604+ pa = p ++ a
605+ pb = p ++ b
606+
607+tl_commonPrefixes a b (NonEmpty p)
608+ = commonPrefixes pa pb ==
609+ repack `fmap` TL.commonPrefixes (packS pa) (packS pb)
610+ where repack (x,y,z) = (unpackS x,unpackS y,unpackS z)
611+ pa = p ++ a
612+ pb = p ++ b
613+
594614sf_elem p c = (L.elem c . L.filter p) `eqP` (S.elem c . S.filter p)
595615sf_filter q p = (L.filter p . L.filter q) `eqP`
596616 (unpackS . S.filter p . S.filter q)
@@ -1120,7 +1140,9 @@ tests = [
11201140 testProperty "t_stripPrefix" t_stripPrefix,
11211141 testProperty "tl_stripPrefix" tl_stripPrefix,
11221142 testProperty "t_stripSuffix" t_stripSuffix,
1123− testProperty "tl_stripSuffix" tl_stripSuffix
1143+ testProperty "tl_stripSuffix" tl_stripSuffix,
1144+ testProperty "t_commonPrefixes" t_commonPrefixes,
1145+ testProperty "tl_commonPrefixes" tl_commonPrefixes
11241146 ]
11251147 ],
11261148