on master help sign in
Add commonPrefixes functions, and make lazy strip{Pre,Suf}fix faster
fc065c3Bryan O'Sullivan
Data/Text.hs+32−6
@@ -158,6 +158,7 @@ module Data.Text
158 -- ** View patterns
158 -- ** View patterns
159 , stripPrefix
159 , stripPrefix
160 , stripSuffix
160 , stripSuffix
161+ , commonPrefixes
161
162
162 -- * Searching
163 -- * Searching
163 , filter
164 , filter
@@ -1473,12 +1474,13 @@ isInfixOf needle haystack
1473-------------------------------------------------------------------------------
1474-------------------------------------------------------------------------------
1474-- * View patterns
1475-- * View patterns
1475
1476
1476−-- | /O(n)/ Returns the suffix of the second string if its prefix
1477+-- | /O(n)/ Return the suffix of the second string if its prefix
1477−-- matches the first.
1478+-- matches the entire first string.
1478--
1479--
1479-- Examples:
1480-- Examples:
1480--
1481--
1481-- > stripPrefix "foo" "foobar" == Just "bar"
1482-- > stripPrefix "foo" "foobar" == Just "bar"
1483+-- > stripPrefix "" "baz" == Just "baz"
1482-- > stripPrefix "foo" "quux" == Nothing
1484-- > stripPrefix "foo" "quux" == Nothing
1483--
1485--
1484-- This is particularly useful with the @ViewPatterns@ extension to
1486-- This is particularly useful with the @ViewPatterns@ extension to
@@ -1489,18 +1491,42 @@ isInfixOf needle haystack
1489-- >
1491-- >
1490-- > fnordLength :: Text -> Int
1492-- > fnordLength :: Text -> Int
1491-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
1493-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
1492−-- > fnordLength _ = -1
1494+-- > fnordLength _ = -1
1493stripPrefix :: Text -> Text -> Maybe Text
1495stripPrefix :: Text -> Text -> Maybe Text
1494stripPrefix p@(Text _arr _off plen) t@(Text arr off len)
1496stripPrefix p@(Text _arr _off plen) t@(Text arr off len)
1495 | p `isPrefixOf` t = Just $! textP arr (off+plen) (len-plen)
1497 | p `isPrefixOf` t = Just $! textP arr (off+plen) (len-plen)
1496 | otherwise = Nothing
1498 | otherwise = Nothing
1497
1499
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+
1498−-- | /O(n)/ Returns the prefix of the second string if its suffix
1523+-- | /O(n)/ Return the prefix of the second string if its suffix
1499−-- matches the first.
1524+-- matches the entire first string.
1500--
1525--
1501-- Examples:
1526-- Examples:
1502--
1527--
1503-- > stripSuffix "bar" "foobar" == Just "foo"
1528-- > stripSuffix "bar" "foobar" == Just "foo"
1529+-- > stripSuffix "" "baz" == Just "baz"
1504-- > stripSuffix "foo" "quux" == Nothing
1530-- > stripSuffix "foo" "quux" == Nothing
1505--
1531--
1506-- This is particularly useful with the @ViewPatterns@ extension to
1532-- This is particularly useful with the @ViewPatterns@ extension to
@@ -1511,7 +1537,7 @@ stripPrefix p@(Text _arr _off plen) t@(Text arr off len)
1511-- >
1537-- >
1512-- > quuxLength :: Text -> Int
1538-- > quuxLength :: Text -> Int
1513-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
1539-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
1514−-- > quuxLength _ = -1
1540+-- > quuxLength _ = -1
1515stripSuffix :: Text -> Text -> Maybe Text
1541stripSuffix :: Text -> Text -> Maybe Text
1516stripSuffix p@(Text _arr _off plen) t@(Text arr off len)
1542stripSuffix p@(Text _arr _off plen) t@(Text arr off len)
1517 | p `isSuffixOf` t = Just $! textP arr off (len-plen)
1543 | p `isSuffixOf` t = Just $! textP arr off (len-plen)
Data/Text/Lazy.hs+43−16
@@ -165,6 +165,7 @@ module Data.Text.Lazy
165 -- ** View patterns
165 -- ** View patterns
166 , stripPrefix
166 , stripPrefix
167 , stripSuffix
167 , stripSuffix
168+ , commonPrefixes
168
169
169 -- * Searching
170 -- * Searching
170 , filter
171 , filter
@@ -189,7 +190,7 @@ module Data.Text.Lazy
189import Prelude (Char, Bool(..), Maybe(..), String,
190import Prelude (Char, Bool(..), Maybe(..), String,
190 Eq(..), Ord(..), Ordering(..), Read(..), Show(..),
191 Eq(..), Ord(..), Ordering(..), Read(..), Show(..),
191 (&&), (||), (+), (-), (.), ($), (++),
192 (&&), (||), (+), (-), (.), ($), (++),
192− div, error, flip, fromIntegral, not, otherwise)
193+ div, error, flip, fmap, fromIntegral, not, otherwise)
193import qualified Prelude as P
194import qualified Prelude as P
194#if defined(HAVE_DEEPSEQ)
195#if defined(HAVE_DEEPSEQ)
195import Control.DeepSeq (NFData(..))
196import Control.DeepSeq (NFData(..))
@@ -1325,51 +1326,77 @@ isInfixOf needle haystack
1325-------------------------------------------------------------------------------
1326-------------------------------------------------------------------------------
1326-- * View patterns
1327-- * View patterns
1327
1328
1328−-- | /O(n)/ Returns the suffix of the second string if its prefix
1329+-- | /O(n)/ Return the suffix of the second string if its prefix
1329−-- matches the first.
1330+-- matches the entire first string.
1330--
1331--
1331-- Examples:
1332-- Examples:
1332--
1333--
1333-- > stripPrefix "foo" "foobar" == Just "bar"
1334-- > stripPrefix "foo" "foobar" == Just "bar"
1335+-- > stripPrefix "" "baz" == Just "baz"
1334-- > stripPrefix "foo" "quux" == Nothing
1336-- > stripPrefix "foo" "quux" == Nothing
1335--
1337--
1336-- This is particularly useful with the @ViewPatterns@ extension to
1338-- This is particularly useful with the @ViewPatterns@ extension to
1337-- GHC, as follows:
1339-- GHC, as follows:
1338--
1340--
1339-- > {-# LANGUAGE ViewPatterns #-}
1341-- > {-# LANGUAGE ViewPatterns #-}
1340−-- > import Data.Text as T
1342+-- > import Data.Text.Lazy as T
1341-- >
1343-- >
1342-- > fnordLength :: Text -> Int
1344-- > fnordLength :: Text -> Int
1343-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
1345-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
1344−-- > fnordLength _ = -1
1346+-- > fnordLength _ = -1
1345stripPrefix :: Text -> Text -> Maybe Text
1347stripPrefix :: Text -> Text -> Maybe Text
1346−-- Yes, this could be much more efficient.
1347stripPrefix p t
1348stripPrefix p t
1348− | p `isPrefixOf` t = Just (drop (length p) t)
1349+ | null p = Just t
1350+ | otherwise = case commonPrefixes p t of
1351+ Just (_,c,r) | null c -> Just r
1349− | otherwise = Nothing
1352+ _ -> Nothing
1350
1353
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+
1351−-- | /O(n)/ Returns the prefix of the second string if its suffix
1380+-- | /O(n)/ Return the prefix of the second string if its suffix
1352−-- matches the first.
1381+-- matches the entire first string.
1353--
1382--
1354-- Examples:
1383-- Examples:
1355--
1384--
1356-- > stripSuffix "bar" "foobar" == Just "foo"
1385-- > stripSuffix "bar" "foobar" == Just "foo"
1386+-- > stripSuffix "" "baz" == Just "baz"
1357-- > stripSuffix "foo" "quux" == Nothing
1387-- > stripSuffix "foo" "quux" == Nothing
1358--
1388--
1359-- This is particularly useful with the @ViewPatterns@ extension to
1389-- This is particularly useful with the @ViewPatterns@ extension to
1360-- GHC, as follows:
1390-- GHC, as follows:
1361--
1391--
1362-- > {-# LANGUAGE ViewPatterns #-}
1392-- > {-# LANGUAGE ViewPatterns #-}
1363−-- > import Data.Text as T
1393+-- > import Data.Text.Lazy as T
1364-- >
1394-- >
1365-- > quuxLength :: Text -> Int
1395-- > quuxLength :: Text -> Int
1366-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
1396-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
1367−-- > quuxLength _ = -1
1397+-- > quuxLength _ = -1
1368stripSuffix :: Text -> Text -> Maybe Text
1398stripSuffix :: Text -> Text -> Maybe Text
1369−-- Yes, this could be much more efficient.
1370−stripSuffix p t
1399+stripSuffix p t = reverse `fmap` stripPrefix (reverse p) (reverse t)
1371− | p `isSuffixOf` t = Just (take (length t - length p) t)
1372− | otherwise = Nothing
1373
1400
1374-- | /O(n)/ 'filter', applied to a predicate and a 'Text',
1401-- | /O(n)/ 'filter', applied to a predicate and a 'Text',
1375-- returns a 'Text' containing those characters that satisfy the
1402-- 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)
591t_stripSuffix s = (fmap packS . stripSuffix s) `eqP` T.stripSuffix (packS s)
591t_stripSuffix s = (fmap packS . stripSuffix s) `eqP` T.stripSuffix (packS s)
592tl_stripSuffix s = (fmap packS . stripSuffix s) `eqP` TL.stripSuffix (packS s)
592tl_stripSuffix s = (fmap packS . stripSuffix s) `eqP` TL.stripSuffix (packS s)
593
593
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+
594sf_elem p c = (L.elem c . L.filter p) `eqP` (S.elem c . S.filter p)
614sf_elem p c = (L.elem c . L.filter p) `eqP` (S.elem c . S.filter p)
595sf_filter q p = (L.filter p . L.filter q) `eqP`
615sf_filter q p = (L.filter p . L.filter q) `eqP`
596 (unpackS . S.filter p . S.filter q)
616 (unpackS . S.filter p . S.filter q)
@@ -1120,7 +1140,9 @@ tests = [
1120 testProperty "t_stripPrefix" t_stripPrefix,
1140 testProperty "t_stripPrefix" t_stripPrefix,
1121 testProperty "tl_stripPrefix" tl_stripPrefix,
1141 testProperty "tl_stripPrefix" tl_stripPrefix,
1122 testProperty "t_stripSuffix" t_stripSuffix,
1142 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
1124 ]
1146 ]
1125 ],
1147 ],
1126
1148