@@ -214,6 +214,7 @@ module Streamly.Internal.Data.Fold
214 -- in individual output buckets using the given fold.
214 -- in individual output buckets using the given fold.
215 , classify
215 , classify
216 , classifyWith
216 , classifyWith
217 , classifyScanWith
218 , classifyScanWith
218 -- , classifyWithSel
219 -- , classifyWithSel
219 -- , classifyWithMin
220 -- , classifyWithMin
@@ -260,14 +261,17 @@ where
261#include "inline.hs"
262#include "inline.hs"
264+import Control.Monad.IO.Class (MonadIO(..))
263import Data.Bifunctor (first)
265import Data.Bifunctor (first)
264import Data.Either (isLeft, isRight, fromLeft, fromRight)
266import Data.Either (isLeft, isRight, fromLeft, fromRight)
265import Data.Functor.Identity (Identity(..))
267import Data.Functor.Identity (Identity(..))
266import Data.Int (Int64)
268import Data.Int (Int64)
269+import Data.IORef (newIORef, readIORef, writeIORef)
267import Data.Map.Strict (Map)
270import Data.Map.Strict (Map)
268import Data.Maybe (isJust, fromJust)
271import Data.Maybe (isJust, fromJust)
269import Streamly.Internal.Data.Either.Strict
272import Streamly.Internal.Data.Either.Strict
270 (Either'(..), fromLeft', fromRight', isLeft', isRight')
273 (Either'(..), fromLeft', fromRight', isLeft', isRight')
274+import Streamly.Internal.Data.IsMap (IsMap(..))
271import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
275import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
272import Streamly.Internal.Data.Unfold.Type (Unfold(..))
276import Streamly.Internal.Data.Unfold.Type (Unfold(..))
273import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
277import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
@@ -276,6 +280,7 @@ import Streamly.Internal.Data.Stream.Serial (SerialT(..))
276import qualified Data.IntSet as IntSet
280import qualified Data.IntSet as IntSet
277import qualified Data.Map.Strict as Map
281import qualified Data.Map.Strict as Map
278import qualified Data.Set as Set
282import qualified Data.Set as Set
283+import qualified Streamly.Internal.Data.IsMap as IsMap
279import qualified Streamly.Internal.Data.Pipe.Type as Pipe
284import qualified Streamly.Internal.Data.Pipe.Type as Pipe
280import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD
285import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD
281-- import qualified Streamly.Internal.Data.Stream.IsStream.Enumeration as Stream
286-- import qualified Streamly.Internal.Data.Stream.IsStream.Enumeration as Stream
@@ -1625,19 +1630,23 @@ demuxDefault :: (Monad m, Ord k)
1625 => Map k (Fold m a b) -> Fold m (k, a) b -> Fold m (k, a) (Map k b, b)
1630 => Map k (Fold m a b) -> Fold m (k, a) b -> Fold m (k, a) (Map k b, b)
1626demuxDefault = demuxDefaultWith id
1631demuxDefault = demuxDefaultWith id
1633+-- | Folds the values for each key using the supplied fold. When scanning, as
1634+-- soon as the fold is complete, its result is available in the second
1635+-- component of the tuple. The first component of the tuple is a snapshot of
1636+-- the in-progress folds.
1628{-# INLINE classifyScanWith #-}
1637{-# INLINE classifyScanWith #-}
1629−classifyScanWith :: (Monad m, Ord k) =>
1638+classifyScanWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
1630 -- Note: we need to return the Map itself to display the in-progress values
1639 -- Note: we need to return the Map itself to display the in-progress values
1631 -- e.g. to implement top. We could possibly create a separate abstraction
1640 -- e.g. to implement top. We could possibly create a separate abstraction
1632 -- for that use case. We return an action because we want it to be lazy so
1641 -- for that use case. We return an action because we want it to be lazy so
1633 -- that the downstream consumers can choose to process or discard it.
1642 -- that the downstream consumers can choose to process or discard it.
1634− (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))
1643+ (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
1635classifyScanWith f (Fold step1 initial1 extract1) =
1644classifyScanWith f (Fold step1 initial1 extract1) =
1636 fmap extract $ foldlM' step initial
1645 fmap extract $ foldlM' step initial
1640− initial = return $ Tuple3' Map.empty Set.empty Nothing
1649+ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing
1642 {-# INLINE initFold #-}
1651 {-# INLINE initFold #-}
1643 initFold kv set k a = do
1652 initFold kv set k a = do
@@ -1648,14 +1657,14 @@ classifyScanWith f (Fold step1 initial1 extract1) =
1649 $ case r of
1658 $ case r of
1650 Partial s1 ->
1659 Partial s1 ->
1651− Tuple3' (Map.insert k s1 kv) set Nothing
1660+ Tuple3' (IsMap.mapInsert k s1 kv) set Nothing
1652 Done b ->
1661 Done b ->
1653 Tuple3' kv set (Just (k, b))
1662 Tuple3' kv set (Just (k, b))
1654 Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
1663 Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
1656 step (Tuple3' kv set _) a = do
1665 step (Tuple3' kv set _) a = do
1657 let k = f a
1666 let k = f a
1658− case Map.lookup k kv of
1667+ case IsMap.mapLookup k kv of
1659 Nothing -> do
1668 Nothing -> do
1660 if Set.member k set
1669 if Set.member k set
1661 then return (Tuple3' kv set Nothing)
1670 then return (Tuple3' kv set Nothing)
@@ -1665,16 +1674,69 @@ classifyScanWith f (Fold step1 initial1 extract1) =
1666 $ case r of
1675 $ case r of
1667 Partial s1 ->
1676 Partial s1 ->
1668− Tuple3' (Map.insert k s1 kv) set Nothing
1677+ Tuple3' (IsMap.mapInsert k s1 kv) set Nothing
1669 Done b ->
1678 Done b ->
1670− let kv1 = Map.delete k kv
1679+ let kv1 = IsMap.mapDelete k kv
1671 in Tuple3' kv1 (Set.insert k set) (Just (k, b))
1680 in Tuple3' kv1 (Set.insert k set) (Just (k, b))
1673 extract (Tuple3' kv _ x) = (Prelude.mapM extract1 kv, x)
1682 extract (Tuple3' kv _ x) = (Prelude.mapM extract1 kv, x)
1684+-- The code is almost the same as classifyScanWith except the IORef operations.
1686+-- | Same as classifyScanWith except that it uses mutable IORef cells in the
1687+-- Map providing better performance. Be aware that if this is used as a scan,
1688+-- the values in the intermediate Maps would be mutable.
1689+{-# INLINE classifyScanMutWith #-}
1690+classifyScanMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
1691+ (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
1692+classifyScanMutWith f (Fold step1 initial1 extract1) =
1693+ fmap extract $ foldlM' step initial
1697+ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing
1699+ {-# INLINE initFold #-}
1700+ initFold kv set k a = do
1707+ ref <- liftIO $ newIORef s1
1708+ return $ Tuple3' (IsMap.mapInsert k ref kv) set Nothing
1710+ return $ Tuple3' kv set (Just (k, b))
1711+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
1713+ step (Tuple3' kv set _) a = do
1715+ case IsMap.mapLookup k kv of
1717+ if Set.member k set
1718+ then return (Tuple3' kv set Nothing)
1719+ else initFold kv set k a
1721+ s <- liftIO $ readIORef ref
1725+ liftIO $ writeIORef ref s1
1726+ return $ Tuple3' kv set Nothing
1728+ let kv1 = IsMap.mapDelete k kv
1730+ $ Tuple3' kv1 (Set.insert k set) (Just (k, b))
1732+ extract (Tuple3' kv _ x) =
1733+ (Prelude.mapM (\ref -> liftIO (readIORef ref) >>= extract1) kv, x)
1735+-- | Fold a key value stream to a key-value container. If the same key appears
1736+-- multiple times, only the last value is retained.
1675{-# INLINE toMap #-}
1737{-# INLINE toMap #-}
1676−toMap :: (Monad m, Ord k) => Fold m (k, a) (Map k a)
1738+toMap :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)
1677−toMap = foldl' (\kv (k, v) -> Map.insert k v kv) Map.empty
1739+toMap = foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
1679-- | Split the input stream based on a key field and fold each split using the
1741-- | Split the input stream based on a key field and fold each split using the
1680-- given fold. Useful for map/reduce, bucketizing the input in different bins
1742-- given fold. Useful for map/reduce, bucketizing the input in different bins
@@ -1711,6 +1773,22 @@ classifyWith f fld =
1711 (lmap snd $ catMaybes toMap)
1773 (lmap snd $ catMaybes toMap)
1712 in postscan classifier aggregator
1774 in postscan classifier aggregator
1776+-- | Like classifyWith but maybe faster because it uses mutable cells as fold
1777+-- accumulators in the Map.
1778+{-# INLINE classifyMutWith #-}
1779+classifyMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
1780+ (a -> Key f) -> Fold m a b -> Fold m a (f b)
1781+classifyMutWith f fld =
1783+ classifier = classifyScanMutWith f fld
1784+ getMap Nothing = pure IsMap.mapEmpty
1785+ getMap (Just action) = action
1787+ teeWith IsMap.mapUnion
1788+ (rmapM getMap $ lmap fst last)
1789+ (lmap snd $ catMaybes toMap)
1790+ in postscan classifier aggregator
1714-- | Given an input stream of key value pairs and a fold for values, fold all
1792-- | Given an input stream of key value pairs and a fold for values, fold all
1715-- the values belonging to each key. Useful for map/reduce, bucketizing the
1793-- the values belonging to each key. Useful for map/reduce, bucketizing the
1716-- input in different bins or for generating histograms.
1794-- input in different bins or for generating histograms.