data-sketches-0.4.0.0: src/DataSketches/Distinct/HyperLogLog.hs
-- | HyperLogLog sketch for cardinality (distinct count) estimation.
--
-- "How many /different/ things have I seen?" — not how many total, but how
-- many unique. Think unique visitors, unique IPs, unique search queries.
-- Counting exactly requires remembering every item you've seen (a set), which
-- grows with the data. HyperLogLog answers the same question using a few
-- kilobytes, no matter how large the stream.
--
-- It works by hashing each item and tracking the maximum number of leading
-- zeros observed across 2^p independent register buckets.
--
-- The standard error is approximately @1.04 / sqrt(2^p)@.
--
-- === Precision configurations
--
-- +------+-----------+------+-------------+
-- | @p@ | Registers | RAM | Std. error |
-- +======+===========+======+=============+
-- | 10 | 1,024 | 1 KB | ~3.25% |
-- +------+-----------+------+-------------+
-- | 12 | 4,096 | 4 KB | ~1.63% |
-- +------+-----------+------+-------------+
-- | 14 | 16,384 | 16 KB| ~0.81% |
-- +------+-----------+------+-------------+
-- | 16 | 65,536 | 64 KB| ~0.41% |
-- +------+-----------+------+-------------+
--
-- === Hashing
--
-- Items must be pre-hashed to 'Word64'. Apply a good hash function (e.g.
-- from @hashable@ or @xxhash@) to your domain types before calling 'insert'.
-- The quality of the cardinality estimate depends on the hash being uniform.
--
-- === Implementation
--
-- Backed by a C implementation (@cbits\/hll.c@) behind a 'ForeignPtr'.
-- All sketch memory lives outside the GHC heap.
--
-- === Usage
--
-- @
-- import qualified DataSketches.Distinct.HyperLogLog as HLL
-- import Data.Hashable (hash)
--
-- main :: IO ()
-- main = do
-- sk <- HLL.'mkHllSketch' 12
-- mapM_ (HLL.'insert' sk . fromIntegral . hash) [\"alice\", \"bob\", \"alice\"]
-- n <- HLL.'estimate' sk
-- putStrLn $ "distinct count ≈ " ++ show n -- ≈ 2.0
-- @
--
-- === Mergeability
--
-- Fully mergeable via 'merge'. The union of two HLL sketches gives the same
-- result as inserting all items from both streams into a single sketch. Both
-- must share the same precision @p@.
--
-- === When to use Theta instead
--
-- HyperLogLog only supports cardinality estimation and union. If you need
-- set intersection or difference, use "DataSketches.Distinct.Theta".
module DataSketches.Distinct.HyperLogLog
( -- * Construction
HllSketch
, mkHllSketch
-- * Updating
, insert
, insertBatch
, merge
-- * Querying
, estimate
, precision
) where
import Control.Monad.Primitive (PrimMonad, PrimState)
import qualified Data.Vector.Storable as VS
import Data.Word (Word64)
import DataSketches.Distinct.HyperLogLog.Internal
-- | Insert an item into the sketch. The item should be a hash of the original value.
insert :: PrimMonad m => HllSketch (PrimState m) -> Word64 -> m ()
insert = hllInsert
-- | Bulk-insert a storable vector of hashed items. Avoids per-element FFI overhead.
insertBatch :: PrimMonad m => HllSketch (PrimState m) -> VS.Vector Word64 -> m ()
insertBatch = hllInsertBatch
-- | Estimate the number of distinct items inserted.
estimate :: PrimMonad m => HllSketch (PrimState m) -> m Double
estimate = hllEstimate
-- | Merge the second sketch into the first. Both must have the same precision.
merge :: PrimMonad m => HllSketch (PrimState m) -> HllSketch (PrimState m) -> m ()
merge = hllMerge
-- | Get the precision (log2 of register count) of the sketch.
precision :: PrimMonad m => HllSketch (PrimState m) -> m Int
precision = hllPrecision