packages feed

hw-eliasfano 0.1.1.0 → 0.1.1.1

raw patch · 14 files changed

+418/−278 lines, 14 filesdep +resourcetdep +temporary-resourcetdep ~generic-lensdep ~hw-packed-vectordep ~hw-primPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: resourcet, temporary-resourcet

Dependency ranges changed: generic-lens, hw-packed-vector, hw-prim

API changes (from Hackage documentation)

- HaskellWorks.Data.EliasFano: bucketBitsToHiSegment :: [Bool] -> [Word64]
- HaskellWorks.Data.EliasFano: class FromListWord64 a
- HaskellWorks.Data.EliasFano: class ToListWord64 a
- HaskellWorks.Data.EliasFano: divup :: Word64 -> Word64 -> Word64
- HaskellWorks.Data.EliasFano: fromListWord64 :: FromListWord64 a => [Word64] -> a
- HaskellWorks.Data.EliasFano: hiSegmentToBucketBits :: Word64 -> [Word64] -> [Bool]
- HaskellWorks.Data.EliasFano: instance HaskellWorks.Data.FromListWord64.FromListWord64 HaskellWorks.Data.EliasFano.EliasFano
- HaskellWorks.Data.EliasFano: instance HaskellWorks.Data.ToListWord64.ToListWord64 HaskellWorks.Data.EliasFano.EliasFano
- HaskellWorks.Data.EliasFano: toListWord64 :: ToListWord64 a => a -> [Word64]
- HaskellWorks.Data.EliasFano.Internal: lastMaybe :: [a] -> Maybe a
- HaskellWorks.Data.FromListWord64: class FromListWord64 a
- HaskellWorks.Data.FromListWord64: fromListWord64 :: FromListWord64 a => [Word64] -> a
- HaskellWorks.Data.ToListWord64: class ToListWord64 a
- HaskellWorks.Data.ToListWord64: toListWord64 :: ToListWord64 a => a -> [Word64]
+ HaskellWorks.Data.EliasFano: empty :: EliasFano
+ HaskellWorks.Data.EliasFano: fromWord64s :: [Word64] -> EliasFano
+ HaskellWorks.Data.EliasFano: toWord64s :: EliasFano -> [Word64]
+ HaskellWorks.Data.EliasFano.Internal: bucketBitsToHiSegment :: [Bool] -> [Word64]
+ HaskellWorks.Data.EliasFano.Internal: bucketBoolsToBucketWords :: [Bool] -> Vector Word64
+ HaskellWorks.Data.EliasFano.Internal: bucketWordsToBucketBools :: Count -> Vector Word64 -> [Bool]
+ HaskellWorks.Data.EliasFano.Internal: divup :: Word64 -> Word64 -> Word64
+ HaskellWorks.Data.EliasFano.Internal: foldCountAndLast :: Foldable t => t a -> (Maybe a, Count)
+ HaskellWorks.Data.EliasFano.Internal: hiSegmentToBucketBits :: Word64 -> [Word64] -> [Bool]
+ HaskellWorks.Data.EliasFano.Internal: hiSegmentToWords :: [Word64] -> [Word64]
- HaskellWorks.Data.EliasFano: EliasFano :: !Vector Word64 -> !PackedVector64 -> !Count -> !Count -> EliasFano
+ HaskellWorks.Data.EliasFano: EliasFano :: !CsPoppy -> !PackedVector64 -> !Count -> !Count -> EliasFano
- HaskellWorks.Data.EliasFano: [efBucketBits] :: EliasFano -> !Vector Word64
+ HaskellWorks.Data.EliasFano: [efBucketBits] :: EliasFano -> !CsPoppy

Files

