packages feed

bloomfilter 1.2.6.10 → 2.0.0.0

raw patch · 11 files changed

+383/−252 lines, 11 filesdep +QuickCheckdep +bloomfilterdep +randomdep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, bloomfilter, random, test-framework, test-framework-quickcheck2

Dependency ranges changed: base, bytestring

API changes (from Hackage documentation)

- Data.BloomFilter: bitArrayB :: Bloom a -> (UArray Int Hash)
- Data.BloomFilter: bitArrayMB :: MBloom s a -> (STUArray s Int Hash)
- Data.BloomFilter: createB :: (a -> [Hash]) -> Int -> (forall s. MBloom s a -> ST s z) -> Bloom a
- Data.BloomFilter: elemB :: a -> Bloom a -> Bool
- Data.BloomFilter: elemMB :: a -> MBloom s a -> ST s Bool
- Data.BloomFilter: emptyB :: (a -> [Hash]) -> Int -> Bloom a
- Data.BloomFilter: fromListB :: (a -> [Hash]) -> Int -> [a] -> Bloom a
- Data.BloomFilter: insertB :: a -> Bloom a -> Bloom a
- Data.BloomFilter: insertListB :: [a] -> Bloom a -> Bloom a
- Data.BloomFilter: insertMB :: MBloom s a -> a -> ST s ()
- Data.BloomFilter: instance Show (MBloom s a)
- Data.BloomFilter: lengthB :: Bloom a -> Int
- Data.BloomFilter: lengthMB :: MBloom s a -> Int
- Data.BloomFilter: modifyB :: (forall s. MBloom s a -> ST s z) -> Bloom a -> Bloom a
- Data.BloomFilter: newMB :: (a -> [Hash]) -> Int -> ST s (MBloom s a)
- Data.BloomFilter: notElemB :: a -> Bloom a -> Bool
- Data.BloomFilter: singletonB :: (a -> [Hash]) -> Int -> a -> Bloom a
- Data.BloomFilter: thawMB :: Bloom a -> ST s (MBloom s a)
- Data.BloomFilter: unfoldB :: (a -> [Hash]) -> Int -> (b -> Maybe (a, b)) -> b -> Bloom a
- Data.BloomFilter: unsafeFreezeMB :: MBloom s a -> ST s (Bloom a)
- Data.BloomFilter.Easy: elemB :: a -> Bloom a -> Bool
- Data.BloomFilter.Easy: lengthB :: Bloom a -> Int
- Data.BloomFilter.Easy: notElemB :: a -> Bloom a -> Bool
+ Data.BloomFilter: bitArray :: Bloom a -> (UArray Int Hash)
+ Data.BloomFilter: elem :: a -> Bloom a -> Bool
+ Data.BloomFilter: empty :: (a -> [Hash]) -> Int -> Bloom a
+ Data.BloomFilter: freeze :: MBloom s a -> ST s (Bloom a)
+ Data.BloomFilter: fromList :: (a -> [Hash]) -> Int -> [a] -> Bloom a
+ Data.BloomFilter: insert :: a -> Bloom a -> Bloom a
+ Data.BloomFilter: insertList :: [a] -> Bloom a -> Bloom a
+ Data.BloomFilter: length :: Bloom a -> Int
+ Data.BloomFilter: notElem :: a -> Bloom a -> Bool
+ Data.BloomFilter: singleton :: (a -> [Hash]) -> Int -> a -> Bloom a
+ Data.BloomFilter: thaw :: Bloom a -> ST s (MBloom s a)
+ Data.BloomFilter: unfold :: (a -> [Hash]) -> Int -> (b -> Maybe (a, b)) -> b -> Bloom a
+ Data.BloomFilter: unsafeFreeze :: MBloom s a -> ST s (Bloom a)
+ Data.BloomFilter.Easy: elem :: a -> Bloom a -> Bool
+ Data.BloomFilter.Easy: length :: Bloom a -> Int
+ Data.BloomFilter.Easy: notElem :: a -> Bloom a -> Bool
+ Data.BloomFilter.Mutable: bitArray :: MBloom s a -> (STUArray s Int Hash)
+ Data.BloomFilter.Mutable: data MBloom s a
+ Data.BloomFilter.Mutable: elem :: a -> MBloom s a -> ST s Bool
+ Data.BloomFilter.Mutable: insert :: MBloom s a -> a -> ST s ()
+ Data.BloomFilter.Mutable: length :: MBloom s a -> Int
+ Data.BloomFilter.Mutable: new :: (a -> [Hash]) -> Int -> ST s (MBloom s a)
+ Data.BloomFilter.Mutable: type Hash = Word32

Files

