packages feed

perfect-vector-shuffle 0.1.0 → 0.1.1

raw patch · 4 files changed

+430/−33 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Immutable.Shuffle: derangement :: forall a g. (Eq a, RandomGen g) => Vector a -> g -> (Vector a, g)
+ Immutable.Shuffle: derangementM :: forall m a. (Eq a, MonadRandom m, PrimMonad m) => Vector a -> m (Vector a)
+ Immutable.Shuffle: maximalCycle :: forall a g. RandomGen g => Vector a -> g -> (Vector a, g)
+ Immutable.Shuffle: maximalCycleM :: forall m a. (MonadRandom m, PrimMonad m) => Vector a -> m (Vector a)
+ Immutable.Shuffle: sampleWithoutReplacement :: forall m a. (MonadRandom m, PrimMonad m) => Int -> Vector a -> m (Vector a)
+ Immutable.Shuffle: shuffleK :: forall m a. (MonadRandom m, PrimMonad m) => Int -> Vector a -> m (Vector a)
+ Mutable.Shuffle: derangement :: forall m a g. (PrimMonad m, RandomGen g, Eq a) => MVector (PrimState m) a -> g -> m g
+ Mutable.Shuffle: derangementM :: forall m a. (PrimMonad m, MonadRandom m, Eq a) => MVector (PrimState m) a -> m ()
+ Mutable.Shuffle: maximalCycle :: forall m a g. (PrimMonad m, RandomGen g) => MVector (PrimState m) a -> g -> m g
+ Mutable.Shuffle: maximalCycleM :: forall m a. (PrimMonad m, MonadRandom m) => MVector (PrimState m) a -> m ()
+ Mutable.Shuffle: shuffleK :: forall m a. (PrimMonad m, MonadRandom m) => Int -> MVector (PrimState m) a -> m ()

Files

