group-with 0.2.0.2 → 0.2.0.3
raw patch · 5 files changed
+79/−69 lines, 5 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- Control/GroupWith.hs +36/−31
- GroupWithTest.hs +31/−28
- README.md +3/−2
- changelog +2/−2
- group-with.cabal +7/−6
Control/GroupWith.hs view
@@ -7,23 +7,23 @@ -- Maintainer : ukoehler@techoverflow.net -- Stability : provisional -- Portability : portable--- +-- -- A collection of grouping utility functions. -- For a given function that assigns a key to objects, -- provides functions that group said objects into a multimap -- by said key.--- +-- -- This can be used similarly to the SQL GROUP BY statement.--- +-- -- Provides a more flexible approach to GHC.Exts.groupWith--- +-- -- > groupWith (take 1) ["a","ab","bc"] == Map.fromList [("a",["a","ab"]), ("b",["bc"])] -- -- In order to use monadic / applicative functions as key generators, -- use the A- or M-postfixed variants like 'groupWithA' or 'groupWithMultipleM' ----- --- +--+-- ----------------------------------------------------------------------------- module Control.GroupWith( MultiMap,@@ -50,8 +50,8 @@ -- for each generated key the values (from the given list) -- that yielded said key by applying the function on said value. groupWith :: (Ord b) =>- (a -> b) -- ^ The function used to map a list value to its key- -> [a] -- ^ The list to be grouped+ (a -> b) -- ^ The function used to map a list value to its key+ -> [a] -- ^ The list to be grouped -> MultiMap b a -- ^ The resulting key --> value multimap groupWith f xs = Map.fromListWith (++) [(f x, [x]) | x <- xs] @@ -60,30 +60,33 @@ -- The corresponding value from the original list will be placed -- in the identifier-corresponding map entry for each generated -- identifier.--- Note that values are added to the +-- Note that values are added to the groupWithMultiple :: (Ord b) =>- (a -> [b]) -- ^ The function used to map a list value to its keys- -> [a] -- ^ The list to be grouped+ (a -> [b]) -- ^ The function used to map a list value to+ -- its keys+ -> [a] -- ^ The list to be grouped -> MultiMap b a -- ^ The resulting map-groupWithMultiple f xs = +groupWithMultiple f xs = let identifiers x = [(val, [x]) | val <- vals] where vals = f x in Map.fromListWith (++) $ concat [identifiers x | x <- xs] -- | Like groupWith, but uses a custom combinator function groupWithUsing :: (Ord b) =>- (a -> c) -- ^ Transformer function used to map a value to the resulting type+ (a -> c) -- ^ Transformer function used to map a value to the+ -- resulting type -> (c -> c -> c) -- ^ The combinator used to combine an existing value -- for a given key with a new value- -> (a -> b) -- ^ The function used to map a list value to its key- -> [a] -- ^ The list to be grouped- -> Map b c -- ^ The resulting key --> transformed value map+ -> (a -> b) -- ^ The function used to map a list value to its key+ -> [a] -- ^ The list to be grouped+ -> Map b c -- ^ The resulting key --> transformed value map groupWithUsing t c f xs = Map.fromListWith c $ map (\v -> (f v, t v)) xs -- | Fuse the functor from a tuple fuseT2 :: Applicative f => (f a, f b) -> f (a,b) fuseT2 = uncurry $ liftA2 (,) --- | Like 'fuseT2', but only requires the first element to be boxed in the functor+-- | Like 'fuseT2', but only requires the first element to be boxed in the+-- functor fuseFirst :: Applicative f => (f a, b) -> f (a,b) fuseFirst = fuseT2 . second pure @@ -96,41 +99,43 @@ -- | Group values in a list by a key, generated by a given applicative function. -- Applicative version of 'groupWith'. See 'groupWith' for documentation. groupWithA :: (Ord b, Applicative f) =>- (a -> f b) -- ^ The function used to map a list value to its key- -> [a] -- ^ The list to be grouped- -> f (MultiMap b a) -- ^ The resulting key --> value multimap+ (a -> f b) -- ^ The function used to map a list value to its key+ -> [a] -- ^ The list to be grouped+ -> f (MultiMap b a) -- ^ The resulting key --> value multimap groupWithA f xs = Map.fromListWith (++) <$> fuseFirstList [(f x, [x]) | x <- xs] -- | Alias for 'groupWithA', with additional monad constraint groupWithM :: (Ord b, Monad m, Applicative m) =>- (a -> m b) -- ^ The function used to map a list value to its key- -> [a] -- ^ The list to be grouped- -> m (MultiMap b a) -- ^ The resulting key --> value multimap+ (a -> m b) -- ^ The function used to map a list value to its key+ -> [a] -- ^ The list to be grouped+ -> m (MultiMap b a) -- ^ The resulting key --> value multimap groupWithM = groupWithA -- | Like 'groupWithM', but the identifier-generating function -- may generate multiple keys for each value (or none at all). -- See 'groupWithMultiple' for further behavioural details.--- +-- -- Note that it's impossible to define this for applicatives: -- See http://stackoverflow.com/a/6032260/2597135 groupWithMultipleM :: (Ord b, Monad m, Applicative m) =>- (a -> m [b]) -- ^ The function used to map a list value to its keys- -> [a] -- ^ The list to be grouped+ (a -> m [b]) -- ^ The function used to map a list value+ -- to its keys+ -> [a] -- ^ The list to be grouped -> m (MultiMap b a) -- ^ The resulting map-groupWithMultipleM f xs = +groupWithMultipleM f xs = let identifiers x = (\vals -> [(val, [x]) | val <- vals]) <$> f x idMap = concat <$> (mapM identifiers xs) in Map.fromListWith (++) <$> idMap -- | Like 'groupWithM', but uses a custom combinator function groupWithUsingM :: (Ord b, Monad m, Applicative m) =>- (a -> m c) -- ^ Transformer function used to map a value to the resulting type+ (a -> m c) -- ^ Transformer function used to map a value to the+ -- resulting type -> (c -> c -> c) -- ^ The combinator used to combine an existing value -- for a given key with a new value- -> (a -> m b) -- ^ The function used to map a list value to its key- -> [a] -- ^ The list to be grouped- -> m (Map b c) -- ^ The resulting key --> transformed value map+ -> (a -> m b) -- ^ The function used to map a list value to its key+ -> [a] -- ^ The list to be grouped+ -> m (Map b c) -- ^ The resulting key --> transformed value map groupWithUsingM t c f xs = Map.fromListWith c <$> mapM (\v -> fuseT2 (f v, t v)) xs
GroupWithTest.hs view
@@ -25,19 +25,22 @@ multimapTupleEq :: (Ord a, Ord b, Eq a, Eq b) => (Map a [b], Map a [b]) -> Bool multimapTupleEq (x,y) = multimapEq x y +never_called_fn :: a+never_called_fn = error "This function should never be called"+ main :: IO () main = hspec $ do describe "groupWith" $ do it "should group a simple value correctly" $- let data_ = ["a","abc","ab","bc"]- fn = take 1- ref = Map.fromList [("a",["a","abc","ab"]),("b",["bc"])]+ let data_ = ["a","abc","ab","bc"]+ fn = take 1+ ref = Map.fromList [("a",["a","abc","ab"]),("b",["bc"])] result = groupWith fn data_ in (result, ref) `shouldSatisfy` multimapTupleEq it "should return an empty map when given an empty list" $ -- We need to specialize here, because it's ⊥- let fn = error "This function should never be called" :: Int -> Int- data_ = [] :: [Int]+ let fn = never_called_fn :: Int -> Int+ data_ = [] :: [Int] result = groupWith fn data_ in result `shouldSatisfy` ((==) 0 . Map.size) -- Fuzzing, couldn't get this to compile properly yet@@ -46,23 +49,23 @@ describe "groupWithUsing" $ do it "should return an empty map when given an empty list" $ -- We need to specialize here, because it's ⊥- let fn = error "This function should never be called" :: Int -> Int- data_ = [] :: [Int]+ let fn = never_called_fn :: Int -> Int+ data_ = [] :: [Int] result = groupWithUsing id (+) fn data_ in result `shouldSatisfy` ((==) 0 . Map.size) it "should be usable for counting" $ -- Instead of building up lists, we count the number of elements- let t n = 1 -- For each x, count 1- c = (+) -- Sum up the counts- fn = take 1+ let t n = 1 -- For each x, count 1+ c = (+) -- Sum up the counts+ fn = take 1 data_ = ["a","abc","ab","bc"]- ref = Map.fromList [("a",3),("b",1)] :: Map String Int+ ref = Map.fromList [("a",3),("b",1)] :: Map String Int result = groupWithUsing t c fn data_ in result `shouldBe` ref describe "groupWithMultiple" $ do it "should group correctly given a simple list" $ let data_ = ["a","abc","ab","bc"]- fn x = [take 1 x, take 2 x]+ fn x = [take 1 x, take 2 x] -- Note the multiple "a"s in the first line: -- one from `take 1`, one from `take 2` ref = Map.fromList [("a",["a","a","abc","ab"]),@@ -73,8 +76,8 @@ in (result, ref) `shouldSatisfy` multimapTupleEq it "should return an empty map when given an empty list" $ -- We need to specialize here, because it's ⊥- let fn = error "This function should never be called" :: Int -> [Int]- data_ = [] :: [Int]+ let fn = never_called_fn :: Int -> [Int]+ data_ = [] :: [Int] result = groupWithMultiple fn data_ in result `shouldSatisfy` ((==) 0 . Map.size) {-@@ -82,15 +85,15 @@ -} describe "groupWithM" $ do it "should group a simple value correctly" $- let data_ = ["a","abc","ab","bc"]- fn = Just . take 1- ref = Map.fromList [("a",["a","abc","ab"]),("b",["bc"])]+ let data_ = ["a","abc","ab","bc"]+ fn = Just . take 1+ ref = Map.fromList [("a",["a","abc","ab"]),("b",["bc"])] result = groupWithM fn data_ in (fromJust result, ref) `shouldSatisfy` multimapTupleEq it "should return an empty map when given an empty list" $ -- We need to specialize here, because it's ⊥- let fn = error "This function should never be called" :: Int -> Maybe Int- data_ = [] :: [Int]+ let fn = never_called_fn :: Int -> Maybe Int+ data_ = [] :: [Int] result = groupWithM fn data_ in (fromJust result) `shouldSatisfy` ((==) 0 . Map.size) -- Fuzzing, couldn't get this to compile properly yet@@ -110,23 +113,23 @@ in (fromJust result, ref) `shouldSatisfy` multimapTupleEq it "should return an empty map when given an empty list" $ -- We need to specialize here, because it's ⊥- let fn = error "This function should never be called" :: Int -> Maybe [Int]- data_ = [] :: [Int]+ let fn = never_called_fn :: Int -> Maybe [Int]+ data_ = [] :: [Int] result = groupWithMultipleM fn data_ in (fromJust result) `shouldSatisfy` ((==) 0 . Map.size) describe "groupWithUsingM" $ do it "should return an empty map when given an empty list" $ -- We need to specialize here, because it's ⊥- let fn = error "This function should never be called" :: Int -> Maybe Int- data_ = [] :: [Int]+ let fn = never_called_fn :: Int -> Maybe Int+ data_ = [] :: [Int] result = groupWithUsingM return (+) fn data_ in (fromJust result) `shouldSatisfy` ((==) 0 . Map.size) it "should be usable for counting" $ -- Instead of building up lists, we count the number of elements- let t n = return 1 -- For each x, count 1- c = (+) -- Sum up the counts- fn = return . take 1- data_ = ["a","abc","ab","bc"]- ref = Map.fromList [("a",3),("b",1)] :: Map String Int+ let t n = return 1 -- For each x, count 1+ c = (+) -- Sum up the counts+ fn = return . take 1+ data_ = ["a","abc","ab","bc"]+ ref = Map.fromList [("a",3),("b",1)] :: Map String Int result = groupWithUsingM t c fn data_ in fromJust result `shouldBe` ref
README.md view
@@ -1,4 +1,5 @@ group-with-========+========== -A Haskell library to classify objects by a function value, just like SQL GROUP BY+A Haskell library to classify objects by a function value, just like SQL GROUP+BY.
changelog view
@@ -1,12 +1,12 @@ ** 0.2.x 0.2.0.0 --> 0.2.0.2-===============+=================== * Fix dependency issue: Can't build with containers < 0.5.0.0 0.1.0.0 --> 0.2.0.0-===============+=================== * Support monadic and applicative (where possible) grouping
group-with.cabal view
@@ -1,8 +1,9 @@ name: group-with-version: 0.2.0.2-synopsis: Classify objects by key-generating function, like SQL GROUP BY-description: A library to classify objects into multimaps by using a function generating- keys for any object in the list.+version: 0.2.0.3+synopsis: Classify objects by key-generating function,+ like SQL GROUP BY+description: A library to classify objects into multimaps by using a+ function generating keys for any object in the list. This allows an approach similar to SQL GROUP BY. @@ -12,7 +13,7 @@ license-file: LICENSE author: Uli Köhler maintainer: ukoehler@techoverflow.net--- copyright: +-- copyright: category: Data build-type: Simple extra-source-files: README.md changelog@@ -26,7 +27,7 @@ exposed-modules: Control.GroupWith -- other-modules: -- other-extensions:- build-depends: base >= 4.2 && < 4.8, containers >= 0.5+ build-depends: base >= 4.2 && < 4.9, containers >= 0.5 -- hs-source-dirs: default-language: Haskell2010