packages feed

data-sketches-0.4.0.0: test/ThetaSpec.hs

{-# LANGUAGE TypeApplications #-}
module ThetaSpec where

import Control.Monad (forM_)
import Data.Word
import Test.Hspec
import qualified DataSketches.Distinct.Theta as Theta

spec :: Spec
spec = describe "Theta Sketch" $ do
  specify "empty sketch estimates 0" $ do
    sk <- Theta.mkThetaSketch 128
    est <- Theta.estimate sk
    est `shouldBe` 0.0
    Theta.isEmpty sk `shouldReturn` True

  specify "single item estimates 1" $ do
    sk <- Theta.mkThetaSketch 128
    Theta.insert sk 42
    est <- Theta.estimate sk
    est `shouldBe` 1.0
    Theta.isEmpty sk `shouldReturn` False

  specify "exact mode for small cardinality" $ do
    sk <- Theta.mkThetaSketch 4096
    forM_ [1..100 :: Word64] $ Theta.insert sk
    est <- Theta.estimate sk
    est `shouldBe` 100.0

  specify "duplicate items don't increase estimate" $ do
    sk <- Theta.mkThetaSketch 4096
    forM_ [1..50 :: Word64] $ Theta.insert sk
    est1 <- Theta.estimate sk
    forM_ [1..50 :: Word64] $ Theta.insert sk
    est2 <- Theta.estimate sk
    est2 `shouldBe` est1

  specify "1000 distinct items estimates approximately 1000" $ do
    sk <- Theta.mkThetaSketch 4096
    forM_ [1..1000 :: Word64] $ Theta.insert sk
    est <- Theta.estimate sk
    est `shouldSatisfy` (\e -> abs (e - 1000) < 100)

  specify "10000 items with smaller k estimates approximately" $ do
    sk <- Theta.mkThetaSketch 256
    forM_ [1..10000 :: Word64] $ Theta.insert sk
    est <- Theta.estimate sk
    est `shouldSatisfy` (\e -> abs (e - 10000) < 2000)

  describe "union" $ do
    specify "union of disjoint sets" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..500 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      forM_ [501..1000 :: Word64] $ Theta.insert sk2
      u <- Theta.union sk1 sk2
      est <- Theta.estimate u
      est `shouldSatisfy` (\e -> abs (e - 1000) < 100)

    specify "union with empty sketch" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..100 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      u <- Theta.union sk1 sk2
      est <- Theta.estimate u
      est `shouldBe` 100.0

  describe "intersection" $ do
    specify "intersection of overlapping sets" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..200 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      forM_ [101..300 :: Word64] $ Theta.insert sk2
      i <- Theta.intersection sk1 sk2
      est <- Theta.estimate i
      -- 100 items overlap (101..200)
      est `shouldSatisfy` (\e -> abs (e - 100) < 30)

    specify "intersection of disjoint sets is ~0" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..100 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      forM_ [101..200 :: Word64] $ Theta.insert sk2
      i <- Theta.intersection sk1 sk2
      est <- Theta.estimate i
      est `shouldBe` 0.0

    specify "intersection with empty sketch is 0" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..100 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      i <- Theta.intersection sk1 sk2
      est <- Theta.estimate i
      est `shouldBe` 0.0

  describe "difference" $ do
    specify "difference removes overlapping items" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..200 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      forM_ [101..300 :: Word64] $ Theta.insert sk2
      d <- Theta.difference sk1 sk2
      est <- Theta.estimate d
      -- sk1 has 1..200, sk2 has 101..300. A \ B = 1..100
      est `shouldSatisfy` (\e -> abs (e - 100) < 30)

    specify "difference with empty B returns A" $ do
      sk1 <- Theta.mkThetaSketch 4096
      forM_ [1..100 :: Word64] $ Theta.insert sk1
      sk2 <- Theta.mkThetaSketch 4096
      d <- Theta.difference sk1 sk2
      est <- Theta.estimate d
      est `shouldBe` 100.0