grouped-list 0.1.2.0 → 0.1.3.0
raw patch · 5 files changed
+182/−8 lines, 5 filesdep +QuickCheckdep +tastydep +tasty-quickcheckdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: QuickCheck, tasty, tasty-quickcheck
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Data.GroupedList: drop :: Int -> Grouped a -> Grouped a
+ Data.GroupedList: instance GHC.Classes.Eq a => GHC.Exts.IsList (Data.GroupedList.Grouped a)
+ Data.GroupedList: take :: Int -> Grouped a -> Grouped a
Files
- CHANGELOG.md +5/−1
- Data/GroupedList.hs +54/−6
- grouped-list.cabal +18/−1
- test/examples.hs +59/−0
- test/properties.hs +46/−0
CHANGELOG.md view
@@ -1,4 +1,8 @@ +# 0.1.2.0++* Addition of ``adjustM``.+ # 0.1.1.0 -* Addition of 'empty'.+* Addition of ``empty``.
Data/GroupedList.hs view
@@ -1,11 +1,12 @@ -{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TupleSections, TypeFamilies #-} -- | Grouped lists are like lists, but internally they are represented -- as groups of consecutive elements. -- -- For example, the list @[1,2,2,3,4,5,5,5]@ would be internally--- represented as @[[1],[2,2],[3],[4],[5,5,5]]@.+-- represented as @[[1],[2,2],[3],[4],[5,5,5]]@. Use 'groupedGroups'+-- to see this. -- module Data.GroupedList ( -- * Type@@ -13,6 +14,7 @@ -- * Builders , empty , point+ -- | Use 'point' to create a 'Grouped' list with a single element. , concatMap , replicate , fromGroup@@ -20,6 +22,9 @@ , index , adjust , adjustM+ -- * Sublists+ , take+ , drop -- * Mapping , map -- * Traversal@@ -32,6 +37,7 @@ -- * Sorting , sort -- * List conversion+ -- | For to-list conversion use 'toList'. , fromList -- * Groups , Group@@ -41,7 +47,9 @@ ) where import Prelude hiding- (concat, concatMap, replicate, filter, map)+ ( concat, concatMap, replicate, filter, map+ , take, drop+ ) import qualified Prelude as Prelude import Data.Pointed import Data.Foldable (toList, fold, foldrM)@@ -53,6 +61,7 @@ import Control.Arrow (second) import qualified Data.Map.Strict as M import Data.Functor.Identity (Identity (..))+import qualified GHC.Exts as GHC ------------------------------------------------------------------ ------------------------------------------------------------------@@ -124,10 +133,18 @@ empty :: Grouped a empty = Grouped S.empty +-- | Method 'fromList' doesn't work for infinite lists.+-- A grouped list cannot be infinite.+instance Eq a => GHC.IsList (Grouped a) where+ type (Item (Grouped a)) = a+ fromList = Grouped . S.fromList . fmap (\g -> Group (length g) $ head g) . group+ toList = toList+ -- | Build a grouped list from a regular list. It doesn't work if--- the input list is infinite.+-- the input list is infinite. This is just a specialized version+-- of 'GHC.fromList'. fromList :: Eq a => [a] -> Grouped a-fromList = Grouped . S.fromList . fmap (\g -> Group (length g) $ head g) . group+fromList = GHC.fromList -- | Build a grouped list from a group (see 'Group'). fromGroup :: Group a -> Grouped a@@ -226,7 +243,7 @@ then (fromGroup g <> gtrue,gfalse) else (gtrue,fromGroup g <> gfalse) --- | Filter a grouped list by keeping only those that match a given condition.+-- | Filter a grouped list by keeping only those elements that match a given condition. filter :: Eq a => (a -> Bool) -> Grouped a -> Grouped a filter f = fst . partition f @@ -251,6 +268,7 @@ adjust :: Eq a => (a -> a) -> Int -> Grouped a -> Grouped a adjust f i g = runIdentity $ adjustM (Identity . f) i g +-- | Just like 'adjust', but the function returns in a 'Monad'. adjustM :: (Monad m, Eq a) => (a -> m a) -> Int -> Grouped a -> m (Grouped a) adjustM f k g@(Grouped gs) = if k < 0 then pure g else Grouped <$> go 0 k gs where@@ -331,6 +349,36 @@ -- Note: n < i ==> 0 < i - n _ | otherwise -> go (npre+1) (i-n) xs _ -> pure S.empty++------------------------------------------------------------------+------------------------------------------------------------------+-- Sublists++-- | Take the given number of elements from the left end of the+-- list.+take :: Int -> Grouped a -> Grouped a+take n (Grouped gs) = Grouped $ if n <= 0 then S.empty else go 0 n gs+ where+ go npre k xs =+ case S.viewl xs of+ Group q x S.:< ys ->+ if k <= q+ then S.take npre gs S.|> Group k x+ else go (npre+1) (k-q) ys -- k - q > 0+ _ -> gs++-- | Discard the given number of elements from the left end of+-- the list.+drop :: Int -> Grouped a -> Grouped a+drop n g@(Grouped gs) = if n <= 0 then g else Grouped $ go n gs+ where+ go k xs =+ case S.viewl xs of+ Group q x S.:< ys ->+ if k < q+ then Group (q - k) x S.<| ys+ else go (k - q) ys+ _ -> S.empty ------------------------------------------------------------------ ------------------------------------------------------------------
grouped-list.cabal view
@@ -1,5 +1,5 @@ name: grouped-list-version: 0.1.2.0+version: 0.1.3.0 synopsis: Grouped lists. Equal consecutive elements are grouped. description: Grouped lists work like regular lists, except for two conditions:@@ -47,3 +47,20 @@ source-repository head type: git location: https://github.com/Daniel-Diaz/grouped-list.git++test-suite grouped-list-examples+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: examples.hs+ build-depends: base >= 4.8, grouped-list++test-suite grouped-list-properties+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: properties.hs+ ghc-options: -Wall+ build-depends:+ base >= 4.8, grouped-list+ , QuickCheck, tasty, tasty-quickcheck
+ test/examples.hs view
@@ -0,0 +1,59 @@++{-# LANGUAGE OverloadedLists #-}++module Main (main) where++import System.Exit (exitFailure)+import Data.GroupedList (Grouped)+import qualified Data.GroupedList as GL+import Control.Monad (unless)++(=:) :: (Show a, Eq a) => a -> a -> IO ()+x =: y = unless (x == y) $ do+ print x+ putStrLn " /= "+ print y+ exitFailure++main :: IO ()+main = do+ (GL.empty :: Grouped ())+ =: []+ (GL.point 0 :: Grouped Int)+ =: [0]+ GL.replicate 3 0+ =: [0,0,0]+ GL.index [1,2,3] 1+ =: Just 2+ GL.index [1,2,3] 3+ =: Nothing+ GL.adjust (+1) 1 [1,2,2]+ =: [1,3,2]+ GL.adjust (+1) 3 [1,2,3]+ =: [1,2,3]+ GL.take 2 [1,1,1,2]+ =: [1,1]+ GL.take 2 [1,2,2,2]+ =: [1,2]+ GL.take 2 [1,2,3,4]+ =: [1,2]+ GL.drop 2 [1,1,1,2]+ =: [1,2]+ GL.drop 2 [1,2,2,2]+ =: [2,2]+ GL.drop 2 [1,2,3,4]+ =: [3,4] + GL.map (+1) [1,2,3,4]+ =: [2,3,4,5]+ GL.map (+1) [1,1,2,3]+ =: [2,2,3,4]+ GL.map (const 0) [1,2,2,3]+ =: [0,0,0,0]+ GL.partition even [1,2,3,4]+ =: ([2,4],[1,3])+ GL.partition even [1,2,1,2]+ =: ([2,2],[1,1])+ GL.sort [1,2,1,2]+ =: [1,1,2,2]+ GL.sort [3,2,1,3]+ =: [1,2,3,3]
+ test/properties.hs view
@@ -0,0 +1,46 @@++module Main (main) where++import Data.GroupedList (Grouped)+import qualified Data.GroupedList as GL+import Data.Monoid ((<>))+import Data.Foldable (toList)++import Test.Tasty+import qualified Test.Tasty.QuickCheck as QC+import Test.QuickCheck (Arbitrary (..), Testable, choose)++-- | A short alias for 'QC.testProperty'.+prop :: Testable a => TestName -> a -> TestTree+prop = QC.testProperty++newtype G = G (Grouped Int)++instance Show G where+ show (G g) = show g++instance Arbitrary G where+ arbitrary = G . GL.fromList <$> arbitrary++main :: IO ()+main = defaultMain $ testGroup "Properties"+ [ prop "left mempty" $ \(G xs) -> GL.empty <> xs == xs+ , prop "right mempty" $ \(G xs) -> xs <> GL.empty == xs+ , prop "mappend" $ \(G xs) (G ys) (G zs) -> (xs <> ys) <> zs == xs <> (ys <> zs)+ , prop "index" $ \(G xs) ->+ let n = length xs+ in (\i -> + let r = GL.index xs i+ in if i <= n - 1+ then r == Just (toList xs !! i)+ else r == Nothing+ ) <$> choose (0,2*n)+ , prop "split and concat" $ \(G xs) ->+ let n = length xs+ in (\i -> GL.take i xs <> GL.drop i xs == xs) <$> choose (0,n)+ , prop "map id" $ \(G xs) -> GL.map id xs == xs+ , prop "map (+1) . map (+1) = map (+2)" $+ \(G xs) -> GL.map (+1) (GL.map (+1) xs) == GL.map (+2) xs+ , prop "'map (const i)' generates at most one group" $+ \(G xs) i -> length (GL.groupedGroups $ GL.map (const i) xs :: [GL.Group Int]) <= 1+ ]