perfect-vector-shuffle.cabal view
@@ -4,7 +4,7 @@  name:               perfect-vector-shuffle synopsis:           Library for performing vector shuffles-version:            0.1.0+version:            0.1.1  author:             Callan McGill maintainer:         callan.mcgill@gmail.com@@ -14,36 +14,56 @@ license:            BSD-3-Clause description:     .-    This package contains functions for performing shuffles on mutable and-    immutable vectors. The shuffles are uniform at random amongst all+    This package contains functions for performing in-place Fisher--Yates +    shuffles on mutable and immutable vectors along with some related +    functionality. The shuffles are uniform at random amongst all     permuations.     .     For an example of how to use it:     .     @-        module Example where+        module Main where         .         import Data.Vector         import Immutable.Shuffle         .+        main :: IO ()+        main = do {+        shuffleMyVector >>= print;+        cycleMyVector   >>= print;+        derangeMyVector >>= print;+        }+        .         myVector :: Vector Int         myVector = fromList [1..10]         .         shuffleMyVector :: IO (Vector Int)         shuffleMyVector = shuffleM myVector+        .+        cycleMyVector :: IO (Vector Int)+        cycleMyVector = maximalCycleM myVector+        .+        derangeMyVector :: IO (Vector Int)+        derangeMyVector = derangementM myVector     @     .     This gives the following:     .     @-    > shuffleMyVector-    [1,10,4,7,2,3,5,9,8,6]-    > shuffleMyVector-    [7,4,2,10,9,8,6,5,1,3]+    >>> main+      [2,8,1,5,10,9,7,3,6,4]+      [6,8,4,10,9,2,5,7,3,1]+      [8,5,4,1,10,9,3,6,2,7]+    .+    >>> main+      [7,9,3,5,10,6,8,1,2,4]+      [2,4,10,7,8,1,5,9,3,6]+      [4,8,5,2,7,3,9,6,10,1]     @  extra-source-files: CHANGELOG.md + source-repository head   type:     git   location: https://github.com/Boarders/perfect-vector-shuffle@@ -54,6 +74,8 @@   hs-source-dirs:   src    ghc-options:      -Wall+                    -fexpose-all-unfoldings+                    -fspecialize-aggressively    build-depends:    base        ^>= 4.12.0.0                   , MonadRandom >= 0.5.1.1  && < 0.6@@ -78,9 +100,11 @@   hs-source-dirs:   test    ghc-options:      -Wall+                    -fexpose-all-unfoldings+                    -fspecialize-aggressively                     -Wincomplete-patterns -  build-depends:    perfect-vector-shuffle ^>= 0.1.0+  build-depends:    perfect-vector-shuffle ^>= 0.1.1                   , base                   ^>= 4.12.0.0                   , QuickCheck             ^>= 2.12.6.1                   , random                 ^>= 1.1@@ -90,7 +114,27 @@                   , quickcheck-instances   >= 0.3.19 && < 0.4  -  other-modules:-                    Immutable.Test+  other-modules:    Immutable.Test    default-language: Haskell2010+++--Executable example+--+--  main-is:          Example.hs+--+--  hs-source-dirs:   src+--+--  ghc-options:      -Wall+--                    -fexpose-all-unfoldings+--                    -fspecialize-aggressively+--                    -Wincomplete-patterns+--+--  build-depends:    perfect-vector-shuffle ^>= 0.1.1+--                  , base                   ^>= 4.12.0+--                  , MonadRandom >= 0.5.1.1  && < 0.6+--                  , primitive   >= 0.6.4.0  && < 0.7+--                  , random      ^>= 1.1+--                  , vector      >= 0.12.0   && < 0.13+--+--  default-language: Haskell2010
src/Immutable/Shuffle.hs view
@@ -11,15 +11,17 @@ import           Control.Monad.ST        (runST) import           Data.Vector import qualified Mutable.Shuffle         as MS-import           Prelude                 hiding (length)+import           Prelude                 hiding (length, take) import           System.Random           (RandomGen (..))   -- | -- Perform a shuffle on an immutable vector with a given random generator returning a shuffled vector and a new generator.+--+-- This uses the Fisher--Yates--Knuth algorithm. shuffle :: forall a g. RandomGen g => Vector a -> g -> (Vector a, g) shuffle v g-  | length v <= 1 = (v, snd . next $ g)+  | length v <= 1 = (v, g)   | otherwise     =       runST $         do@@ -31,6 +33,8 @@  -- | -- Perform a shuffle on an input immutable vector in a monad which has a source of randomness.+--+-- This uses the Fisher--Yates--Knuth algorithm. shuffleM :: forall m a . (MonadRandom m, PrimMonad m) => Vector a -> m (Vector a) shuffleM v   | length v <= 1 = pure v@@ -38,4 +42,93 @@       do         mutV   <- thaw v         MS.shuffleM mutV+        unsafeFreeze mutV+++-- |+-- Perform a shuffle on the first k elements of a vector in a monad which has a+-- source of randomness.+--+shuffleK :: forall m a . (MonadRandom m, PrimMonad m) => Int -> Vector a -> m (Vector a)+shuffleK k v+  | length v <= 1 = pure v+  | otherwise =+      do+        mutV   <- thaw v+        MS.shuffleK k mutV+        unsafeFreeze mutV+++-- |+-- Get a random sample of k elements without replacement from a vector.+sampleWithoutReplacement :: forall m a . (MonadRandom m, PrimMonad m) => Int -> Vector a -> m (Vector a)+{-# INLINEABLE sampleWithoutReplacement #-}+sampleWithoutReplacement k v = take k <$> shuffleK k v+++-- |+-- Perform an in-place shuffle on an immutable vector wherein the shuffled+-- indices form a maximal cycle.+--+-- This uses the Sattolo algorithm.+maximalCycle :: forall a g. RandomGen g => Vector a -> g -> (Vector a, g)+maximalCycle v g+  | length v <= 1 = (v, g)+  | otherwise     =+      runST $+        do+          mutV   <- thaw v+          newGen <- MS.maximalCycle mutV g+          immutV <- unsafeFreeze mutV+          pure (immutV, newGen)++-- |+-- Perform an in-place shuffle on an immutable vector wherein the shuffled+-- indices form a maximal cycle in a monad with a source of randomness.+--+-- This uses the Sattolo algorithm.+maximalCycleM :: forall m a . (MonadRandom m, PrimMonad m) => Vector a -> m (Vector a)+maximalCycleM v+  | length v <= 1 = pure v+  | otherwise =+      do+        mutV   <- thaw v+        MS.maximalCycleM mutV+        unsafeFreeze mutV+++-- |+-- Perform an in-place  [derangement](https://en.wikipedia.org/wiki/Derangement)+-- on an immutable vector with a given random generator, returning a new random+-- generator.+--+-- __Note:__ It is assumed the input vector consists of distinct values.+--+-- This uses the "early refusal" algorithm.+derangement :: forall a g. (Eq a, RandomGen g) => Vector a -> g -> (Vector a, g)+derangement v g+  | length v <= 1 = (v, g)+  | otherwise     =+      runST $+        do+          mutV   <- thaw v+          newGen <- MS.derangement mutV g+          immutV <- unsafeFreeze mutV+          pure (immutV, newGen)+++-- |+-- Perform an in-place [derangement](https://en.wikipedia.org/wiki/Derangement) on+-- an immutable vector in a monad which has a source of randomness.+--+-- __Note:__ It is assumed the input vector consists of distinct values.+--+-- This uses the "early refusal" algorithm.+derangementM :: forall m a . (Eq a, MonadRandom m, PrimMonad m) => Vector a -> m (Vector a)+derangementM v+  | length v <= 1 = pure v+  | otherwise =+      do+        mutV   <- thaw v+        MS.derangementM mutV         unsafeFreeze mutV
src/Mutable/Shuffle.hs view
@@ -10,21 +10,26 @@ import           Control.Monad.Primitive import           Control.Monad.Random    (MonadRandom (..)) import           Data.Vector.Mutable-import           Prelude                 hiding (length, tail)+import           Prelude                 hiding (length, read, tail) import           System.Random           (RandomGen) import qualified System.Random           as SR + -- | -- Perform a shuffle on a mutable vector with a given random generator, returning a new random generator.+--+-- This uses the Fisher--Yates--Knuth algorithm shuffle   :: forall m a g   . ( PrimMonad m     , RandomGen g     )   => MVector (PrimState m) a -> g -> m g+{-# INLINABLE shuffle #-} shuffle mutV gen = go mutV gen (length mutV - 1)   where     go :: MVector (PrimState m) a -> g -> Int -> m g+    {-# INLINE go #-}     go _ g 0   =  pure g     go v g maxInd =       do@@ -33,17 +38,22 @@         go (tail v) newGen (maxInd - 1)  + -- | -- Perform a shuffle on a mutable vector in a monad which has a source of randomness.+--+-- This uses the Fisher--Yates--Knuth algorithm shuffleM   :: forall m a   . ( PrimMonad m     , MonadRandom m     )   => MVector (PrimState m) a  -> m ()+{-# INLINABLE shuffleM #-} shuffleM mutV = go mutV (length mutV - 1)   where     go :: MVector (PrimState m) a -> Int -> m ()+    {-# INLINE go #-}     go _ 0   =  pure ()     go v maxInd =       do@@ -51,4 +61,170 @@         swap v 0 ind         go (tail v) (maxInd - 1) +{-# SPECIALISE shuffleM :: MVector RealWorld a -> IO () #-} +-- |+-- Shuffle the first k elements of a vector.+--+shuffleK+  :: forall m a+  . ( PrimMonad m+    , MonadRandom m+    )+  => Int -> MVector (PrimState m) a  -> m ()+{-# INLINABLE shuffleK #-}+shuffleK numberOfShuffles mutV = go mutV (numberOfShuffles - 1)+  where+    go :: MVector (PrimState m) a -> Int -> m ()+    {-# INLINE go #-}+    go _ k | k < 0+      = error "Cannot pass negative value to ShuffleK"+    go _ k | k >= length mutV+      = error "Cannot pass value greater than the length of the vector  to ShuffleK"+    go _ 0   =  pure ()+    go v maxInd =+      do+        ind <-  getRandomR (0, maxInd)+        swap v 0 ind+        go (tail v) (maxInd - 1)++++-- |+-- Perform a shuffle on a mutable vector wherein the shuffled indices form a maximal cycle.+--+-- This uses the Sattolo algorithm.+maximalCycle+  :: forall m a g+  . ( PrimMonad m+    , RandomGen g+    )+  => MVector (PrimState m) a -> g -> m g+{-# INLINABLE maximalCycle #-}+maximalCycle mutV gen = go mutV gen (length mutV - 1)+  where+    go :: MVector (PrimState m) a -> g -> Int -> m g+    {-# INLINE go #-}+    go _ g 0      =  pure g+    go v g maxInd =+      do+        let (ind, newGen) :: (Int, g) = SR.randomR (1, maxInd) g+        swap v 0 ind+        go (tail v) newGen (maxInd - 1)+++-- |+-- Perform a shuffle on a mutable vector wherein the shuffled indices form a maximal cycle+-- in a monad with a source of randomness.+--+-- This uses the Sattolo algorithm.+maximalCycleM+  :: forall m a+  . ( PrimMonad m+    , MonadRandom m+    )+  => MVector (PrimState m) a  -> m ()+{-# INLINABLE maximalCycleM #-}+maximalCycleM mutV = go mutV (length mutV - 1)+  where+    go :: MVector (PrimState m) a -> Int -> m ()+    {-# INLINE go #-}+    go _ 0   =  pure ()+    go v maxInd =+      do+        ind <-  getRandomR (1, maxInd)+        swap v 0 ind+        go (tail v) (maxInd - 1)++{-# SPECIALISE maximalCycleM :: MVector RealWorld a -> IO () #-}++++-- |+-- Perform a [derangement](https://en.wikipedia.org/wiki/Derangement) on a mutable vector with a given random generator, returning a new random generator.+--+-- __Note:__ It is assumed the input vector consists of distinct values.+--+-- This uses the "early refusal" algorithm.+derangement+  :: forall m a g+  . ( PrimMonad m+    , RandomGen g+    , Eq a+    )+  => MVector (PrimState m) a -> g -> m g+{-# INLINABLE derangement #-}+derangement mutV gen = do+  mutV_copy <- clone mutV+  go mutV_copy mutV gen 0 (length mutV - 1)+  where+    go :: MVector (PrimState m) a -> MVector (PrimState m) a -> g -> Int -> Int -> m g+    {-# INLINE go #-}+    go c v g lastInd 0 =+      do+        v_last_old <- read c lastInd+        v_last_new <- read v 0+        if v_last_old == v_last_new then+          do+            unsafeCopy mutV c+            go c mutV g 0 (length mutV - 1)+        else+          pure g+    go c v oldGen currInd maxInd =+      do+        let (swapInd, newGen) :: (Int, g) = SR.randomR (0, maxInd) oldGen+        v_old  <- read c currInd+        v_ind  <- read v swapInd+        if v_old == v_ind then+          do+            unsafeCopy mutV c+            go c mutV newGen 0 (length mutV - 1)+        else+          do+            swap v 0 swapInd+            go c (tail v) newGen (currInd + 1) (maxInd - 1)+++-- |+-- Perform a [derangement](https://en.wikipedia.org/wiki/Derangement) on a mutable vector in a monad which has a source of randomness.+--+-- __Note:__ It is assumed the input vector consists of distinct values.+--+-- This uses the "early refusal" algorithm+derangementM+  :: forall m a+  . ( PrimMonad m+    , MonadRandom m+    , Eq a+    )+  => MVector (PrimState m) a -> m ()+{-# INLINABLE derangementM #-}+derangementM mutV = do+  mutV_copy <- clone mutV+  go mutV_copy mutV 0 (length mutV - 1)+  where+    go :: MVector (PrimState m) a -> MVector (PrimState m) a -> Int -> Int -> m ()+    {-# INLINE go #-}+    go c v lastInd 0 =+      do+        v_last_old <- read c lastInd+        v_last_new <- read v 0+        if v_last_old == v_last_new then+          do+            unsafeCopy mutV c+            go c mutV 0 (length mutV - 1)+        else+          pure ()+    go c v currInd maxInd =+      do+        swapInd :: Int <- getRandomR (0, maxInd)+        v_old  <- read c currInd+        v_ind  <- read v swapInd+        if v_old == v_ind then+          do+            unsafeCopy mutV c+            go c mutV 0 (length mutV - 1)+        else+          do+            swap v 0 swapInd+            go c (tail v) (currInd + 1) (maxInd - 1)
test/Immutable/Test.hs view
@@ -5,7 +5,7 @@   ) where  import           Data.List                        (sort)-import           Data.Vector                      (Vector)+import           Data.Vector                      (Vector, (!)) import qualified Data.Vector                      as V import           Immutable.Shuffle import           System.Random@@ -17,40 +17,124 @@  testSuite :: TestTree testSuite = testGroup ""-  [ localOption (QuickCheckTests 1000) shuffleTestSuite-  , localOption (QuickCheckTests 1000) shuffleMTestSuite+  [ localOption (QuickCheckTests 10000) shuffleTestSuite+  , localOption (QuickCheckTests 0    ) performanceTest+  , localOption (QuickCheckTests 10000) maximalCycleTestSuite+  , localOption (QuickCheckTests 10000) derangementTestSuite   ] +performanceTest :: TestTree+performanceTest = testGroup "Performance"+  [ QC.testProperty+      "Shuffling preserves length and elements"+      (monadicIO . sameLength)]  shuffleTestSuite :: TestTree-shuffleTestSuite = testGroup "shuffle"+shuffleTestSuite = testGroup "shuffleM"   [ QC.testProperty-      "Shuffling preserves length and elements"-      (monadicIO . isPermutation @Int)]+      "shuffleM: Shuffling preserves length and elements"+       (monadicIO . isPermutationM @Int)+  , QC.testProperty+      "shuffle: Shuffling preserves length and elements"+       (monadicIO . isPermutation  @Int)+  ] +maximalCycleTestSuite :: TestTree+maximalCycleTestSuite = testGroup "maximalCycleM"+  [ QC.testProperty+      "maximalCycleM: maximal cycle does indeed produce a maximal cycle on [0..n]"+      (monadicIO . isMaximalCycleM)+  , QC.testProperty+      "maximalCycle: maximal cycle does indeed produce a maximal cycle on [0..n]"+      (monadicIO . isMaximalCycle)+  ] -shuffleMTestSuite :: TestTree-shuffleMTestSuite = testGroup "shuffleM"++derangementTestSuite :: TestTree+derangementTestSuite = testGroup "derangementM"   [ QC.testProperty-      "Shuffling preserves length and elements"-      (monadicIO . isPermutationM @Int)]+      "derangementM: derangement does indeed produce a derangment on [0..n]."+      (monadicIO . isDerangementM)+  , QC.testProperty+      "derangement: derangement does indeed produce a derangment on [0..n]."+      (monadicIO . isDerangement)+  ] +sameLength :: () -> PropertyM IO Property+sameLength _ = do+    let v :: Vector Int = V.fromList [1..1000000]+    v' <- run $ shuffleM v+    pure $ length v === length v' -isPermutation :: forall a . (Ord a , Show a, Arbitrary a) => Vector a -> PropertyM IO Property-isPermutation v =++isPermutationM :: forall a . (Ord a , Show a, Arbitrary a) => Vector a -> PropertyM IO Property+isPermutationM v =   do-    gen <- run $ getStdGen-    let v' = fst $ shuffle v gen+    v'  <- run $ shuffleM v     let ls  = V.toList v     let ls' = V.toList v'-    pure $ (sort ls) === (sort ls')+    pure $ sort ls === sort ls'  -isPermutationM :: forall a . (Ord a , Show a, Arbitrary a) => Vector a -> PropertyM IO Property-isPermutationM v =+isPermutation :: forall a . (Ord a , Show a, Arbitrary a) => Vector a -> PropertyM IO Property+isPermutation v =   do-    v'  <- run $ shuffleM v+    g        <- run getStdGen+    let (v', _) = shuffle v g     let ls  = V.toList v     let ls' = V.toList v'-    pure $ (sort ls) === (sort ls')+    pure $ sort ls === sort ls'+++isMaximalCycleM :: Positive Int -> PropertyM IO Property+isMaximalCycleM (Positive n) =+  do+    v <- run $ maximalCycleM (V.fromList [0..n])+    pure $ cycleLength v === (n + 1)++   where+     cycleLength :: Vector Int -> Int+     cycleLength v = go (V.head v) v++     go :: Int -> Vector Int -> Int+     go k xs = if k == 0+                       then 1+                     else+                       1 + go (xs ! k) xs++isMaximalCycle :: Positive Int -> PropertyM IO Property+isMaximalCycle (Positive n) =+  do+    g      <- run getStdGen+    let (v, _) = maximalCycle (V.fromList [0..n]) g+    pure $ cycleLength v === (n + 1)++   where+     cycleLength :: Vector Int -> Int+     cycleLength v = go (V.head v) v++     go :: Int -> Vector Int -> Int+     go k xs = if k == 0+                       then 1+                     else+                       1 + go (xs ! k) xs+++isDerangementM :: Positive Int -> PropertyM IO Property+isDerangementM (Positive n) =+  do+    v <- run $ derangementM (V.fromList [0.. n])+    let perm    = V.indexed v+    let unmoved = V.filter (uncurry (==)) perm+    pure $ null unmoved === True+++isDerangement :: Positive Int -> PropertyM IO Property+isDerangement (Positive n) =+  do+    g      <- run getStdGen+    let (v, _)  = derangement (V.fromList [0.. n]) g+    let perm    = V.indexed v+    let unmoved = V.filter (uncurry (==)) perm+    pure $ null unmoved === True