+ app/App/Codec.hs view
@@ -0,0 +1,19 @@+module App.Codec+  ( decodeWord32s+  ) where++import Data.Semigroup ((<>))+import Data.Word++import qualified Data.Binary.Get      as G+import qualified Data.ByteString.Lazy as LBS++decodeWord32s :: LBS.ByteString -> [Word32]+decodeWord32s = fmap (G.runGet G.getWord32le) . go+  where go :: LBS.ByteString -> [LBS.ByteString]+        go lbs = case LBS.splitAt 4 lbs of+          (lt, rt) -> if LBS.length lt == 4+            then lt:go rt+            else if LBS.length lt == 0+              then []+              else [LBS.take 4 (lt <> LBS.replicate 4 0)]
app/App/Commands.hs view
@@ -1,7 +1,8 @@ module App.Commands where +import App.Commands.CreateIndex import App.Commands.LoadSave-import Data.Semigroup        ((<>))+import Data.Semigroup           ((<>)) import Options.Applicative  commands :: Parser (IO ())@@ -11,3 +12,4 @@ commandsGeneral = subparser $ mempty   <>  commandGroup "Commands:"   <>  cmdLoadSave+  <>  cmdCreateIndex
+ app/App/Commands/CreateIndex.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}++module App.Commands.CreateIndex+  ( cmdCreateIndex+  ) where++import App.Codec+import Control.Lens+import Control.Monad.IO.Class+import Control.Monad.Trans.Resource+import Data.Generics.Product.Any+import Data.Semigroup                          ((<>))+import Data.Word+import HaskellWorks.Data.Bits.BitWise+import HaskellWorks.Data.Bits.Log2+import HaskellWorks.Data.PackedVector.Internal+import HaskellWorks.Data.Positioning+import Options.Applicative                     hiding (columns)++import qualified App.Commands.Types                   as Z+import qualified Data.ByteString.Builder              as B+import qualified Data.ByteString.Lazy                 as LBS+import qualified HaskellWorks.Data.EliasFano.Internal as EF+import qualified System.Exit                          as IO+import qualified System.IO                            as IO+import qualified System.IO.Temp                       as RIO++{-# ANN module ("HLint: ignore Reduce duplication"  :: String) #-}+{-# ANN module ("HLint: ignore Redundant do"        :: String) #-}++readWord32s :: MonadIO m => FilePath -> m [Word32]+readWord32s fp = do+  lbs <- liftIO $ LBS.readFile fp++  return (decodeWord32s lbs)++w32To64 :: Word32 -> Word64+w32To64 = fromIntegral++runCreateIndex :: Z.CreateIndexOptions -> IO ()+runCreateIndex opts = runResourceT $ do+  let output = opts ^. the @"output"+  (maybeEnd, count) <- EF.foldCountAndLast <$> readWord32s (opts ^. the @"input")++  case maybeEnd of+    Just end -> do+      let length'   = count+      let loBits'   = fromIntegral (log2 (fromIntegral end `EF.divup` length')) :: Count+      let hiMask    = maxBound .<. loBits' :: Word64+      let loMask    = comp hiMask :: Word64+      his <- fmap ((.>. loBits') . (.&. hiMask) . w32To64) <$> readWord32s (opts ^. the @"input")+      los <- fmap ((.&. loMask)                 . w32To64) <$> readWord32s (opts ^. the @"input")++      let hiWords       = EF.hiSegmentToWords his+      let loWords       = packBits loBits' los+      let efLoBitCount  = loBits'+      let efCount       = length'++      liftIO . IO.putStrLn $ "Lo bit count: " <> show efLoBitCount+      liftIO . IO.putStrLn $ "Entries: " <> show efCount++      (_, _, hHi) <- RIO.openBinaryTempFile Nothing (output <> ".hi")+      (_, _, hLo) <- RIO.openBinaryTempFile Nothing (output <> ".lo")++      liftIO $ LBS.hPut hHi (B.toLazyByteString (foldMap B.word64LE hiWords)) >> IO.hFlush hHi+      liftIO $ LBS.hPut hLo (B.toLazyByteString (foldMap B.word64LE loWords)) >> IO.hFlush hLo++      liftIO $ IO.hSeek hHi IO.AbsoluteSeek 0+      liftIO $ IO.hSeek hLo IO.AbsoluteSeek 0++      hiSize <- liftIO $ IO.hFileSize hHi+      loSize <- liftIO $ IO.hFileSize hLo++      loBytes <- liftIO $ LBS.hGetContents hLo+      hiBytes <- liftIO $ LBS.hGetContents hHi++      liftIO $ LBS.writeFile output $ B.toLazyByteString $ mempty+        <> B.word64LE count+        <> B.word64LE (fromIntegral loSize)+        <> B.word64LE (fromIntegral hiSize)+        <> B.lazyByteString loBytes+        <> B.lazyByteString hiBytes++      return ()++    Nothing -> do+      liftIO $ IO.hPutStrLn IO.stderr "Empty input"+      liftIO   IO.exitFailure++optsCreateIndex :: Parser Z.CreateIndexOptions+optsCreateIndex = Z.CreateIndexOptions+  <$> strOption+        (   long "input"+        <>  short 'i'+        <>  help "Input file of little-endian monotonically increasing word32s"+        <>  metavar "FILE"+        )+  <*> strOption+        (   long "output"+        <>  short 'o'+        <>  help "Output files"+        <>  metavar "FILE"+        )++cmdCreateIndex :: Mod CommandFields (IO ())+cmdCreateIndex = command "create-index"  $ flip info idm $ runCreateIndex <$> optsCreateIndex
app/App/Commands/LoadSave.hs view
@@ -6,14 +6,15 @@   ( cmdLoadSave   ) where +import App.Codec import Control.Lens import Data.Generics.Product.Any-import Data.Semigroup            ((<>))+import Data.Semigroup                       ((<>)) import Data.Word-import Options.Applicative       hiding (columns)+import HaskellWorks.Data.RankSelect.CsPoppy+import Options.Applicative                  hiding (columns)  import qualified App.Commands.Types                            as Z-import qualified Data.Binary.Get                               as G import qualified Data.ByteString.Lazy                          as LBS import qualified Data.Vector.Storable                          as DVS import qualified HaskellWorks.Data.EliasFano                   as EF@@ -23,24 +24,14 @@ {-# ANN module ("HLint: ignore Reduce duplication"  :: String) #-} {-# ANN module ("HLint: ignore Redundant do"        :: String) #-} -decodeWord32s :: LBS.ByteString -> [Word32]-decodeWord32s = fmap (G.runGet G.getWord32le) . go-  where go :: LBS.ByteString -> [LBS.ByteString]-        go lbs = case LBS.splitAt 4 lbs of-          (lt, rt) -> if LBS.length lt == 4-            then lt:go rt-            else if LBS.length lt == 0-              then []-              else [LBS.take 4 (lt <> LBS.replicate 4 0)]- runLoadSave :: Z.LoadSaveOptions -> IO () runLoadSave opts = do-  lbs <- LBS.readFile (opts ^. the @"input")+  let input = opts ^. the @"output"+  lbs <- LBS.readFile input   let ws = fmap fromIntegral (decodeWord32s lbs) :: [Word64]-  let ef = EF.fromListWord64 ws :: EF.EliasFano-  -- let x = DVS.length (PV.swBuffer (EF.efCount ef))+  let ef = EF.fromWord64s ws :: EF.EliasFano   IO.putStrLn $ "Eliasfano:"-    <> " bits: "  <> show (ef & EF.efBucketBits & DVS.length                )+    <> " bits: "  <> show (ef & EF.efBucketBits & csPoppyBits & DVS.length  )     <> " count: " <> show (ef & EF.efLoSegments & PV.swBuffer & DVS.length  )     <> " pv: "    <> show (ef & EF.efCount                                  )     <> " lbc: "   <> show (ef & EF.efLoBitCount                             )
app/App/Commands/Types.hs view
@@ -3,11 +3,17 @@  module App.Commands.Types   ( LoadSaveOptions(..)+  , CreateIndexOptions(..)   ) where  import GHC.Generics  data LoadSaveOptions = LoadSaveOptions+  { input  :: FilePath+  , output :: FilePath+  } deriving (Eq, Show, Generic)++data CreateIndexOptions = CreateIndexOptions   { input  :: FilePath   , output :: FilePath   } deriving (Eq, Show, Generic)
bench/Main.hs view
@@ -9,7 +9,6 @@ import HaskellWorks.Data.Bits.BitWise import HaskellWorks.Data.EliasFano import HaskellWorks.Data.FromForeignRegion-import HaskellWorks.Data.PackedVector import System.Environment import System.IO.MMap @@ -31,17 +30,13 @@   !ibFr  <- mmapFileForeignPtr filename ReadOnly Nothing   let !ib  = fromForeignRegion ibFr  :: DVS.Vector Word64   let !positions = getPositions ib-  let !ef = fromListWord64 positions :: EliasFano-  putStrLn $ "Position count: " <> show (efCount ef)-  putStrLn $ "bucket size: " <> show (8 * DVS.length (efBucketBits ef))-  putStrLn $ "lo size: " <> show (8 * DVS.length (swBuffer (efLoSegments ef)))-  putStrLn $ "efLoBitCount: " <> show (efLoBitCount ef)+  let !_ = fromWord64s positions :: EliasFano   return ()  loadBitString :: FilePath -> IO BS.ByteString loadBitString filepath = do-  (fptr :: ForeignPtr Word8, offset, size) <- mmapFileForeignPtr filepath ReadOnly Nothing-  let !bs = BSI.fromForeignPtr (castForeignPtr fptr) offset size+  (fptr :: ForeignPtr Word8, offset, sz) <- mmapFileForeignPtr filepath ReadOnly Nothing+  let !bs = BSI.fromForeignPtr (castForeignPtr fptr) offset sz   return bs  benchEliasFano :: [Benchmark]
hw-eliasfano.cabal view
@@ -1,20 +1,20 @@ cabal-version:  2.2 -name:           hw-eliasfano-version:        0.1.1.0-synopsis:       Elias-Fano-description:    Please see README.md-category:       Data, Succinct Data Structures, Data Structures-homepage:       http://github.com/haskell-works/hw-eliasfano#readme-bug-reports:    https://github.com/haskell-works/hw-eliasfano/issues-author:         John Ky-maintainer:     newhoggy@gmail.com-copyright:      2016-2019 John Ky-license:        BSD-3-Clause-license-file:   LICENSE-build-type:     Simple-extra-source-files:-    README.md+name:                   hw-eliasfano+version:                0.1.1.1+synopsis:               Elias-Fano+description:            Please see README.md+category:               Data, Succinct Data Structures, Data Structures+homepage:               http://github.com/haskell-works/hw-eliasfano#readme+bug-reports:            https://github.com/haskell-works/hw-eliasfano/issues+author:                 John Ky+maintainer:             newhoggy@gmail.com+copyright:              2016-2019 John Ky+license:                BSD-3-Clause+license-file:           LICENSE+tested-with:            GHC == 8.8.1, GHC == 8.6.5, GHC == 8.4.4, GHC == 8.2.2+build-type:             Simple+extra-source-files:     README.md  source-repository head   type: git@@ -26,109 +26,117 @@ common binary               { build-depends: binary               >= 0.8        && < 0.9    } common criterion            { build-depends: criterion            >= 1.5.5.0    && < 1.6    } common deepseq              { build-depends: deepseq              >= 1.4        && < 1.5    }-common generic-lens         { build-depends: generic-lens         >= 1.1.0.0    && < 1.2    }+common generic-lens         { build-depends: generic-lens         >= 1.2.0.1    && < 1.3    } common hedgehog             { build-depends: hedgehog             >= 0.6        && < 1.1    } common hspec                { build-depends: hspec                >= 2.7.1      && < 3      } common hw-bits              { build-depends: hw-bits              >= 0.7.0.6    && < 0.8    } common hw-hedgehog          { build-depends: hw-hedgehog          >= 0.1.0.3    && < 0.2    } common hw-hspec-hedgehog    { build-depends: hw-hspec-hedgehog    >= 0.1.0.7    && < 0.2    } common hw-int               { build-depends: hw-int               >= 0.0.0.3    && < 0.1    }-common hw-packed-vector     { build-depends: hw-packed-vector     >= 0.0.0.2    && < 0.1    }-common hw-prim              { build-depends: hw-prim              >= 0.6.2.25   && < 0.7    }+common hw-packed-vector     { build-depends: hw-packed-vector     >= 0.0.0.3    && < 0.1    }+common hw-prim              { build-depends: hw-prim              >= 0.6.2.26   && < 0.7    } common hw-rankselect        { build-depends: hw-rankselect        >= 0.13       && < 0.14   } common hw-rankselect-base   { build-depends: hw-rankselect-base   >= 0.3.2.1    && < 0.4    } common lens                 { build-depends: lens                 >= 4          && < 5      } common mmap                 { build-depends: mmap                 >= 0.5.9      && < 0.6    }-common optparse-applicative { build-depends: optparse-applicative >= 0.14       && < 0.15   }+common optparse-applicative { build-depends: optparse-applicative >= 0.14       && < 0.16   }+common resourcet            { build-depends: resourcet            >= 1.2.2      && < 1.3    }+common temporary-resourcet  { build-depends: temporary-resourcet  >= 0.1.0.1    && < 0.2    } common vector               { build-depends: vector               >= 0.12.0.3   && < 0.13   }  common semigroups   { if impl(ghc <  8    ) { build-depends: semigroups     >= 0.16     && < 0.19 } }  common config-  default-language:   Haskell2010-  ghc-options:        -Wall+  default-language:     Haskell2010+  ghc-options:          -Wall  library-  import:   base, config-          , deepseq-          , hw-bits-          , hw-int-          , hw-packed-vector-          , hw-prim-          , hw-rankselect-          , hw-rankselect-base-          , vector-  exposed-modules:-      HaskellWorks.Data.EliasFano-      HaskellWorks.Data.EliasFano.Internal-      HaskellWorks.Data.FromListWord64-      HaskellWorks.Data.ToListWord64-  other-modules:      Paths_hw_eliasfano-  autogen-modules:    Paths_hw_eliasfano-  hs-source-dirs:     src+  import:               base, config+                      , deepseq+                      , hw-bits+                      , hw-int+                      , hw-packed-vector+                      , hw-prim+                      , hw-rankselect+                      , hw-rankselect-base+                      , temporary-resourcet+                      , vector+  exposed-modules:      HaskellWorks.Data.EliasFano+                        HaskellWorks.Data.EliasFano.Internal+  other-modules:        Paths_hw_eliasfano+  autogen-modules:      Paths_hw_eliasfano+  hs-source-dirs:       src  executable hw-eliasfano-  import:   base, config-          , binary-          , bytestring-          , generic-lens-          , hw-packed-vector-          , lens-          , optparse-applicative-          , semigroups-          , vector-  main-is:        Main.hs-  hs-source-dirs: app-  ghc-options:    -threaded -rtsopts -with-rtsopts=-N -O2-  build-depends:  hw-eliasfano-  other-modules:-      App.Commands-      App.Commands.LoadSave-      App.Commands.Types+  import:               base, config+                      , binary+                      , bytestring+                      , generic-lens+                      , hw-bits+                      , hw-packed-vector+                      , hw-prim+                      , hw-rankselect+                      , hw-rankselect-base+                      , lens+                      , optparse-applicative+                      , resourcet+                      , semigroups+                      , temporary-resourcet+                      , vector+  main-is:              Main.hs+  hs-source-dirs:       app+  ghc-options:          -threaded -rtsopts -with-rtsopts=-N -O2+  build-depends:        hw-eliasfano+  other-modules:        App.Codec+                        App.Commands+                        App.Commands.CreateIndex+                        App.Commands.LoadSave+                        App.Commands.Types  test-suite hw-eliasfano-test-  import:   base, config-          , hedgehog-          , hspec-          , hw-bits-          , hw-hedgehog-          , hw-hspec-hedgehog-          , hw-int-          , hw-packed-vector-          , hw-prim-          , vector-  type:               exitcode-stdio-1.0-  main-is:            Spec.hs-  hs-source-dirs:     test-  ghc-options:        -Wall -threaded -rtsopts -with-rtsopts=-N-  build-depends:      hw-eliasfano-  other-modules:-      HaskellWorks.Data.EliasFano.Reference-      HaskellWorks.Data.EliasFano.ReferenceSpec-      HaskellWorks.Data.EliasFanoSpec-      Paths_hw_eliasfano-  autogen-modules:    Paths_hw_eliasfano-  default-language:   Haskell2010-  build-tool-depends: hspec-discover:hspec-discover+  import:               base, config+                      , hedgehog+                      , hspec+                      , hw-bits+                      , hw-hedgehog+                      , hw-hspec-hedgehog+                      , hw-int+                      , hw-packed-vector+                      , hw-prim+                      , hw-rankselect+                      , hw-rankselect-base+                      , vector+  type:                 exitcode-stdio-1.0+  main-is:              Spec.hs+  hs-source-dirs:       test+  ghc-options:          -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:        hw-eliasfano+  other-modules:        HaskellWorks.Data.EliasFano.Reference+                        HaskellWorks.Data.EliasFano.ReferenceSpec+                        HaskellWorks.Data.EliasFanoSpec+                        Paths_hw_eliasfano+  autogen-modules:      Paths_hw_eliasfano+  default-language:     Haskell2010+  build-tool-depends:   hspec-discover:hspec-discover  benchmark bench-  import:   base, config-          , base-          , bytestring-          , criterion-          , hedgehog-          , hspec-          , hw-bits-          , hw-hedgehog-          , hw-hspec-hedgehog-          , hw-int-          , hw-packed-vector-          , hw-prim-          , mmap-          , vector-  type:               exitcode-stdio-1.0-  main-is:            Main.hs-  hs-source-dirs:     bench-  other-modules:      Paths_hw_eliasfano-  build-depends:      hw-eliasfano-  autogen-modules:    Paths_hw_eliasfano+  import:               base, config+                      , base+                      , bytestring+                      , criterion+                      , hedgehog+                      , hspec+                      , hw-bits+                      , hw-hedgehog+                      , hw-hspec-hedgehog+                      , hw-int+                      , hw-packed-vector+                      , hw-prim+                      , mmap+                      , vector+  type:                 exitcode-stdio-1.0+  main-is:              Main.hs+  hs-source-dirs:       bench+  other-modules:        Paths_hw_eliasfano+  build-depends:        hw-eliasfano+  autogen-modules:      Paths_hw_eliasfano
src/HaskellWorks/Data/EliasFano.hs view
@@ -4,35 +4,29 @@  module HaskellWorks.Data.EliasFano   ( EliasFano(..)-  , FromListWord64(..)-  , ToListWord64(..)-  , divup-  , hiSegmentToBucketBits-  , bucketBitsToHiSegment+  , fromWord64s+  , toWord64s+  , empty   , size   ) where  import Control.DeepSeq-import Data.Bits                                 (countLeadingZeros, finiteBitSize)-import Data.Int import Data.Word import GHC.Generics import HaskellWorks.Data.AtIndex                 hiding (end) import HaskellWorks.Data.Bits.BitWise import HaskellWorks.Data.Bits.Log2 import HaskellWorks.Data.EliasFano.Internal-import HaskellWorks.Data.FromListWord64 import HaskellWorks.Data.Positioning import HaskellWorks.Data.RankSelect.Base.Select1-import HaskellWorks.Data.ToListWord64+import HaskellWorks.Data.RankSelect.CsPoppy import Prelude                                   hiding (length, take)  import qualified Data.Vector.Storable                          as DVS import qualified HaskellWorks.Data.PackedVector.PackedVector64 as PV-import qualified Prelude                                       as P  data EliasFano = EliasFano-  { efBucketBits :: !(DVS.Vector Word64)  -- 1 marks bucket, 0 marks skip to next+  { efBucketBits :: !CsPoppy              -- 1 marks bucket, 0 marks skip to next   , efLoSegments :: !PV.PackedVector64    -- Lower segment of each entry   , efLoBitCount :: !Count                -- Number of bits in each lower segment   , efCount      :: !Count                -- Number of entries@@ -43,76 +37,34 @@ size :: EliasFano -> Count size = efCount --- | Calculates ceil (n / d) for small numbers-divup :: Word64 -> Word64 -> Word64-divup n d = fromIntegral (-((-sn) `div` sd)) :: Word64-  where sd = fromIntegral d :: Int64-        sn = fromIntegral n :: Int64--bucketBoolsToBucketWords :: [Bool] -> DVS.Vector Word64-bucketBoolsToBucketWords bs = DVS.unfoldrN ((P.length bs `div` 64) + 1) gen bs-  where gen :: [Bool] -> Maybe (Word64, [Bool])-        gen cs = if not (null cs) then genWord cs 0 0 else Nothing-        genWord :: [Bool] -> Count -> Word64 -> Maybe (Word64, [Bool])-        genWord (True :cs) i acc | i < 64 = genWord cs (i + 1) (acc .|. (1 .<. i))-        genWord (False:cs) i acc | i < 64 = genWord cs (i + 1)  acc-        genWord        cs  _ acc = Just (acc, cs)--bucketWordsToBucketBools :: Count -> DVS.Vector Word64 -> [Bool]-bucketWordsToBucketBools n v = fst (DVS.foldl go (id, n) v) []-  where go :: ([Bool] -> [Bool], Count) -> Word64 -> ([Bool] -> [Bool], Count)-        go (bs, c) w | c > 0 = case goWord c 64 w of-                                (cs, finalCount) -> (bs . cs, finalCount)-        go (bs, _) _         = (bs, 0)-        goWord :: Count -> Count -> Word64 -> ([Bool] -> [Bool], Count)-        goWord c i w | c > 0 && i > 0 = let b = (w .&. 1) /= 0-                                        in case goWord (if b then c - 1 else c) (i - 1) (w .>. 1) of-                                              (bs, finalCount) -> ((b:) . bs, finalCount)-        goWord c _ _                  = (id, c)--hiSegmentToBucketBits :: Word64 -> [Word64] -> [Bool]-hiSegmentToBucketBits lastWord = go 0-  where go :: Word64 -> [Word64] -> [Bool]-        go i []     | i >= lastWord = []-        go i (a:as) | i == a        = True:go i as-        go i (a:as) | i <  a        = False:go (i + 1) (a:as)-        go i []     = False:go (i + 1) []-        go _ (_:_)  = error "Invalid entry"--bucketBitsToHiSegment :: [Bool] -> [Word64]-bucketBitsToHiSegment = go 0-  where go :: Word64 -> [Bool] -> [Word64]-        go _ []          = []-        go i (True:bs)   = i:go  i      bs-        go i (False: bs) =   go (i + 1) bs+empty :: EliasFano+empty = EliasFano+  { efBucketBits  = makeCsPoppy DVS.empty+  , efLoSegments  = PV.empty+  , efLoBitCount  = 0+  , efCount       = 0+  } -instance FromListWord64 EliasFano where-  fromListWord64 ws = case lastMaybe ws of-    Just end' -> EliasFano-      { efBucketBits  = bucketBoolsToBucketWords (hiSegmentToBucketBits (bucketEnd - 1) his)-      , efLoSegments  = PV.fromList loBits' los-      , efLoBitCount  = loBits'-      , efCount       = length'-      }-      where length'   = length ws-            loBits'   = fromIntegral (log2 (end' `divup` length')) :: Count-            hiMask    = maxBound .<. loBits' :: Word64-            loMask    = comp hiMask :: Word64-            his       = (.>. loBits') . (.&. hiMask) <$> ws-            los       = (.&. loMask) <$> ws-            hiEnd     = end' .>. loBits'-            bucketEnd = 1 .<. fromIntegral (finiteBitSize hiEnd - countLeadingZeros hiEnd) :: Word64-    Nothing -> EliasFano-      { efBucketBits  = DVS.empty-      , efLoSegments  = PV.empty-      , efLoBitCount  = 0-      , efCount       = 0-      }+fromWord64s :: [Word64] -> EliasFano+fromWord64s ws = case foldCountAndLast ws of+  (Just end', _) -> EliasFano+    { efBucketBits  = makeCsPoppy . DVS.fromList $ hiSegmentToWords his+    , efLoSegments  = PV.fromList loBits' los+    , efLoBitCount  = loBits'+    , efCount       = length'+    }+    where length'   = length ws+          loBits'   = fromIntegral (log2 (end' `divup` length')) :: Count+          hiMask    = maxBound .<. loBits' :: Word64+          loMask    = comp hiMask :: Word64+          his       = (.>. loBits') . (.&. hiMask) <$> ws+          los       = (.&. loMask) <$> ws+  (Nothing, _) -> empty -instance ToListWord64 EliasFano where-  toListWord64 ef = uncurry combine <$> zip (bucketBitsToHiSegment bucketBits) (PV.toList (efLoSegments ef))-    where combine hi lo = (hi .<. efLoBitCount ef) .|. lo-          bucketBits = bucketWordsToBucketBools (efCount ef) (efBucketBits ef)+toWord64s :: EliasFano -> [Word64]+toWord64s ef = uncurry combine <$> zip (bucketBitsToHiSegment bucketBits) (PV.toList (efLoSegments ef))+  where combine hi lo = (hi .<. efLoBitCount ef) .|. lo+        bucketBits = bucketWordsToBucketBools (efCount ef) (csPoppyBits (efBucketBits ef))  instance Container EliasFano where   type Elem EliasFano = Word64
src/HaskellWorks/Data/EliasFano/Internal.hs view
@@ -1,8 +1,82 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies      #-}+ module HaskellWorks.Data.EliasFano.Internal-  ( lastMaybe+  ( divup+  , hiSegmentToBucketBits+  , bucketBitsToHiSegment+  , bucketBoolsToBucketWords+  , bucketWordsToBucketBools+  , hiSegmentToWords+  , foldCountAndLast   ) where -lastMaybe :: [a] -> Maybe a-lastMaybe (a:as@(b:bs)) = lastMaybe as-lastMaybe [a]           = Just a-lastMaybe _             = Nothing+import Data.Int+import Data.Word+import HaskellWorks.Data.Bits.BitWise+import HaskellWorks.Data.Positioning+import Prelude                        hiding (length, take)++import qualified Data.Vector.Storable as DVS+import qualified Prelude              as P++foldCountAndLast :: Foldable t => t a -> (Maybe a, Count)+foldCountAndLast = foldl go (Nothing, 0)+  where go :: (Maybe a, Count) -> a -> (Maybe a, Count)+        go (_, n) a = (Just a, n + 1)+{-# INLINE foldCountAndLast #-}++-- | Calculates ceil (n / d) for small numbers+divup :: Word64 -> Word64 -> Word64+divup n d = fromIntegral (-((-sn) `div` sd)) :: Word64+  where sd = fromIntegral d :: Int64+        sn = fromIntegral n :: Int64++bucketBoolsToBucketWords :: [Bool] -> DVS.Vector Word64+bucketBoolsToBucketWords bs = DVS.unfoldrN ((P.length bs `div` 64) + 1) gen bs+  where gen :: [Bool] -> Maybe (Word64, [Bool])+        gen cs = if not (null cs) then genWord cs 0 0 else Nothing+        genWord :: [Bool] -> Count -> Word64 -> Maybe (Word64, [Bool])+        genWord (True :cs) i acc | i < 64 = genWord cs (i + 1) (acc .|. (1 .<. i))+        genWord (False:cs) i acc | i < 64 = genWord cs (i + 1)  acc+        genWord        cs  _ acc = Just (acc, cs)++bucketWordsToBucketBools :: Count -> DVS.Vector Word64 -> [Bool]+bucketWordsToBucketBools n v = fst (DVS.foldl go (id, n) v) []+  where go :: ([Bool] -> [Bool], Count) -> Word64 -> ([Bool] -> [Bool], Count)+        go (bs, c) w | c > 0 = case goWord c 64 w of+                                (cs, finalCount) -> (bs . cs, finalCount)+        go (bs, _) _         = (bs, 0)+        goWord :: Count -> Count -> Word64 -> ([Bool] -> [Bool], Count)+        goWord c i w | c > 0 && i > 0 = let b = (w .&. 1) /= 0+                                        in case goWord (if b then c - 1 else c) (i - 1) (w .>. 1) of+                                              (bs, finalCount) -> ((b:) . bs, finalCount)+        goWord c _ _                  = (id, c)++hiSegmentToWords :: [Word64] -> [Word64]+hiSegmentToWords = go 0 0 0+  where go :: Count -> Word64 -> Word64 -> [Word64] -> [Word64]+        go n acc lst us@(v:vs) = if n < 64+          then if lst < v+            then go (n + 1)  acc                (lst + 1) us+            else go (n + 1) (acc .|. (1 .<. n))  v        vs+          else acc:go 0 0 lst us+        go 0 _   _    _         = []+        go _ acc _    _         = [acc]++hiSegmentToBucketBits :: Word64 -> [Word64] -> [Bool]+hiSegmentToBucketBits lastWord = go 0+  where go :: Word64 -> [Word64] -> [Bool]+        go i []     | i >= lastWord = []+        go i (a:as) | i == a        = True:go i as+        go i (a:as) | i <  a        = False:go (i + 1) (a:as)+        go i []     = False:go (i + 1) []+        go _ (_:_)  = error "Invalid entry"++bucketBitsToHiSegment :: [Bool] -> [Word64]+bucketBitsToHiSegment = go 0+  where go :: Word64 -> [Bool] -> [Word64]+        go _ []          = []+        go i (True:bs)   = i:go  i      bs+        go i (False: bs) =   go (i + 1) bs
− src/HaskellWorks/Data/FromListWord64.hs
@@ -1,10 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}--module HaskellWorks.Data.FromListWord64-  ( FromListWord64(..)-  ) where--import Data.Word--class FromListWord64 a where-  fromListWord64 :: [Word64] -> a
− src/HaskellWorks/Data/ToListWord64.hs
@@ -1,8 +0,0 @@-module HaskellWorks.Data.ToListWord64-  ( ToListWord64(..)-  ) where--import Data.Word--class ToListWord64 a where-  toListWord64 :: a -> [Word64]
test/HaskellWorks/Data/EliasFano/Reference.hs view
@@ -3,21 +3,21 @@ module HaskellWorks.Data.EliasFano.Reference   ( EliasFano(..)   , divup+  , fromWord64s+  , toWord64s   , hiSegmentToBucketBits   , bucketBitsToHiSegment   ) where -import Data.Bits                            (countLeadingZeros, finiteBitSize)+import Data.Bits                      (countLeadingZeros, finiteBitSize) import Data.Int import Data.Word-import HaskellWorks.Data.AtIndex            hiding (end)+import HaskellWorks.Data.AtIndex      hiding (end) import HaskellWorks.Data.Bits.BitWise import HaskellWorks.Data.Bits.Log2-import HaskellWorks.Data.EliasFano.Internal-import HaskellWorks.Data.FromListWord64+import HaskellWorks.Data.Foldable import HaskellWorks.Data.Positioning-import HaskellWorks.Data.ToListWord64-import Prelude                              hiding (length, take)+import Prelude                        hiding (length, take)  data EliasFano = EliasFano   { efBucketBits :: [Bool]   -- 1 marks bucket, 0 marks skip to next@@ -48,35 +48,29 @@         go i (True:bs)   = i:go  i      bs         go i (False: bs) =   go (i + 1) bs -instance FromListWord64 EliasFano where-  fromListWord64 ws = case lastMaybe ws of-    Just end' -> EliasFano-      { efBucketBits  = hiSegmentToBucketBits (bucketEnd - 1) his-      , efLoSegments  = los-      , efLoBitCount  = loBits'-      , efCount       = length'-      }-      where length'   = length ws-            loBits'   = fromIntegral (log2 ((end' + 2) `divup` length')) :: Count-            hiMask    = maxBound .<. loBits' :: Word64-            loMask    = comp hiMask :: Word64-            his       = (.>. loBits') . (.&. hiMask) <$> ws-            los       = (.&. loMask) <$> ws-            hiEnd     = end' .>. loBits'-            bucketEnd = 1 .<. fromIntegral (finiteBitSize hiEnd - countLeadingZeros hiEnd) :: Word64-    Nothing -> EliasFano-      { efBucketBits  = []-      , efLoSegments  = []-      , efLoBitCount  = 0-      , efCount       = 0-      }--instance ToListWord64 EliasFano where-  toListWord64 ef = uncurry combine <$> zip (bucketBitsToHiSegment (efBucketBits ef)) (efLoSegments ef)-    where combine hi lo = (hi .<. efLoBitCount ef) .|. lo+fromWord64s :: [Word64] -> EliasFano+fromWord64s ws = case foldLast ws of+  Just end' -> EliasFano+    { efBucketBits  = hiSegmentToBucketBits (bucketEnd - 1) his+    , efLoSegments  = los+    , efLoBitCount  = loBits'+    , efCount       = length'+    }+    where length'   = length ws+          loBits'   = fromIntegral (log2 ((end' + 2) `divup` length')) :: Count+          hiMask    = maxBound .<. loBits' :: Word64+          loMask    = comp hiMask :: Word64+          his       = (.>. loBits') . (.&. hiMask) <$> ws+          los       = (.&. loMask) <$> ws+          hiEnd     = end' .>. loBits'+          bucketEnd = 1 .<. fromIntegral (finiteBitSize hiEnd - countLeadingZeros hiEnd) :: Word64+  Nothing -> EliasFano+    { efBucketBits  = []+    , efLoSegments  = []+    , efLoBitCount  = 0+    , efCount       = 0+    } --- instance AtIndex EliasFano where---   (!!!)   v i = v !! fromIntegral i---   atIndex v i = v !! fromIntegral i---   {-# INLINE (!!!)   #-}---   {-# INLINE atIndex #-}+toWord64s :: EliasFano -> [Word64]+toWord64s ef = uncurry combine <$> zip (bucketBitsToHiSegment (efBucketBits ef)) (efLoSegments ef)+  where combine hi lo = (hi .<. efLoBitCount ef) .|. lo
test/HaskellWorks/Data/EliasFano/ReferenceSpec.hs view
@@ -4,8 +4,6 @@  import Data.Word import HaskellWorks.Data.EliasFano.Reference-import HaskellWorks.Data.FromListWord64-import HaskellWorks.Data.ToListWord64 import HaskellWorks.Hspec.Hedgehog import Hedgehog import Test.Hspec@@ -19,7 +17,7 @@ spec = describe "HaskellWorks.Data.EliasFano.ReferenceSpec" $ do   it "List to EliasFano" $ require $ withTests 1 $ property $ do     ws <- forAll $ pure $ [2, 3, 5, 7, 11, 13, 24]-    let actual = fromListWord64 ws+    let actual = fromWord64s ws     let expected = EliasFano           { efBucketBits  = [ True                             , True@@ -67,11 +65,11 @@           , efLoBitCount  = 2           , efCount       = 7           }-    let actual = toListWord64 ws+    let actual = toWord64s ws     let expected = [2, 3, 5, 7, 11, 13, 24]     actual === expected   it "Round trip" $ require $ property $ do     vs <- forAll $ G.list (R.linear 0 100) (G.word64 (R.linear 1 20))     ws <- forAll $ pure $ drop 1 $ scanl (+) 0xffffffffffffffff vs-    ef :: EliasFano <- forAll $ pure $ fromListWord64 ws-    toListWord64 ef === (ws :: [Word64])+    ef :: EliasFano <- forAll $ pure $ fromWord64s ws+    toWord64s ef === (ws :: [Word64])
test/HaskellWorks/Data/EliasFanoSpec.hs view
@@ -4,7 +4,10 @@  import Data.Word import HaskellWorks.Data.AtIndex+import HaskellWorks.Data.Bits.BitShow import HaskellWorks.Data.EliasFano+import HaskellWorks.Data.EliasFano.Internal+import HaskellWorks.Data.RankSelect.CsPoppy import HaskellWorks.Hspec.Hedgehog import Hedgehog import Test.Hspec@@ -20,9 +23,9 @@ spec = describe "HaskellWorks.Data.EliasFanoSpec" $ do   it "List to EliasFano" $ requireTest $ do     ws <- forAll $ pure $ [2, 3, 5, 7, 11, 13, 24]-    let actual = fromListWord64 ws+    let actual = fromWord64s ws     let expected = EliasFano-          { efBucketBits  = DVS.fromList [4443]+          { efBucketBits  = makeCsPoppy (DVS.fromList [4443])           , efLoSegments  = PV.fromList 2 [2, 3, 1, 3, 3, 1, 0]           , efLoBitCount  = 2           , efCount       = 7@@ -35,12 +38,12 @@     bucketBitsToHiSegment (hiSegmentToBucketBits maxW ws) === ws   it "List to EliasFano" $ requireTest $ do     ws <- forAll $ pure $ EliasFano-          { efBucketBits  = DVS.fromList [4443]+          { efBucketBits  = makeCsPoppy (DVS.fromList [4443])           , efLoSegments  = PV.fromList 2 [2, 3, 1, 3, 3, 1, 0]           , efLoBitCount  = 2           , efCount       = 7           }-    let actual = toListWord64 ws+    let actual = toWord64s ws     let expected = [2, 3, 5, 7, 11, 13, 24]     actual === expected   it "List to EliasFano 2" $ requireTest $ do@@ -61,20 +64,20 @@               , 1446, 1447, 1452, 1477, 1482, 1483, 1486, 1493               , 1498, 1529, 1533, 1534, 1537, 1544, 1549, 1581               ]-    let ef = fromListWord64 ws :: EliasFano+    let ef = fromWord64s ws :: EliasFano     _ <- forAll $ pure ef-    let actual = toListWord64 ef+    let actual = toWord64s ef     let expected = ws     actual === expected   it "Round trip" $ requireProperty $ do     vs <- forAll $ G.list (R.linear 0 100) (G.word64 (R.linear 1 20))     ws <- forAll $ pure $ drop 1 $ scanl (+) 0 vs-    ef :: EliasFano <- forAll $ pure $ fromListWord64 ws-    let actual = toListWord64 ef+    ef :: EliasFano <- forAll $ pure $ fromWord64s ws+    let actual = toWord64s ef     actual === (ws :: [Word64])   it "atIndex" $ requireTest $ do     ef <- forAll $ pure $ EliasFano-          { efBucketBits  = DVS.fromList [4443]+          { efBucketBits  = makeCsPoppy (DVS.fromList [4443])           , efLoSegments  = PV.fromList 2 [2, 3, 1, 3, 3, 1, 0]           , efLoBitCount  = 2           , efCount       = 7@@ -82,3 +85,11 @@     let actual = fmap (atIndex ef) [0 .. end ef - 1]     let expected = [2, 3, 5, 7, 11, 13, 24]     actual === expected+  it "hiSegmentToWords" $ requireTest $ do+    ws <- forAll $ pure $ [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]++    filter (/= ' ') (bitShow (hiSegmentToWords ws)) === concat+      [ "01101010", "01000100", "00010000", "00001000", "00000000", "00100000", "00000000", "00000000"+      , "10000000", "00000000", "00000000", "00000000", "00010000", "00000000", "00000000", "00000000"+      , "00000000", "00000000", "00000000", "00010000", "00000000", "00000000", "00000000", "00000000"+      ]