Data/BloomFilter.hs view
@@ -1,5 +1,4 @@-{-# LANGUAGE BangPatterns, CPP, Rank2Types, ScopedTypeVariables,-    TypeOperators #-}+{-# LANGUAGE BangPatterns, Rank2Types, ScopedTypeVariables, TypeOperators #-}  -- | -- Module: Data.BloomFilter@@ -42,38 +41,27 @@     , MBloom      -- * Immutable Bloom filters-    -- ** Creation-    , unfoldB -    , fromListB-    , emptyB-    , singletonB--    -- ** Accessors-    , lengthB-    , elemB-    , notElemB--    -- ** Mutators-    , insertB-    , insertListB--    -- * Mutable Bloom filters-    -- ** Immutability wrappers-    , createB-    , modifyB+    -- ** Conversion+    , freeze+    , thaw+    , unsafeFreeze      -- ** Creation-    , newMB-    , unsafeFreezeMB-    , thawMB+    , unfold +    , fromList+    , empty+    , singleton+     -- ** Accessors-    , lengthMB-    , elemMB+    , length+    , elem+    , notElem -    -- ** Mutation-    , insertMB+    -- ** Modification+    , insert+    , insertList      -- * The underlying representation     -- | If you serialize the raw bit arrays below to disk, do not@@ -81,101 +69,43 @@     -- conventions for endianness or word size.      -- | The raw bit array used by the immutable 'Bloom' type.-    , bitArrayB--    -- | The raw bit array used by the immutable 'MBloom' type.-    , bitArrayMB+    , bitArray     ) where -#include "MachDeps.h"- import Control.Monad (liftM, forM_) import Control.Monad.ST (ST, runST) import Control.DeepSeq (NFData(..))-import Data.Array.Base (unsafeAt, unsafeRead, unsafeWrite)-import Data.Array.ST (STUArray, thaw, unsafeFreeze)+import Data.Array.Base (unsafeAt)+import qualified Data.Array.Base as ST import Data.Array.Unboxed (UArray)-import Data.Bits ((.&.), (.|.))-import Data.BloomFilter.Array (newArray)-import Data.BloomFilter.Util (FastShift(..), (:*)(..), nextPowerOfTwo)+import Data.Bits ((.&.))+import Data.BloomFilter.Util (FastShift(..), (:*)(..))+import qualified Data.BloomFilter.Mutable as MB+import qualified Data.BloomFilter.Mutable.Internal as MB+import Data.BloomFilter.Mutable.Internal (Hash, MBloom) import Data.Word (Word32) --- Make sure we're not performing any expensive arithmetic operations.-import Prelude hiding ((/), (*), div, divMod, mod, rem)+import Prelude hiding (elem, length, notElem,+                       (/), (*), div, divMod, mod, rem) -{--import Debug.Trace-traceM :: (Show a, Monad m) => a -> m ()-traceM v = show v `trace` return ()-traces :: Show a => a -> b -> b-traces s = trace (show s)--} --- | A hash value is 32 bits wide.  This limits the maximum size of a--- filter to about four billion elements, or 512 megabytes of memory.-type Hash = Word32---- | A mutable Bloom filter, for use within the 'ST' monad.-data MBloom s a = MB {-      hashMB :: !(a -> [Hash])-    , shiftMB :: {-# UNPACK #-} !Int-    , maskMB :: {-# UNPACK #-} !Int-    , bitArrayMB :: {-# UNPACK #-} !(STUArray s Int Hash)-    }- -- | An immutable Bloom filter, suitable for querying from pure code. data Bloom a = B {-      hashB :: !(a -> [Hash])-    , shiftB :: {-# UNPACK #-} !Int-    , maskB :: {-# UNPACK #-} !Int-    , bitArrayB :: {-# UNPACK #-} !(UArray Int Hash)+      hashes :: !(a -> [Hash])+    , shift :: {-# UNPACK #-} !Int+    , mask :: {-# UNPACK #-} !Int+    , bitArray :: {-# UNPACK #-} !(UArray Int Hash)     } -instance Show (MBloom s a) where-    show mb = "MBloom { " ++ show (lengthMB mb) ++ " bits } "- instance Show (Bloom a) where-    show ub = "Bloom { " ++ show (lengthB ub) ++ " bits } "+    show ub = "Bloom { " ++ show ((1::Int) `shiftL` shift ub) ++ " bits } "  instance NFData (Bloom a) where     rnf !_ = () --- | Create a new mutable Bloom filter.  For efficiency, the number of--- bits used may be larger than the number requested.  It is always--- rounded up to the nearest higher power of two, but clamped at a--- maximum of 4 gigabits, since hashes are 32 bits in size.------ For a safer creation interface, use 'createB'.  To convert a--- mutable filter to an immutable filter for use in pure code, use--- 'unsafeFreezeMB'.-newMB :: (a -> [Hash])          -- ^ family of hash functions to use-      -> Int                    -- ^ number of bits in filter-      -> ST s (MBloom s a)-newMB hash numBits = MB hash shift mask `liftM` newArray numElems numBytes-  where twoBits | numBits < 1 = 1-                | numBits > maxHash = maxHash-                | isPowerOfTwo numBits = numBits-                | otherwise = nextPowerOfTwo numBits-        numElems = max 2 (twoBits `shiftR` logBitsInHash)-        numBytes = numElems `shiftL` logBytesInHash-        trueBits = numElems `shiftL` logBitsInHash-        shift = logPower2 trueBits-        mask = trueBits - 1-        isPowerOfTwo n = n .&. (n - 1) == 0--maxHash :: Int-#if WORD_SIZE_IN_BITS == 64-maxHash = 4294967296-#else-maxHash = 1073741824-#endif- logBitsInHash :: Int logBitsInHash = 5 -- logPower2 bitsInHash -logBytesInHash :: Int-logBytesInHash = 2 -- logPower2 (sizeOf (undefined :: Hash))- -- | Create an immutable Bloom filter, using the given setup function -- which executes in the 'ST' monad. --@@ -184,42 +114,60 @@ -- @ --import "Data.BloomFilter.Hash" (cheapHashes) -----filter = createB (cheapHashes 3) 1024 $ \mf -> do+--filter = create (cheapHashes 3) 1024 $ \mf -> do --           insertMB mf \"foo\" --           insertMB mf \"bar\" -- @ -- -- Note that the result of the setup function is not used.-createB :: (a -> [Hash])        -- ^ family of hash functions to use+create :: (a -> [Hash])        -- ^ family of hash functions to use         -> Int                  -- ^ number of bits in filter-        -> (forall s. (MBloom s a -> ST s z))  -- ^ setup function (result is discarded)+        -> (forall s. (MBloom s a -> ST s ()))  -- ^ setup function         -> Bloom a-{-# INLINE createB #-}-createB hash numBits body = runST $ do-  mb <- newMB hash numBits-  _ <- body mb-  unsafeFreezeMB mb+{-# INLINE create #-}+create hash numBits body = runST $ do+  mb <- MB.new hash numBits+  body mb+  unsafeFreeze mb +-- | Create an immutable Bloom filter from a mutable one.  The mutable+-- filter may be modified afterwards.+freeze :: MBloom s a -> ST s (Bloom a)+freeze mb = B (MB.hashes mb) (MB.shift mb) (MB.mask mb) `liftM`+            ST.freeze (MB.bitArray mb)++-- | Create an immutable Bloom filter from a mutable one.  The mutable+-- filter /must not/ be modified afterwards, or a runtime crash may+-- occur.  For a safer creation interface, use 'freeze' or 'create'.+unsafeFreeze :: MBloom s a -> ST s (Bloom a)+unsafeFreeze mb = B (MB.hashes mb) (MB.shift mb) (MB.mask mb) `liftM`+                    ST.unsafeFreeze (MB.bitArray mb)++-- | Copy an immutable Bloom filter to create a mutable one.  There is+-- no non-copying equivalent.+thaw :: Bloom a -> ST s (MBloom s a)+thaw ub = MB.MB (hashes ub) (shift ub) (mask ub) `liftM` ST.thaw (bitArray ub)+ -- | Create an empty Bloom filter. ----- This function is subject to fusion with 'insertB'--- and 'insertListB'.-emptyB :: (a -> [Hash])         -- ^ family of hash functions to use+-- This function is subject to fusion with 'insert'+-- and 'insertList'.+empty :: (a -> [Hash])         -- ^ family of hash functions to use        -> Int                   -- ^ number of bits in filter        -> Bloom a-{-# INLINE [1] emptyB #-}-emptyB hash numBits = createB hash numBits (\_ -> return ())+{-# INLINE [1] empty #-}+empty hash numBits = create hash numBits (\_ -> return ())  -- | Create a Bloom filter with a single element. ----- This function is subject to fusion with 'insertB'--- and 'insertListB'.-singletonB :: (a -> [Hash])     -- ^ family of hash functions to use+-- This function is subject to fusion with 'insert'+-- and 'insertList'.+singleton :: (a -> [Hash])     -- ^ family of hash functions to use            -> Int               -- ^ number of bits in filter            -> a                 -- ^ element to insert            -> Bloom a-{-# INLINE [1] singletonB #-}-singletonB hash numBits elt = createB hash numBits (\mb -> insertMB mb elt)+{-# INLINE [1] singleton #-}+singleton hash numBits elt = create hash numBits (\mb -> MB.insert mb elt)  -- | Given a filter's mask and a hash value, compute an offset into -- a word array and a bit offset within that word.@@ -231,50 +179,28 @@ -- | Hash the given value, returning a list of (word offset, bit -- offset) pairs, one per hash value. hashesM :: MBloom s a -> a -> [Int :* Int]-hashesM mb elt = hashIdx (maskMB mb) `map` hashMB mb elt+hashesM mb elt = hashIdx (MB.mask mb) `map` MB.hashes mb elt  -- | Hash the given value, returning a list of (word offset, bit -- offset) pairs, one per hash value. hashesU :: Bloom a -> a -> [Int :* Int]-hashesU ub elt = hashIdx (maskB ub) `map` hashB ub elt---- | Insert a value into a mutable Bloom filter.  Afterwards, a--- membership query for the same value is guaranteed to return @True@.-insertMB :: MBloom s a -> a -> ST s ()-insertMB mb elt = do-  let mu = bitArrayMB mb-  forM_ (hashesM mb elt) $ \(word :* bit) -> do-      old <- unsafeRead mu word-      unsafeWrite mu word (old .|. (1 `shiftL` bit))---- | Query a mutable Bloom filter for membership.  If the value is--- present, return @True@.  If the value is not present, there is--- /still/ some possibility that @True@ will be returned.-elemMB :: a -> MBloom s a -> ST s Bool-elemMB elt mb = loop (hashesM mb elt)-  where mu = bitArrayMB mb-        loop ((word :* bit):wbs) = do-          i <- unsafeRead mu word-          if i .&. (1 `shiftL` bit) == 0-            then return False-            else loop wbs-        loop _ = return True+hashesU ub elt = hashIdx (mask ub) `map` hashes ub elt  -- | Query an immutable Bloom filter for membership.  If the value is -- present, return @True@.  If the value is not present, there is -- /still/ some possibility that @True@ will be returned.-elemB :: a -> Bloom a -> Bool-elemB elt ub = all test (hashesU ub elt)-  where test (off :* bit) = (bitArrayB ub `unsafeAt` off) .&. (1 `shiftL` bit) /= 0+elem :: a -> Bloom a -> Bool+elem elt ub = all test (hashesU ub elt)+  where test (off :* bit) = (bitArray ub `unsafeAt` off) .&. (1 `shiftL` bit) /= 0           -modifyB :: (forall s. (MBloom s a -> ST s z))  -- ^ mutation function (result is discarded)+modify :: (forall s. (MBloom s a -> ST s z))  -- ^ mutation function (result is discarded)         -> Bloom a         -> Bloom a-{-# INLINE modifyB #-}-modifyB body ub = runST $ do-  mb <- thawMB ub+{-# INLINE modify #-}+modify body ub = runST $ do+  mb <- thaw ub   _ <- body mb-  unsafeFreezeMB mb+  unsafeFreeze mb  -- | Create a new Bloom filter from an existing one, with the given -- member added.@@ -284,9 +210,9 @@ -- -- Repeated applications of this function with itself are subject to -- fusion.-insertB :: a -> Bloom a -> Bloom a-{-# NOINLINE insertB #-}-insertB elt = modifyB (flip insertMB elt)+insert :: a -> Bloom a -> Bloom a+{-# NOINLINE insert #-}+insert elt = modify (flip MB.insert elt)  -- | Create a new Bloom filter from an existing one, with the given -- members added.@@ -296,63 +222,44 @@ -- -- Repeated applications of this function with itself are subject to -- fusion.-insertListB :: [a] -> Bloom a -> Bloom a-{-# NOINLINE insertListB #-}-insertListB elts = modifyB $ \mb -> mapM_ (insertMB mb) elts+insertList :: [a] -> Bloom a -> Bloom a+{-# NOINLINE insertList #-}+insertList elts = modify $ \mb -> mapM_ (MB.insert mb) elts -{-# RULES "Bloom insertB . insertB" forall a b u.-    insertB b (insertB a u) = insertListB [a,b] u+{-# RULES "Bloom insert . insert" forall a b u.+    insert b (insert a u) = insertList [a,b] u   #-} -{-# RULES "Bloom insertListB . insertB" forall x xs u.-    insertListB xs (insertB x u) = insertListB (x:xs) u+{-# RULES "Bloom insertList . insert" forall x xs u.+    insertList xs (insert x u) = insertList (x:xs) u   #-} -{-# RULES "Bloom insertB . insertListB" forall x xs u.-    insertB x (insertListB xs u) = insertListB (x:xs) u+{-# RULES "Bloom insert . insertList" forall x xs u.+    insert x (insertList xs u) = insertList (x:xs) u   #-} -{-# RULES "Bloom insertListB . insertListB" forall xs ys u.-    insertListB xs (insertListB ys u) = insertListB (xs++ys) u+{-# RULES "Bloom insertList . insertList" forall xs ys u.+    insertList xs (insertList ys u) = insertList (xs++ys) u   #-} -{-# RULES "Bloom insertListB . emptyB" forall h n xs.-    insertListB xs (emptyB h n) = fromListB h n xs+{-# RULES "Bloom insertList . empty" forall h n xs.+    insertList xs (empty h n) = fromList h n xs   #-} -{-# RULES "Bloom insertListB . singletonB" forall h n x xs.-    insertListB xs (singletonB h n x) = fromListB h n (x:xs)+{-# RULES "Bloom insertList . singleton" forall h n x xs.+    insertList xs (singleton h n x) = fromList h n (x:xs)   #-}  -- | Query an immutable Bloom filter for non-membership.  If the value -- /is/ present, return @False@.  If the value is not present, there -- is /still/ some possibility that @True@ will be returned.-notElemB :: a -> Bloom a -> Bool-notElemB elt ub = any test (hashesU ub elt)-  where test (off :* bit) = (bitArrayB ub `unsafeAt` off) .&. (1 `shiftL` bit) == 0---- | Create an immutable Bloom filter from a mutable one.  The mutable--- filter /must not/ be modified afterwards, or a runtime crash may--- occur.  For a safer creation interface, use 'createB'.-unsafeFreezeMB :: MBloom s a -> ST s (Bloom a)-unsafeFreezeMB mb = B (hashMB mb) (shiftMB mb) (maskMB mb) `liftM`-                    unsafeFreeze (bitArrayMB mb)---- | Copy an immutable Bloom filter to create a mutable one.  There is--- no non-copying equivalent.-thawMB :: Bloom a -> ST s (MBloom s a)-thawMB ub = MB (hashB ub) (shiftB ub) (maskB ub) `liftM` thaw (bitArrayB ub)---- bitsInHash :: Int--- bitsInHash = sizeOf (undefined :: Hash) `shiftL` 3---- | Return the size of a mutable Bloom filter, in bits.-lengthMB :: MBloom s a -> Int-lengthMB = shiftL 1 . shiftMB+notElem :: a -> Bloom a -> Bool+notElem elt ub = any test (hashesU ub elt)+  where test (off :* bit) = (bitArray ub `unsafeAt` off) .&. (1 `shiftL` bit) == 0  -- | Return the size of an immutable Bloom filter, in bits.-lengthB :: Bloom a -> Int-lengthB = shiftL 1 . shiftB+length :: Bloom a -> Int+length = shiftL 1 . shift  -- | Build an immutable Bloom filter from a seed value.  The seeding -- function populates the filter as follows.@@ -362,16 +269,16 @@ -- --   * If it returns @'Just' (a,b)@, @a@ is added to the filter and --     @b@ is used as a new seed.-unfoldB :: forall a b. (a -> [Hash]) -- ^ family of hash functions to use+unfold :: forall a b. (a -> [Hash]) -- ^ family of hash functions to use         -> Int                       -- ^ number of bits in filter         -> (b -> Maybe (a, b))       -- ^ seeding function         -> b                         -- ^ initial seed         -> Bloom a-{-# INLINE unfoldB #-}-unfoldB hashes numBits f k = createB hashes numBits (loop k)+{-# INLINE unfold #-}+unfold hashes numBits f k = create hashes numBits (loop k)   where loop :: forall s. b -> MBloom s a -> ST s ()         loop j mb = case f j of-                      Just (a, j') -> insertMB mb a >> loop j' mb+                      Just (a, j') -> MB.insert mb a >> loop j' mb                       _ -> return ()  -- | Create an immutable Bloom filter, populating it from a list of@@ -384,24 +291,24 @@ -- @ --import "Data.BloomFilter.Hash" (cheapHashes) -----filt = fromListB (cheapHashes 3) 1024 [\"foo\", \"bar\", \"quux\"]+--filt = fromList (cheapHashes 3) 1024 [\"foo\", \"bar\", \"quux\"] -- @-fromListB :: (a -> [Hash])      -- ^ family of hash functions to use+fromList :: (a -> [Hash])      -- ^ family of hash functions to use           -> Int                -- ^ number of bits in filter           -> [a]                -- ^ values to populate with           -> Bloom a-{-# INLINE [1] fromListB #-}-fromListB hashes numBits list = createB hashes numBits $ forM_ list . insertMB+{-# INLINE [1] fromList #-}+fromList hashes numBits list = create hashes numBits $ forM_ list . MB.insert -{-# RULES "Bloom insertListB . fromListB" forall h n xs ys.-    insertListB xs (fromListB h n ys) = fromListB h n (xs ++ ys)+{-# RULES "Bloom insertList . fromList" forall h n xs ys.+    insertList xs (fromList h n ys) = fromList h n (xs ++ ys)   #-}  {- -- This is a simpler definition, but GHC doesn't inline the unfold -- sensibly. -fromListB hashes numBits = unfoldB hashes numBits convert+fromList hashes numBits = unfold hashes numBits convert   where convert (x:xs) = Just (x, xs)         convert _      = Nothing -}@@ -432,11 +339,9 @@  -- $ease ----- This module provides both mutable and immutable interfaces for--- creating and querying a Bloom filter.  It is most useful as a--- low-level way to create a Bloom filter with a custom set of--- characteristics, perhaps in combination with the hashing functions--- in 'Data.BloomFilter.Hash'.+-- This module provides immutable interfaces for working with a+-- query-only Bloom filter, and for converting to and from mutable+-- Bloom filters. -- -- For a higher-level interface that is easy to use, see the -- 'Data.BloomFilter.Easy' module.
Data/BloomFilter/Array.hs view
@@ -3,7 +3,8 @@  module Data.BloomFilter.Array (newArray) where -import Control.Monad.ST (ST, unsafeIOToST)+import Control.Monad.ST (ST)+import Control.Monad.ST.Unsafe (unsafeIOToST) import Data.Array.Base (MArray, STUArray(..), unsafeNewArray_) #if __GLASGOW_HASKELL__ >= 704 import Foreign.C.Types (CInt(..), CSize(..))
Data/BloomFilter/Easy.hs view
@@ -16,9 +16,9 @@     -- * Easy creation and querying       Bloom     , easyList-    , elemB-    , notElemB-    , lengthB+    , B.elem+    , B.notElem+    , B.length      -- ** Example: a spell checker     -- $example@@ -28,11 +28,12 @@     , suggestSizing     ) where -import Data.BloomFilter (Bloom, elemB, fromListB, lengthB, notElemB)+import Data.BloomFilter (Bloom) import Data.BloomFilter.Hash (Hashable, cheapHashes) import Data.BloomFilter.Util (nextPowerOfTwo) import qualified Data.ByteString as SB import qualified Data.ByteString.Lazy as LB+import qualified Data.BloomFilter as B  -- | Create a Bloom filter with the given false positive rate and -- members.  The hash functions used are computed by the @cheapHashes@@@ -44,7 +45,7 @@ {-# SPECIALIZE easyList :: Double -> [String] -> Bloom String #-} {-# SPECIALIZE easyList :: Double -> [LB.ByteString] -> Bloom LB.ByteString #-} {-# SPECIALIZE easyList :: Double -> [SB.ByteString] -> Bloom SB.ByteString #-}-easyList errRate xs = fromListB (cheapHashes numHashes) numBits xs+easyList errRate xs = B.fromList (cheapHashes numHashes) numBits xs     where capacity = length xs           (numBits, numHashes)               | capacity > 0 = suggestSizing capacity errRate@@ -71,7 +72,7 @@             minimum [((-k) * cap / log (1 - (errRate ** (1 / k))), k)                      | k <- [1..100]]         roundedBits = nextPowerOfTwo (ceiling bits)-    in if roundedBits <= 0+    in if roundedBits <= 0 || roundedBits > 0xffffffff        then Left  "capacity too large to represent"        else Right (roundedBits, truncate hashes) 
Data/BloomFilter/Hash.hs view
@@ -96,13 +96,13 @@                    h1 <- hashIO32 v s1                    h2 <- hashIO32 v s2                    return $ (fromIntegral h1 `shiftL` 32) .|. fromIntegral h2-      + -- | Compute a 32-bit hash. hash32 :: Hashable a => a -> Word32-hash32 = hashSalt32 0x106fc397+hash32 = hashSalt32 0x16fc397c  hash64 :: Hashable a => a -> Word64-hash64 = hashSalt64 0x106fc397cf62f64d3+hash64 = hashSalt64 0x16fc397cf62f64d3  -- | Compute a salted 32-bit hash. hashSalt32 :: Hashable a => Word32  -- ^ salt@@ -123,7 +123,7 @@ hashes :: Hashable a => Int     -- ^ number of hashes to compute        -> a                     -- ^ value to hash        -> [Word32]-hashes n v = unfoldr go (n,0x3f56da2d3ddbb9f6)+hashes n v = unfoldr go (n,0x3f56da2d)     where go (k,s) | k <= 0    = Nothing                    | otherwise = let s' = hashSalt32 s v                                  in Just (s', (k-1,s'))@@ -160,7 +160,7 @@  instance Hashable Integer where     hashIO32 k salt | k < 0 = hashIO32 (unfoldr go (-k))-                                   (salt `xor` 0x3ece731e9c1c64f8)+                                   (salt `xor` 0x3ece731e)                   | otherwise = hashIO32 (unfoldr go k) salt         where go 0 = Nothing               go i = Just (fromIntegral i :: Word32, i `shiftR` 32)@@ -377,7 +377,7 @@                 then step c (fromIntegral (nstoff - stoff))                 else frag xs nstoff             frag LB.Empty stoff = return (fromIntegral (12 - stoff))-        c_begin p1 p2 st +        c_begin p1 p2 st         unread <- step s 0         c_end (fromIntegral unread) p1 p2 st       peek sp
+ Data/BloomFilter/Mutable.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE BangPatterns, CPP, Rank2Types,+    TypeOperators #-}++-- |+-- Module: Data.BloomFilter.Mutable+-- Copyright: Bryan O'Sullivan+-- License: BSD3+--+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: unstable+-- Portability: portable+--+-- A fast, space efficient Bloom filter implementation.  A Bloom+-- filter is a set-like data structure that provides a probabilistic+-- membership test.+--+-- * Queries do not give false negatives.  When an element is added to+--   a filter, a subsequent membership test will definitely return+--   'True'.+--+-- * False positives /are/ possible.  If an element has not been added+--   to a filter, a membership test /may/ nevertheless indicate that+--   the element is present.+--+-- This module provides low-level control.  For an easier to use+-- interface, see the "Data.BloomFilter.Easy" module.++module Data.BloomFilter.Mutable+    (+    -- * Overview+    -- $overview++    -- ** Ease of use+    -- $ease++    -- ** Performance+    -- $performance++    -- * Types+      Hash+    , MBloom+    -- * Mutable Bloom filters++    -- ** Creation+    , new++    -- ** Accessors+    , length+    , elem++    -- ** Mutation+    , insert++    -- * The underlying representation+    -- | If you serialize the raw bit arrays below to disk, do not+    -- expect them to be portable to systems with different+    -- conventions for endianness or word size.++    -- | The raw bit array used by the mutable 'MBloom' type.+    , bitArray+    ) where++#include "MachDeps.h"++import Control.Monad (liftM, forM_)+import Control.Monad.ST (ST)+import Data.Array.Base (unsafeRead, unsafeWrite)+import Data.Bits ((.&.), (.|.))+import Data.BloomFilter.Array (newArray)+import Data.BloomFilter.Util (FastShift(..), (:*)(..), nextPowerOfTwo)+import Data.Word (Word32)+import Data.BloomFilter.Mutable.Internal++import Prelude hiding (elem, length, notElem,+                       (/), (*), div, divMod, mod, rem)++-- | Create a new mutable Bloom filter.  For efficiency, the number of+-- bits used may be larger than the number requested.  It is always+-- rounded up to the nearest higher power of two, but will be clamped+-- at a maximum of 4 gigabits, since hashes are 32 bits in size.+new :: (a -> [Hash])          -- ^ family of hash functions to use+    -> Int                    -- ^ number of bits in filter+    -> ST s (MBloom s a)+new hash numBits = MB hash shft msk `liftM` newArray numElems numBytes+  where twoBits | numBits < 1 = 1+                | numBits > maxHash = maxHash+                | isPowerOfTwo numBits = numBits+                | otherwise = nextPowerOfTwo numBits+        numElems = max 2 (twoBits `shiftR` logBitsInHash)+        numBytes = numElems `shiftL` logBytesInHash+        trueBits = numElems `shiftL` logBitsInHash+        shft     = logPower2 trueBits+        msk      = trueBits - 1+        isPowerOfTwo n = n .&. (n - 1) == 0++maxHash :: Int+#if WORD_SIZE_IN_BITS == 64+maxHash = 4294967296+#else+maxHash = 1073741824+#endif++logBitsInHash :: Int+logBitsInHash = 5 -- logPower2 bitsInHash++logBytesInHash :: Int+logBytesInHash = 2 -- logPower2 (sizeOf (undefined :: Hash))++-- | Given a filter's mask and a hash value, compute an offset into+-- a word array and a bit offset within that word.+hashIdx :: Int -> Word32 -> (Int :* Int)+hashIdx msk x = (y `shiftR` logBitsInHash) :* (y .&. hashMask)+  where hashMask = 31 -- bitsInHash - 1+        y = fromIntegral x .&. msk++-- | Hash the given value, returning a list of (word offset, bit+-- offset) pairs, one per hash value.+hashesM :: MBloom s a -> a -> [Int :* Int]+hashesM mb elt = hashIdx (mask mb) `map` hashes mb elt++-- | Insert a value into a mutable Bloom filter.  Afterwards, a+-- membership query for the same value is guaranteed to return @True@.+insert :: MBloom s a -> a -> ST s ()+insert mb elt = do+  let mu = bitArray mb+  forM_ (hashesM mb elt) $ \(word :* bit) -> do+      old <- unsafeRead mu word+      unsafeWrite mu word (old .|. (1 `shiftL` bit))++-- | Query a mutable Bloom filter for membership.  If the value is+-- present, return @True@.  If the value is not present, there is+-- /still/ some possibility that @True@ will be returned.+elem :: a -> MBloom s a -> ST s Bool+elem elt mb = loop (hashesM mb elt)+  where mu = bitArray mb+        loop ((word :* bit):wbs) = do+          i <- unsafeRead mu word+          if i .&. (1 `shiftL` bit) == 0+            then return False+            else loop wbs+        loop _ = return True++-- bitsInHash :: Int+-- bitsInHash = sizeOf (undefined :: Hash) `shiftL` 3++-- | Return the size of a mutable Bloom filter, in bits.+length :: MBloom s a -> Int+length = shiftL 1 . shift+++-- | Slow, crummy way of computing the integer log of an integer known+-- to be a power of two.+logPower2 :: Int -> Int+logPower2 k = go 0 k+    where go j 1 = j+          go j n = go (j+1) (n `shiftR` 1)++-- $overview+--+-- Each of the functions for creating Bloom filters accepts two parameters:+--+-- * The number of bits that should be used for the filter.  Note that+--   a filter is fixed in size; it cannot be resized after creation.+--+-- * A function that accepts a value, and should return a fixed-size+--   list of hashes of that value.  To keep the false positive rate+--   low, the hashes computes should, as far as possible, be+--   independent.+--+-- By choosing these parameters with care, it is possible to tune for+-- a particular false positive rate.  The @suggestSizing@ function in+-- the "Data.BloomFilter.Easy" module calculates useful estimates for+-- these parameters.++-- $ease+--+-- This module provides both mutable interfaces for creating and+-- querying a Bloom filter.  It is most useful as a low-level way to+-- manage a Bloom filter with a custom set of characteristics.++-- $performance+--+-- The implementation has been carefully tuned for high performance+-- and low space consumption.+--+-- For efficiency, the number of bits requested when creating a Bloom+-- filter is rounded up to the nearest power of two.  This lets the+-- implementation use bitwise operations internally, instead of much+-- more expensive multiplication, division, and modulus operations.
+ Data/BloomFilter/Mutable/Internal.hs view
@@ -0,0 +1,37 @@+-- |+-- Module: Data.BloomFilter.Mutable.Internal+-- Copyright: Bryan O'Sullivan+-- License: BSD3+--+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: unstable+-- Portability: portable++module Data.BloomFilter.Mutable.Internal+    (+    -- * Types+      Hash+    , MBloom(..)+    ) where++import Data.Array.Base (STUArray)+import Data.Bits (shiftL)+import Data.Word (Word32)++import Prelude hiding (elem, length, notElem,+                       (/), (*), div, divMod, mod, rem)++-- | A hash value is 32 bits wide.  This limits the maximum size of a+-- filter to about four billion elements, or 512 megabytes of memory.+type Hash = Word32++-- | A mutable Bloom filter, for use within the 'ST' monad.+data MBloom s a = MB {+      hashes :: !(a -> [Hash])+    , shift :: {-# UNPACK #-} !Int+    , mask :: {-# UNPACK #-} !Int+    , bitArray :: {-# UNPACK #-} !(STUArray s Int Hash)+    }++instance Show (MBloom s a) where+    show mb = "MBloom { " ++ show ((1::Int) `shiftL` shift mb) ++ " bits } "
bloomfilter.cabal view
@@ -1,5 +1,5 @@ name:            bloomfilter-version:         1.2.6.10+version:         2.0.0.0 license:         BSD3 license-file:    LICENSE author:          Bryan O'Sullivan <bos@serpentine.com>@@ -18,22 +18,35 @@ library   build-depends:     array,-    base       < 5,+    base       >= 4.4 && < 5,     bytestring >= 0.9,     deepseq-  if impl(ghc >= 6.10)-    build-depends:-      base >= 4   exposed-modules:  Data.BloomFilter                     Data.BloomFilter.Easy+                    Data.BloomFilter.Mutable                     Data.BloomFilter.Hash   other-modules:    Data.BloomFilter.Array+                    Data.BloomFilter.Mutable.Internal                     Data.BloomFilter.Util   c-sources:        cbits/lookup3.c   ghc-options:      -O2 -Wall   include-dirs:     cbits   includes:         lookup3.h   install-includes: lookup3.h++test-suite tests+  type:           exitcode-stdio-1.0+  hs-source-dirs: tests+  main-is:        QC.hs+  other-modules:  QCSupport+  build-depends:+    QuickCheck >= 2.5,+    base >= 4.4 && < 5,+    bloomfilter,+    bytestring,+    random,+    test-framework,+    test-framework-quickcheck2  source-repository head   type:     git
examples/SpellChecker.hs view
@@ -1,5 +1,5 @@-import Data.BloomFilter.Easy (easyList, notElemB)+import qualified Data.BloomFilter.Easy as BFE  main = do-  filt <- (easyList 0.01 . words) `fmap` readFile "/usr/share/dict/words"-  interact (unlines . filter (`notElemB` filt) . lines)+  filt <- (BFE.easyList 0.01 . words) `fmap` readFile "/usr/share/dict/words"+  interact (unlines . filter (`BFE.notElem` filt) . lines)
examples/Words.hs view
@@ -2,23 +2,23 @@ -- builds a Bloom filter from a list of words, one per line, and -- queries it exhaustively. -module Main () where+module Main (main) where  import Control.Monad (forM_, mapM_)-import Data.BloomFilter (Bloom, fromListB, elemB, lengthB)+import qualified Data.BloomFilter as BF import Data.BloomFilter.Hash (cheapHashes) import Data.BloomFilter.Easy (easyList, suggestSizing) import qualified Data.ByteString.Lazy.Char8 as B import Data.Time.Clock (diffUTCTime, getCurrentTime) import System.Environment (getArgs) -conservative, aggressive :: Double -> [B.ByteString] -> Bloom B.ByteString+conservative, aggressive :: Double -> [B.ByteString] -> BF.Bloom B.ByteString conservative = easyList  aggressive fpr xs     = let (size, numHashes) = suggestSizing (length xs) fpr           k = 3-      in fromListB (cheapHashes (numHashes - k)) (size * k) xs+      in BF.fromList (cheapHashes (numHashes - k)) (size * k) xs  testFunction = conservative @@ -36,6 +36,6 @@     print filt     c <- getCurrentTime     putStrLn $ show (diffUTCTime c b) ++ "s to construct filter"-    {-# SCC "query" #-} mapM_ print $ filter (not . (`elemB` filt)) words+    {-# SCC "query" #-} mapM_ print $ filter (not . (`BF.elem` filt)) words     d <- getCurrentTime     putStrLn $ show (diffUTCTime d c) ++ "s to query every element"
tests/QC.hs view
@@ -1,7 +1,7 @@ module Main where  import Control.Monad (forM_)-import Data.BloomFilter.Easy (easyList, elemB)+import qualified Data.BloomFilter.Easy as B import Data.BloomFilter.Hash (Hashable(..), hash64) import qualified Data.ByteString.Char8 as SB import qualified Data.ByteString.Lazy.Char8 as LB@@ -15,7 +15,7 @@ import QCSupport (P(..))  prop_pai :: (Hashable a) => a -> a -> P -> Bool-prop_pai _ xs (P q) = let bf = easyList q [xs] in xs `elemB` bf+prop_pai _ xs (P q) = let bf = B.easyList q [xs] in xs `B.elem` bf  tests :: [Test] tests = [
tests/QCSupport.hs view
@@ -20,21 +20,6 @@     arbitrary = choose (epsilon, 1 - epsilon)         where epsilon = 1e-6 :: P -instance Arbitrary Ordering where-    arbitrary = oneof [return LT, return GT, return EQ]---- For some reason, MIN_VERSION_random doesn't work here :-(-#if __GLASGOW_HASKELL__ < 704-integralRandomR :: (Integral a, RandomGen g) => (a, a) -> g -> (a, g)-integralRandomR (a,b) g = case randomR (fromIntegral a :: Int,-                                        fromIntegral b :: Int) g-                          of (x,g') -> (fromIntegral x, g')--instance Random Int64 where-  randomR = integralRandomR-  random = randomR (minBound,maxBound)-#endif- instance Arbitrary LB.ByteString where     arbitrary = sized $ \n -> resize (round (sqrt (toEnum n :: Double)))                 ((LB.fromChunks . filter (not . SB.null)) `fmap` arbitrary)