[Haskell-cafe] Interesting folds over bytestring lists?
Justin Bailey
jgbailey at gmail.com
Wed Sep 19 19:30:29 EDT 2007
I have a data structure which is a list of bytestrings, but externally
it looks like one big string. One of the operations I want to support
takes a section of the string, starting at some arbitrary index and
ending somewhere further down the line. In implementing the function I
came up with the two functions below, dropTo and takeTo.
In my mind, dropTo moves over the list of bytestrings until it reaches
the starting point, and then returns the rest. takeTo, in contrast,
scans over the list until it has seen enough bytes to return the
amount requested.
In both cases I am trying to share structure as much as possible in
order to avoid unnecessary copying and space leaks.
I thought these two functions were interested and am looking for
feedback, comments, improvements, etc. Thanks!
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Base as B
import Test.QuickCheck
import Data.List (foldl')
import Data.Word
dropTo :: Int -> [L.ByteString] -> [L.ByteString]
dropTo _ [] = []
dropTo amt strs =
let dropTo' :: (Int, [L.ByteString]) -> L.ByteString -> (Int, [L.ByteString])
dropTo' (rem, acc) ss
| rem == 0 = (0, acc)
| otherwise =
let chunks = L.toChunks ss
in
case foldl' dropStricts (rem, chunks) chunks of
(!n, rest)
| null rest -> (n, drop 1 acc)
| otherwise -> (0, L.fromChunks rest : drop 1 acc)
dropStricts :: (Int, [S.ByteString]) -> S.ByteString -> (Int,
[S.ByteString])
dropStricts (rem, acc) str
| rem == 0 = (0, acc)
| rem - S.length str == 0 = (0, drop 1 acc)
| rem - S.length str < 0 = (0, S.drop rem str : (drop 1 acc))
| otherwise = (rem - S.length str, drop 1 acc)
(_, rest) = foldl' dropTo' (amt, strs) strs
in
rest
takeTo :: Int -> [L.ByteString] -> [L.ByteString]
takeTo _ [] = []
takeTo amt strs =
let countLazies :: (Int, Int, L.ByteString) -> L.ByteString -> (Int,
Int, L.ByteString)
countLazies (rem, !total, lazyLeftover) ss
| rem == 0 = (0, total, lazyLeftover)
| otherwise =
let chunks = L.toChunks ss
in
case foldl' countStricts (rem, 0, S.empty) chunks of
(!n, amt, strictLeftover)
| S.null strictLeftover -> (n, total + 1, L.empty)
| otherwise -> (n, total, L.fromChunks (take amt
chunks ++ [strictLeftover]))
countStricts :: (Int, Int, S.ByteString) -> S.ByteString ->
(Int, Int, S.ByteString)
countStricts (rem, !total, leftover) str
| rem == 0 = (0, total, leftover)
| rem - S.length str == 0 = (0, total + 1, S.empty)
| rem - S.length str < 0 = (0, total, S.take rem str)
| otherwise = (rem - S.length str, total + 1, S.empty)
in
case foldl' countLazies (amt, 0, L.empty) strs of
(_, total, leftover)
| L.null leftover -> take total strs
| otherwise -> take total strs ++ [leftover]
prop_dropToNonEmpty :: [[Word8]] -> Int -> Property
prop_dropToNonEmpty strs amt =
amt >= 0 && (all (not . null) strs) ==>
all (not . L.null) (dropTo amt (map (L.pack) strs))
prop_dropToCorrect :: [[Word8]] -> Int -> Property
prop_dropToCorrect strs amt =
let lazyStr = L.drop amt64 (toLazyBS strs)
amt64 = fromIntegral amt
in
amt >= 0 && (all (not . null) strs) ==>
(L.concat (dropTo amt (map (L.pack) strs))) == lazyStr
prop_takeToNonEmpty :: [[Word8]] -> Int -> Property
prop_takeToNonEmpty strs amt =
amt >= 0 && (all (not . null) strs) ==>
all (not . L.null) (takeTo amt (map (L.pack) strs))
prop_takeToCorrect :: [[Word8]] -> Int -> Property
prop_takeToCorrect strs amt =
let lazyStr = L.take amt64 (toLazyBS strs)
amt64 = fromIntegral amt
in
amt >= 0 && (all (not . null) strs) ==>
(L.concat (takeTo amt (map (L.pack) strs))) == lazyStr
-- Functions and instances for testing purposes
toLazyBS :: [[Word8]] -> L.ByteString
toLazyBS = L.concat . map L.pack
instance Arbitrary Word8 where
arbitrary = elements [minBound .. maxBound]
coarbitrary = undefined
main =
do
putStrLn "prop_takeToNonEmpty"
quickCheck prop_takeToNonEmpty
putStrLn "prop_takeToCorrect"
quickCheck prop_takeToCorrect
putStrLn "prop_dropToCorrect"
quickCheck prop_dropToCorrect
putStrLn "prop_dropToNonEmpty"
quickCheck prop_dropToNonEmpty
More information about the Haskell-Cafe
mailing list