OrderedBits 0.0.1.2 → 0.0.2.0
raw patch · 5 files changed
+392/−331 lines, 5 filesdep ~vectorPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: vector
API changes (from Hackage documentation)
+ Data.Bits.Ordered: subseqBit :: (Ord t, Ranked t) => t -> t -> Maybe (t, t)
+ Data.Bits.Ordered: subsequencesBitsL :: (Ord t, Ranked t) => t -> [t]
+ Data.Bits.Ordered: subsequencesBitsLslow :: (Ord t, Ranked t) => t -> [t]
- Data.Bits.Ordered: popShiftL :: (Ranked t) => t -> t -> t
+ Data.Bits.Ordered: popShiftL :: Ranked t => t -> t -> t
- Data.Bits.Ordered: popShiftR :: (Ranked t) => t -> t -> t
+ Data.Bits.Ordered: popShiftR :: Ranked t => t -> t -> t
Files
- Data/Bits/Ordered.hs +0/−308
- OrderedBits.cabal +27/−23
- changelog.md +15/−0
- lib/Data/Bits/Ordered.hs +344/−0
- tests/properties.hs +6/−0
− Data/Bits/Ordered.hs
@@ -1,308 +0,0 @@---- | Efficiently enumerate the bits in data types in order of population--- count. This yields, say, @000, 001, 010, 100, 011, 101, 110, 111@ (or--- @0, 1, 2, 4, 3, 5, 6, 7@). Another view is of looking at the bits as--- a bitset, first enumerating the empty set, then all 1-element sets, all--- 2-element sets, up to the set size.------ The enumerator can be inlined with @unfoldr@ (of the @vector@ package)--- and is a good producer.------ A memo-table is available, since @popCntSorted@ is still waiting for an--- efficient @popCntEnumerated@ that does not require sorting.--module Data.Bits.Ordered - -- bitset operations- ( lsbZ- , nextActiveZ- , maybeNextActive- , maybeLsb- -- population operations- , popPermutation- , popComplement- -- stream ever larger population counts- , popCntSorted- , popCntMemoInt- , popCntMemoWord- , popShiftL- , popShiftR- -- structures with active bits- , activeBitsL- , activeBitsS- , activeBitsV- ) where--import Control.Arrow-import Data.Bits-import Data.Bits.Extras-import Data.Ord (comparing)-import Data.Vector.Fusion.Util-import Data.Vector.Unboxed (Unbox)-import Data.Word(Word(..))-import qualified Data.Vector.Algorithms.Intro as AI-import qualified Data.Vector.Fusion.Stream.Monadic as SM-import qualified Data.Vector.Generic as VG-import qualified Data.Vector.Unboxed as VU-#if MIN_VERSION_vector(0,11,0)-import Data.Vector.Fusion.Bundle.Size-import qualified Data.Vector.Fusion.Bundle.Monadic as BM-#endif------ * Move from one active bit to the next one---- | Capture the no-bit-set case--captureNull :: Ranked t => t -> (t -> Int) -> Int-captureNull t f = if t==0 then -1 else f t-{-# Inline captureNull #-}---- | Get the lowest active bit. Returns @-1@ if no bit is set.--lsbZ :: Ranked t => t -> Int-lsbZ t = captureNull t lsb-{-# Inline lsbZ #-}---- | Given the currently active bit @k@ and the set @t@, get the next--- active bit. Return @-1@ if there is no next active bit.--nextActiveZ :: Ranked t => Int -> t -> Int-nextActiveZ k t = lsbZ $ (t `shiftR` (k+1)) `shiftL` (k+1)-{-# Inline nextActiveZ #-}---- | Return next active bit, using @Maybe@.--maybeNextActive :: Ranked t => Int -> t -> Maybe Int-maybeNextActive k t = if t'==0 then Nothing else Just (lsb t')- where t' = (t `shiftR` (k+1) `shiftL` (k+1))-{-# Inline maybeNextActive #-}---- | @Maybe@ the lowest active bit.--maybeLsb :: Ranked t => t -> Maybe Int-maybeLsb t = if t==0 then Nothing else Just (lsb t)-{-# Inline maybeLsb #-}---- | List of all active bits, from lowest to highest.--activeBitsL :: Ranked t => t -> [Int]-activeBitsL = unId . SM.toList . activeBitsS-{-# Inline activeBitsL #-}---- | A generic vector (specializes to the corrept type) of the active bits,--- lowest to highest.--activeBitsV :: (Ranked t, VG.Vector v Int) => t -> v Int-#if MIN_VERSION_vector(0,11,0)-activeBitsV = VG.unstream . flip BM.fromStream Unknown . activeBitsS-#else-activeBitsV = VG.unstream . activeBitsS-#endif-{-# Inline activeBitsV #-}---- | A stream with the currently active bits, lowest to highest.--activeBitsS :: (Ranked t, Monad m) => t -> SM.Stream m Int-activeBitsS t = SM.unfoldr (fmap (id &&& (`maybeNextActive` t))) (maybeLsb t)-{-# Inline activeBitsS #-}---- * Population count methods---- | The /slow/ default implementation. We sort the vector, not the list,--- as sorting will walk the whole data structure anyway, and the vector--- requires not as much memory.------ Replaced @popCount &&& id@ as sort, which provides for @a<b@ on equal--- @popCount@ with @popCount &&& activeBitsL@ which sorts according to--- a list of increasing bit indices. Mostly to stay in sync with the @pred@--- / @succ@ functions below.--popCntSorted :: (Unbox n, Integral n, Bits n, Ranked n) => Int -> VU.Vector n-popCntSorted n = VU.modify (AI.sortBy (comparing (popCount &&& activeBitsL))) $ VU.enumFromN 0 (2^n)-{-# Inline popCntSorted #-}---- | Memoized version of 'popCntSorted' for @Int@s.------ NOTE Since this uses @popCntSorted@ for now it will still require a lot--- of memory for sorting the vector!--popCntMemoInt- :: Int -- ^ size of the set we want. If larger than memo limit, will just call 'popCntSorted'- -> VU.Vector Int-popCntMemoInt n- | n>limit = error $ "for safety reasons, memoization is only performed for popcounts up to " ++ show limit ++ " bits, memoize manually!"- | otherwise = _popCntMemoInt !! n- where limit = 28-{-# Inline popCntMemoInt #-}---- | Memoizes popcount arrays. The limit to memoization is enforced by--- popCntMemo, not here.--_popCntMemoInt = map popCntSorted [0..]-{-# NoInline _popCntMemoInt #-}---- | Memoized version of 'popCntSorted' for @Word@s.------ NOTE Since this uses @popCntSorted@ for now it will still require a lot--- of memory for sorting the vector!--popCntMemoWord- :: Int -- ^ size of the set we want. If larger than memo limit, will just call 'popCntSorted'- -> VU.Vector Word-popCntMemoWord n- | n>limit = error $ "for safety reasons, memoization is only performed for popcounts up to " ++ show limit ++ " bits, memoize manually!"- | otherwise = _popCntMemoWord !! n- where limit = 28-{-# Inline popCntMemoWord #-}---- | Memoizes popcount arrays. The limit to memoization is enforced by--- popCntMemo, not here.--_popCntMemoWord = map popCntSorted [0..]-{-# NoInline _popCntMemoWord #-}---- | Enumerate all sets with the same population count. Given a population--- @i@, this returns @Just j@ with @j>i@ (but same number of set bits) or--- @Nothing@. For a population count of @k@, start with @2^(k+1) -1@.------ Note that @sort permutations == sort (nub permutations)@ if--- @permutations@ is a set of all permutations for a given @popCount@--- generated by @popPermutation@. The @Data.List.permutations@ functions--- will create duplicates.------ cf--- <http://en.wikipedia.org/wiki/Permutation#Algorithms_to_generate_permutations>--popPermutation- :: Ranked t- => Int -- size of the set we want. (i.e. numbor of bits available for @0@ or @1@)- -> t -- current population- -> Maybe t -- Just the new population, or nothing if now higher-ordered population exists.-popPermutation !h' !s'- | popCount s' < 1 || h' < 2 = Nothing- | Just k <- findK (h' -2)- , Just l <- findL k (h' -1)- = let swp = setBit (clearBit s' k) l- in Just $ reverseFrom (k+1) (h' -1) swp swp- | otherwise = Nothing- where findK k- | k < 0 = Nothing- | testBit s' k && not (testBit s' (k+1)) = Just k- | otherwise = findK (k-1)- findL k l- | l <= k = Nothing- | not $ testBit s' l = Just l- | otherwise = findL k $ l-1- reverseFrom u d src tgt- | u >= h' = tgt- | otherwise = reverseFrom (u+1) (d-1) src (assignBit (assignBit tgt u (testBit src d)) d (testBit src u))-{-# Inline popPermutation #-}---- | Given a population, get the complement. The first argument is the size--- of the population (say. 8 for 8 bits); the second the current--- population.------ Examples:------ >>> popComplement 5 (3 :: Int)--- 28------ >>> popComplement 6 (3 :: Int)--- 60--popComplement- :: Ranked t- => Int -- size of the population set- -> t -- current population- -> t -- complement of the population. All bits higher than the highest bit are kept zero.-popComplement !h !s = mask .&. complement s- where mask = (2^h -1)-{-# Inline popComplement #-}---- | Move a population more to the left. This, effectively, introduces @0@s--- in the set, whereever the @mask@ has a @0@. Only as many @1@s can be--- set, as the mask holds. Assume that you have a bitmask @mask = 10101@--- and a least-significant aligned population @11@, then given mask and--- population you'd like to see @00101@, i.e. the two lowest one bits of--- the mask are set. @101@ would set the lowest and third one bit.------ Examples:------ >>> popShiftL (21::Int) 3 -- 10101 00011 -- 00101--- 5--- >>> popShiftL (28::Int) 0 -- 11100 00000 -- 00000--- 0--- >>> popShiftL (28::Int) 1 -- 11100 00001 -- 00100--- 4--- >>> popShiftL (28::Int) 2 -- 11100 00010 -- 01000--- 8--- >>> popShiftL (28::Int) 3 -- 11100 00011 -- 01100--- 12--popShiftL- :: (Ranked t)- => t -- the mask- -> t -- the population- -> t -- final population-popShiftL mask lsp = go 0 0 mask lsp where- go !acc !(k::Int) !m !l- | l==0 || m==0 = acc- | testBit m 0- , testBit l 0 = go (acc + bit k) (k+1) (unsafeShiftR m 1) (unsafeShiftR l 1)- | not $ testBit m 0 = go acc (k+1) (unsafeShiftR m 1) l- | not $ testBit l 0 = go acc (k+1) (unsafeShiftR m 1) (unsafeShiftR l 1)-{-# Inline popShiftL #-}---- | Effectively compresses a bitset, given a mask. Removes set elements,--- whenever the mask is @0@, by moving all remaining elements one to the--- right.--popShiftR- :: (Ranked t)- => t -- the mask- -> t -- the population- -> t -- final population-popShiftR mask lsp = go 0 0 mask lsp where- go !acc !k !m !l- | m==0 || l==0 = acc- | testBit m 0- , testBit l 0 = go (acc .|. bit k) (k+1) (m `unsafeShiftR` 1) (l `unsafeShiftR` 1)- | testBit m 0 = go acc (k+1) (m `unsafeShiftR` 1) (l `unsafeShiftR` 1)- | otherwise = go acc k (m `unsafeShiftR` 1) (l `unsafeShiftR` 1)-{-# Inline popShiftR #-}------ WARNING: Conditional compilation based on architecture!--instance Ranked Int where-#if x86_64_HOST_ARCH- lsb = lsb . w64- rank = rank . w64- nlz = nlz . w64-#endif-#if i386_HOST_ARCH- lsb = lsb . w32- rank = rank . w32- nlz = nlz . w32-#endif- {-# Inline lsb #-}- {-# Inline rank #-}- {-# Inline nlz #-}--instance Ranked Word where-#if x86_64_HOST_ARCH- lsb = lsb . w64- rank = rank . w64- nlz = nlz . w64-#endif-#if i386_HOST_ARCH- lsb = lsb . w32- rank = rank . w32- nlz = nlz . w32-#endif- {-# Inline lsb #-}- {-# Inline rank #-}- {-# Inline nlz #-}-
OrderedBits.cabal view
@@ -1,17 +1,17 @@+cabal-version: 2.2 name: OrderedBits-version: 0.0.1.2+version: 0.0.2.0 author: Christian Hoener zu Siederdissen-copyright: Christian Hoener zu Siederdissen, 2014 - 2016+copyright: Christian Hoener zu Siederdissen, 2014 - 2019 homepage: https://github.com/choener/OrderedBits bug-reports: https://github.com/choener/OrderedBits/issues maintainer: choener@bioinf.uni-leipzig.de category: Data-license: BSD3+license: BSD-3-Clause license-file: LICENSE build-type: Simple stability: experimental-cabal-version: >= 1.10.0-tested-with: GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.1+tested-with: GHC == 8.4.4 synopsis: Efficient ordered (by popcount) enumeration of bits description: This library provides efficient methods to enumerate all@@ -30,12 +30,7 @@ -library- build-depends: base >= 4.7 && < 5.0- , bits >= 0.4- , primitive >= 0.5- , vector >= 0.10- , vector-algorithms >= 0.6+common deps default-language: Haskell2010 default-extensions: BangPatterns@@ -43,34 +38,47 @@ , FlexibleContexts , PatternGuards , ScopedTypeVariables- exposed-modules:- Data.Bits.Ordered+ , TemplateHaskell+ , UnicodeSyntax ghc-options: -O2 -funbox-strict-fields +library+ import:+ deps+ build-depends: base >= 4.7 && < 5.0+ , bits >= 0.4+ , primitive >= 0.5+ , vector >= 0.11+ , vector-algorithms >= 0.6+ hs-source-dirs:+ lib+ exposed-modules:+ Data.Bits.Ordered+++ benchmark benchmark+ import:+ deps build-depends: base , criterion >= 1.0.2- , vector -- , OrderedBits- default-language:- Haskell2010 hs-source-dirs: tests main-is: benchmark.hs type: exitcode-stdio-1.0- ghc-options:- -O2- -funbox-strict-fields test-suite properties+ import:+ deps type: exitcode-stdio-1.0 main-is:@@ -79,10 +87,6 @@ -threaded -rtsopts -with-rtsopts=-N hs-source-dirs: tests- default-language:- Haskell2010- default-extensions: TemplateHaskell- , ScopedTypeVariables build-depends: base , QuickCheck >= 2.7 , tasty >= 0.11
changelog.md view
@@ -1,3 +1,18 @@+0.0.2.0+-------++- subsequences of a given mask++0.0.1.4+-------++- added bitmasks++0.0.1.3+-------++- tests with ghc 8.2.x+ 0.0.1.2 -------
+ lib/Data/Bits/Ordered.hs view
@@ -0,0 +1,344 @@++-- | Efficiently enumerate the bits in data types in order of population+-- count. This yields, say, @000, 001, 010, 100, 011, 101, 110, 111@ (or+-- @0, 1, 2, 4, 3, 5, 6, 7@). Another view is of looking at the bits as+-- a bitset, first enumerating the empty set, then all 1-element sets, all+-- 2-element sets, up to the set size.+--+-- The enumerator can be inlined with @unfoldr@ (of the @vector@ package)+-- and is a good producer.+--+-- A memo-table is available, since @popCntSorted@ is still waiting for an+-- efficient @popCntEnumerated@ that does not require sorting.++module Data.Bits.Ordered+ -- bitset operations+ ( lsbZ+ , nextActiveZ+ , maybeNextActive+ , maybeLsb+ -- population operations+ , popPermutation+ , popComplement+ -- stream ever larger population counts+ , popCntSorted+ , popCntMemoInt+ , popCntMemoWord+ , popShiftL+ , popShiftR+ -- structures with active bits+ , activeBitsL+ , activeBitsS+ , activeBitsV+ -- subsequences of populations+ , subseqBit+ , subsequencesBitsL+ , subsequencesBitsLslow+ ) where++import Control.Arrow+import Data.Bits+import Data.Bits.Extras+import Data.List (subsequences,foldl',unfoldr)+import Data.Ord (comparing)+import Data.Vector.Fusion.Bundle.Size+import Data.Vector.Fusion.Util+import Data.Vector.Unboxed (Unbox)+import Data.Word(Word(..))+import qualified Data.Vector.Algorithms.Intro as AI+import qualified Data.Vector.Fusion.Bundle.Monadic as BM+import qualified Data.Vector.Fusion.Stream.Monadic as SM+import qualified Data.Vector.Generic as VG+import qualified Data.Vector.Unboxed as VU++++-- * Move from one active bit to the next one++-- | Capture the no-bit-set case++captureNull ∷ Ranked t ⇒ t → (t → Int) → Int+{-# Inline captureNull #-}+captureNull t f = if t==0 then -1 else f t++-- | Get the lowest active bit. Returns @-1@ if no bit is set.++lsbZ ∷ Ranked t ⇒ t → Int+{-# Inline lsbZ #-}+lsbZ t = captureNull t lsb++-- | Given the currently active bit @k@ and the set @t@, get the next+-- active bit. Return @-1@ if there is no next active bit.+--+-- TODO compare performance of the part right of @$@ with 'clearBit'++nextActiveZ :: Ranked t => Int -> t -> Int+nextActiveZ k t = lsbZ $ (t `shiftR` (k+1)) `shiftL` (k+1)+{-# Inline nextActiveZ #-}++-- | Return next active bit, using @Maybe@.++maybeNextActive :: Ranked t => Int -> t -> Maybe Int+maybeNextActive k t = if t'==0 then Nothing else Just (lsb t')+ where t' = (t `shiftR` (k+1) `shiftL` (k+1))+{-# Inline maybeNextActive #-}++-- | @Maybe@ the lowest active bit.++maybeLsb :: Ranked t => t -> Maybe Int+maybeLsb t = if t==0 then Nothing else Just (lsb t)+{-# Inline maybeLsb #-}++-- | List of all active bits, from lowest to highest.++activeBitsL :: Ranked t => t -> [Int]+activeBitsL = unId . SM.toList . activeBitsS+{-# Inline activeBitsL #-}++-- | A generic vector (specializes to the corrept type) of the active bits,+-- lowest to highest.++activeBitsV :: (Ranked t, VG.Vector v Int) => t -> v Int+activeBitsV = VG.unstream . flip BM.fromStream Unknown . activeBitsS+{-# Inline activeBitsV #-}++-- | A stream with the currently active bits, lowest to highest.++activeBitsS :: (Ranked t, Monad m) => t -> SM.Stream m Int+activeBitsS t = SM.unfoldr (fmap (id &&& (`maybeNextActive` t))) (maybeLsb t)+{-# Inline activeBitsS #-}++-- * Population count methods++-- | The /slow/ default implementation. We sort the vector, not the list,+-- as sorting will walk the whole data structure anyway, and the vector+-- requires not as much memory.+--+-- Replaced @popCount &&& id@ as sort, which provides for @a<b@ on equal+-- @popCount@ with @popCount &&& activeBitsL@ which sorts according to+-- a list of increasing bit indices. Mostly to stay in sync with the @pred@+-- / @succ@ functions below.++popCntSorted :: (Unbox n, Integral n, Bits n, Ranked n) => Int -> VU.Vector n+popCntSorted n = VU.modify (AI.sortBy (comparing (popCount &&& activeBitsL))) $ VU.enumFromN 0 (2^n)+{-# Inline popCntSorted #-}++-- | Memoized version of 'popCntSorted' for @Int@s.+--+-- NOTE Since this uses @popCntSorted@ for now it will still require a lot+-- of memory for sorting the vector!++popCntMemoInt+ :: Int -- ^ size of the set we want. If larger than memo limit, will just call 'popCntSorted'+ -> VU.Vector Int+popCntMemoInt n+ | n>limit = error $ "for safety reasons, memoization is only performed for popcounts up to " ++ show limit ++ " bits, memoize manually!"+ | otherwise = _popCntMemoInt !! n+ where limit = 28+{-# Inline popCntMemoInt #-}++-- | Memoizes popcount arrays. The limit to memoization is enforced by+-- popCntMemo, not here.++_popCntMemoInt = map popCntSorted [0..]+{-# NoInline _popCntMemoInt #-}++-- | Memoized version of 'popCntSorted' for @Word@s.+--+-- NOTE Since this uses @popCntSorted@ for now it will still require a lot+-- of memory for sorting the vector!++popCntMemoWord+ :: Int -- ^ size of the set we want. If larger than memo limit, will just call 'popCntSorted'+ -> VU.Vector Word+popCntMemoWord n+ | n>limit = error $ "for safety reasons, memoization is only performed for popcounts up to " ++ show limit ++ " bits, memoize manually!"+ | otherwise = _popCntMemoWord !! n+ where limit = 28+{-# Inline popCntMemoWord #-}++-- | Memoizes popcount arrays. The limit to memoization is enforced by+-- popCntMemo, not here.++_popCntMemoWord = map popCntSorted [0..]+{-# NoInline _popCntMemoWord #-}++-- | Enumerate all sets with the same population count. Given a population+-- @i@, this returns @Just j@ with @j>i@ (but same number of set bits) or+-- @Nothing@. For a population count of @k@, start with @2^(k+1) -1@.+--+-- Note that @sort permutations == sort (nub permutations)@ if+-- @permutations@ is a set of all permutations for a given @popCount@+-- generated by @popPermutation@. The @Data.List.permutations@ functions+-- will create duplicates.+--+-- cf+-- <http://en.wikipedia.org/wiki/Permutation#Algorithms_to_generate_permutations>++popPermutation+ :: Ranked t+ => Int -- size of the set we want. (i.e. numbor of bits available for @0@ or @1@)+ -> t -- current population+ -> Maybe t -- Just the new population, or nothing if now higher-ordered population exists.+popPermutation !h' !s'+ | popCount s' < 1 || h' < 2 = Nothing+ | Just k <- findK (h' -2)+ , Just l <- findL k (h' -1)+ = let swp = setBit (clearBit s' k) l+ in Just $ reverseFrom (k+1) (h' -1) swp swp+ | otherwise = Nothing+ where findK k+ | k < 0 = Nothing+ | testBit s' k && not (testBit s' (k+1)) = Just k+ | otherwise = findK (k-1)+ findL k l+ | l <= k = Nothing+ | not $ testBit s' l = Just l+ | otherwise = findL k $ l-1+ reverseFrom u d src tgt+ | u >= h' = tgt+ | otherwise = reverseFrom (u+1) (d-1) src (assignBit (assignBit tgt u (testBit src d)) d (testBit src u))+{-# Inline popPermutation #-}++-- | Given a population, get the complement. The first argument is the size+-- of the population (say. 8 for 8 bits); the second the current+-- population.+--+-- Examples:+--+-- >>> popComplement 5 (3 :: Int)+-- 28+--+-- >>> popComplement 6 (3 :: Int)+-- 60++popComplement+ :: Ranked t+ => Int -- size of the population set+ -> t -- current population+ -> t -- complement of the population. All bits higher than the highest bit are kept zero.+popComplement !h !s = mask .&. complement s+ where mask = (2^h -1)+{-# Inline popComplement #-}++-- | Move a population more to the left. This, effectively, introduces @0@s+-- in the set, whereever the @mask@ has a @0@. Only as many @1@s can be+-- set, as the mask holds. Assume that you have a bitmask @mask = 10101@+-- and a least-significant aligned population @11@, then given mask and+-- population you'd like to see @00101@, i.e. the two lowest one bits of+-- the mask are set. @101@ would set the lowest and third one bit.+--+-- Examples:+--+-- >>> popShiftL (21::Int) 3 -- 10101 00011 -- 00101+-- 5+-- >>> popShiftL (28::Int) 0 -- 11100 00000 -- 00000+-- 0+-- >>> popShiftL (28::Int) 1 -- 11100 00001 -- 00100+-- 4+-- >>> popShiftL (28::Int) 2 -- 11100 00010 -- 01000+-- 8+-- >>> popShiftL (28::Int) 3 -- 11100 00011 -- 01100+-- 12++popShiftL+ :: (Ranked t)+ => t -- the mask+ -> t -- the population+ -> t -- final population+popShiftL mask lsp = go 0 0 mask lsp where+ go !acc !(k::Int) !m !l+ | l==0 || m==0 = acc+ | testBit m 0+ , testBit l 0 = go (acc + bit k) (k+1) (unsafeShiftR m 1) (unsafeShiftR l 1)+ | not $ testBit m 0 = go acc (k+1) (unsafeShiftR m 1) l+ | not $ testBit l 0 = go acc (k+1) (unsafeShiftR m 1) (unsafeShiftR l 1)+{-# Inline popShiftL #-}++-- | Effectively compresses a bitset, given a mask. Removes set elements,+-- whenever the mask is @0@, by moving all remaining elements one to the+-- right.++popShiftR+ :: (Ranked t)+ => t -- the mask+ -> t -- the population+ -> t -- final population+popShiftR mask lsp = go 0 0 mask lsp where+ go !acc !k !m !l+ | m==0 || l==0 = acc+ | testBit m 0+ , testBit l 0 = go (acc .|. bit k) (k+1) (m `unsafeShiftR` 1) (l `unsafeShiftR` 1)+ | testBit m 0 = go acc (k+1) (m `unsafeShiftR` 1) (l `unsafeShiftR` 1)+ | otherwise = go acc k (m `unsafeShiftR` 1) (l `unsafeShiftR` 1)+{-# Inline popShiftR #-}++-- | Given a mask, and a packed bit vector, produce the unpacked bit vector,+-- and the next element in order. This is a generator for all possible+-- subsequences given the mask.++subseqBit+ ∷ (Ord t, Ranked t)+ ⇒ t+ -- ^ mask+ → t+ -- ^ packed bit vector+ → Maybe (t,t)+ -- ^ Maybe (unpacked, next packed vector)+{-# Inline subseqBit #-}+subseqBit mask cur+ | cur > limit = Nothing+ | otherwise = Just (popShiftL mask cur,cur+1)+ where !limit = 2 ^ popCount mask - 1++-- | A list of all bitsets given a mask. Like @subsequences@++subsequencesBitsL ∷ (Ord t, Ranked t) ⇒ t → [t]+{-# Inline subsequencesBitsL #-}+subsequencesBitsL t =+ let+ in unfoldr (subseqBit t) zeroBits++-- | A presumably slower version of 'subsequencesBitsL'++subsequencesBitsLslow ∷ (Ord t, Ranked t) ⇒ t → [t]+{-# Inline subsequencesBitsLslow #-}+subsequencesBitsLslow t =+ let as = activeBitsL t+ xs = subsequences as+ setBits = foldl' setBit zeroBits+ in map setBits xs+++-- WARNING: Conditional compilation based on architecture!++instance Ranked Int where+#if x86_64_HOST_ARCH+ lsb = lsb . w64+ rank = rank . w64+ nlz = nlz . w64+#endif+#if i386_HOST_ARCH+ lsb = lsb . w32+ rank = rank . w32+ nlz = nlz . w32+#endif+ {-# Inline lsb #-}+ {-# Inline rank #-}+ {-# Inline nlz #-}++instance Ranked Word where+#if x86_64_HOST_ARCH+ lsb = lsb . w64+ rank = rank . w64+ nlz = nlz . w64+#endif+#if i386_HOST_ARCH+ lsb = lsb . w32+ rank = rank . w32+ nlz = nlz . w32+#endif+ {-# Inline lsb #-}+ {-# Inline rank #-}+ {-# Inline nlz #-}+
tests/properties.hs view
@@ -34,6 +34,7 @@ memoSorted, enumSorted :: Int -> [[Int]] memoSorted b = map sort . groupBy ((==) `on` popCount) $ VU.toList $ popCntMemoInt b+ enumSorted b = map sort $ [0] : [ roll (popPermutation b) (Just $ 2^k-1) | k <- [1..b] ] where roll f (Just k) = k : roll f (f k) roll _ Nothing = []@@ -66,6 +67,11 @@ s = a .&. b l = popShiftL m r r = popShiftR m s++prop_subsequences (a'::Word) = fast == slow+ where fast = sort (subsequencesBitsL a)+ slow = sort (subsequencesBitsLslow a)+ a = a' .&. (2^15 -1)