packages feed

haggle-0.3.1: src/Data/Graph/Haggle/Internal/BitSet.hs

module Data.Graph.Haggle.Internal.BitSet (
  BitSet,
  newBitSet,
  setBit,
  testBit,
  setBitUnsafe,
  testBitUnsafe
  ) where

import Control.Monad.ST
import qualified Data.Bits as B
import Data.Vector.Unboxed.Mutable ( STVector )
import qualified Data.Vector.Unboxed.Mutable as V
import Data.Word ( Word64 )

-- Note that the implementation here assumes thaththe bit numbers are all
-- unsigned.  A proper implementation would perhaps use 'Natural' instead of
-- 'Int', but that would require gratuitous fromEnum/toEnum conversions from all
-- the other API's that just use 'Int', which has about a 33% performance impact
-- when measured.
--
-- The 'setBit' and 'testBit' operations use V.unsafeRead instead of V.read
-- (where the latter is roughly 25% slower) because this is an internal module
-- that is generally always used with a positive 'Int' value, and the value is
-- also checked against 'sz' (which is also probably superfluous).  In other
-- words, this module prioritizes performance over robustness and should only be
-- used when the caller can guarantee positive Int values and otherwise good
-- behavior.


data BitSet s = BS (STVector s Word64) {-# UNPACK #-} !Int

bitsPerWord :: Int
bitsPerWord = 64

-- | Allocate a new 'BitSet' with @n@ bits.  Bits are all
-- initialized to zero.
--
-- > bs <- newBitSet n
newBitSet :: Int -> ST s (BitSet s)
newBitSet n = do
  let nWords = (n `div` bitsPerWord) + 1
  v <- V.replicate nWords 0
  return $ BS v n

-- | Set a bit in the bitset.  Out of range has no effect.
setBit :: BitSet s -> Int -> ST s ()
setBit b@(BS _ sz) bitIx
  | bitIx >= sz = return ()
  | bitIx < 0 = return ()
  | otherwise = setBitUnsafe b bitIx

-- |  Set a bit in the bitset.  The specified bit must be in range.
setBitUnsafe :: BitSet s -> Int -> ST s ()
setBitUnsafe (BS v _) bitIx = do
    let wordIx = bitIx `div` bitsPerWord
        bitPos = bitIx `mod` bitsPerWord
    oldWord <- V.unsafeRead v wordIx
    let newWord = B.setBit oldWord bitPos
    V.write v wordIx newWord

-- | Return True if the bit is set.  Out of range will return False.
testBit :: BitSet s -> Int -> ST s Bool
testBit b@(BS _ sz) bitIx
  | bitIx >= sz = return False
  | bitIx < 0 = return False
  | otherwise = testBitUnsafe b bitIx

-- | Return True if the bit is set.  The specified bit must be in range.
testBitUnsafe :: BitSet s -> Int -> ST s Bool
testBitUnsafe (BS v _) bitIx = do
    let wordIx = bitIx `div` bitsPerWord
        bitPos = bitIx `mod` bitsPerWord
    w <- V.unsafeRead v wordIx
    return $ B.testBit w bitPos