Skip to content

Commit e8dbba8

Browse files
authored
Add foldMap for IntSet (#1048)
1 parent 9b1d9d4 commit e8dbba8

File tree

5 files changed

+69
-3
lines changed

5 files changed

+69
-3
lines changed

containers-tests/benchmarks/IntSet.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Control.DeepSeq (rnf)
77
import Control.Exception (evaluate)
88
import Test.Tasty.Bench (bench, defaultMain, whnf)
99
import Data.List (foldl')
10-
import Data.Monoid (Sum(..))
10+
import Data.Monoid (Sum(..), All(..))
1111
import qualified Data.IntSet as IS
1212
-- benchmarks for "instance Ord IntSet"
1313
-- uses IntSet as keys of maps, and elements of sets
@@ -56,6 +56,8 @@ main = do
5656
, bench "splitMember:dense" $ whnf (IS.splitMember elem_mid) s
5757
, bench "splitMember:sparse" $ whnf (IS.splitMember elem_sparse_mid) s_sparse
5858
, bench "eq" $ whnf (\s' -> s' == s') s -- worst case, compares everything
59+
, bench "foldMap:dense" $ whnf (IS.foldMap (All . (>0))) s
60+
, bench "foldMap:sparse" $ whnf (IS.foldMap (All . (>0))) s_sparse
5961
]
6062
where
6163
bound = 2^12

containers-tests/tests/intset-properties.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import qualified Data.Foldable1 as Foldable1
1414
#endif
1515
import qualified Data.Set as Set
1616
import IntSetValidity (valid)
17-
import Prelude hiding (lookup, null, map, filter, foldr, foldl, foldl')
17+
import Prelude hiding (lookup, null, map, filter, foldr, foldl, foldl', foldMap)
1818
import Test.Tasty
1919
import Test.Tasty.HUnit
2020
import Test.Tasty.QuickCheck hiding ((.&.))
@@ -71,6 +71,7 @@ main = defaultMain $ testGroup "intset-properties"
7171
, testProperty "prop_foldR'" prop_foldR'
7272
, testProperty "prop_foldL" prop_foldL
7373
, testProperty "prop_foldL'" prop_foldL'
74+
, testProperty "prop_foldMap" prop_foldMap
7475
, testProperty "prop_map" prop_map
7576
, testProperty "prop_mapMonotonicId" prop_mapMonotonicId
7677
, testProperty "prop_mapMonotonicLinear" prop_mapMonotonicLinear
@@ -386,6 +387,9 @@ prop_foldL s = foldl (flip (:)) [] s == List.foldl (flip (:)) [] (toList s)
386387
prop_foldL' :: IntSet -> Bool
387388
prop_foldL' s = foldl' (flip (:)) [] s == List.foldl' (flip (:)) [] (toList s)
388389

390+
prop_foldMap :: IntSet -> Property
391+
prop_foldMap s = foldMap (:[]) s === toList s
392+
389393
prop_map :: IntSet -> Bool
390394
prop_map s = map id s == s
391395

containers/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
* Add `Intersection` and `intersections` for `Data.Set` and `Data.IntSet`.
5252
(Reed Mullanix, Soumik Sarkar)
5353

54+
* Add `foldMap` for `Data.IntSet`. (Soumik Sarkar)
55+
5456
## Unreleased with `@since` annotation for 0.7.1:
5557

5658
### Additions

containers/src/Data/IntSet.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ module Data.IntSet (
134134
-- * Folds
135135
, IS.foldr
136136
, IS.foldl
137+
, IS.foldMap
137138
-- ** Strict folds
138139
, IS.foldr'
139140
, IS.foldl'

containers/src/Data/IntSet/Internal.hs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ module Data.IntSet.Internal (
150150
-- * Folds
151151
, foldr
152152
, foldl
153+
, foldMap
153154
-- ** Strict folds
154155
, foldr'
155156
, foldl'
@@ -206,7 +207,7 @@ import qualified Data.Foldable1 as Foldable1
206207
import Data.List.NonEmpty (NonEmpty(..))
207208
#endif
208209
import Utils.Containers.Internal.Prelude hiding
209-
(filter, foldr, foldl, foldl', null, map)
210+
(filter, foldr, foldl, foldl', foldMap, null, map)
210211
import Prelude ()
211212

212213
import Utils.Containers.Internal.BitUtil
@@ -1252,6 +1253,29 @@ foldl' f z = \t -> -- Use lambda t to be inlinable with two arguments only.
12521253
go z' (Bin _ l r) = go (go z' l) r
12531254
{-# INLINE foldl' #-}
12541255

1256+
-- | \(O(n))\). Map the elements in the set to a monoid and combine with @(<>)@.
1257+
foldMap :: Monoid a => (Key -> a) -> IntSet -> a
1258+
foldMap f = \t -> -- Use lambda t to be inlinable with one argument only.
1259+
case t of
1260+
Bin p l r
1261+
#if MIN_VERSION_base(4,11,0)
1262+
| signBranch p -> go r <> go l -- handle negative numbers
1263+
| otherwise -> go l <> go r
1264+
#else
1265+
| signBranch p -> go r `mappend` go l -- handle negative numbers
1266+
| otherwise -> go l `mappend` go r
1267+
#endif
1268+
_ -> go t
1269+
where
1270+
#if MIN_VERSION_base(4,11,0)
1271+
go (Bin _ l r) = go l <> go r
1272+
#else
1273+
go (Bin _ l r) = go l `mappend` go r
1274+
#endif
1275+
go (Tip kx bm) = foldMapBits kx f bm
1276+
go Nil = mempty
1277+
{-# INLINE foldMap #-}
1278+
12551279
{--------------------------------------------------------------------
12561280
List variations
12571281
--------------------------------------------------------------------}
@@ -1675,6 +1699,11 @@ foldlBits :: Int -> (a -> Int -> a) -> a -> Nat -> a
16751699
foldl'Bits :: Int -> (a -> Int -> a) -> a -> Nat -> a
16761700
foldrBits :: Int -> (Int -> a -> a) -> a -> Nat -> a
16771701
foldr'Bits :: Int -> (Int -> a -> a) -> a -> Nat -> a
1702+
#if MIN_VERSION_base(4,11,0)
1703+
foldMapBits :: Semigroup a => Int -> (Int -> a) -> Nat -> a
1704+
#else
1705+
foldMapBits :: Monoid a => Int -> (Int -> a) -> Nat -> a
1706+
#endif
16781707
takeWhileAntitoneBits :: Int -> (Int -> Bool) -> Nat -> Nat
16791708

16801709
{-# INLINE lowestBitSet #-}
@@ -1683,6 +1712,7 @@ takeWhileAntitoneBits :: Int -> (Int -> Bool) -> Nat -> Nat
16831712
{-# INLINE foldl'Bits #-}
16841713
{-# INLINE foldrBits #-}
16851714
{-# INLINE foldr'Bits #-}
1715+
{-# INLINE foldMapBits #-}
16861716
{-# INLINE takeWhileAntitoneBits #-}
16871717

16881718
lowestBitMask :: Nat -> Nat
@@ -1738,6 +1768,20 @@ foldr'Bits prefix f z bitmap = go (revNat bitmap) z
17381768
where !bitmask = lowestBitMask bm
17391769
!bi = countTrailingZeros bitmask
17401770

1771+
foldMapBits prefix f bitmap = go (prefix + bi0) (bitmap `xor` bitmask0)
1772+
where
1773+
bitmask0 = lowestBitMask bitmap
1774+
bi0 = countTrailingZeros bitmask0
1775+
go !x 0 = f x
1776+
#if MIN_VERSION_base(4,11,0)
1777+
go !x bm = f x <> go (prefix + bi) (bm `xor` bitmask)
1778+
#else
1779+
go !x bm = f x `mappend` go (prefix + bi) (bm `xor` bitmask)
1780+
#endif
1781+
where
1782+
bitmask = lowestBitMask bm
1783+
bi = countTrailingZeros bitmask
1784+
17411785
takeWhileAntitoneBits prefix predicate bitmap =
17421786
-- Binary search for the first index where the predicate returns false, but skip a predicate
17431787
-- call if the high half of the current range is empty. This ensures
@@ -1810,6 +1854,19 @@ foldr'Bits prefix f z bm = let lb = lowestBitSet bm
18101854
go bi n | n `testBit` 0 = f bi $! go (bi + 1) (n `shiftRL` 1)
18111855
| otherwise = go (bi + 1) (n `shiftRL` 1)
18121856

1857+
foldMapBits prefix f bm = go x0 (x0 + 1) ((bm `shiftRL` lb) `shiftRL` 1)
1858+
where
1859+
lb = lowestBitSet bm
1860+
x0 = prefix + lb
1861+
go !x !_ 0 = f x
1862+
go !x !bi n
1863+
#if MIN_VERSION_base(4,11,0)
1864+
| n `testBit` 0 = f x <> go bi (bi + 1) (n `shiftRL` 1)
1865+
#else
1866+
| n `testBit` 0 = f x `mappend` go bi (bi + 1) (n `shiftRL` 1)
1867+
#endif
1868+
| otherwise = go x (bi + 1) (n `shiftRL` 1)
1869+
18131870
takeWhileAntitoneBits prefix predicate = foldl'Bits prefix f 0 -- Does not use antitone property
18141871
where
18151872
f acc bi | predicate bi = acc .|. bitmapOf bi

0 commit comments

Comments
 (0)