packages feed

choose (empty) → 0.1.0.0

raw patch · 6 files changed

+255/−0 lines, 6 filesdep +MonadRandomdep +basesetup-changed

Dependencies added: MonadRandom, base

Files

+ Data/Random/Choose.hs view
@@ -0,0 +1,36 @@+{- |++"Data.Random.Choose" provides an efficient mechanism to select /n/ items+uniformly at random from an input stream, for some fixed /n/.++* "Data.Random.Choose.Tree" contains the implementation details.+* "Data.Random.Choose.IO" puts everything together as a single function+  (using IO).++-}++module Data.Random.Choose+    ( -- * Algorithm outline+      -- $outline+    ) where++import Data.Random.Choose.IO+import Data.Random.Choose.Tree++{- $outline++We store items on a binary tree ('Tree'), moving them down the left or right+branch according to a coin flip. At the end, the rightmost /n/ items on the tree+are selected. Each time we 'insert' into a tree having /n/ items, we prune its+leftmost item ('applyLimit' and 'evict').++It may be helpful to think of this as lazily assigning each item a /d/-bit+score, where /d/ is the maximum depth of the tree. Moving an item to the left+or right corresponds to appending a 0 or 1 to its score. Note that the laziness+is necessary, because /d/ is not known a priori.++The process of moving items futher down the tree is referred to as+disambiguation ('disambiguate') because its purpose is to resolve ties in the+score.++-}
+ Data/Random/Choose/IO.hs view
@@ -0,0 +1,59 @@+{- |++'IO'-specific functionality around the random selection algorithm described in+"Data.Random.Choose".++-}++{-# LANGUAGE ScopedTypeVariables #-}++module Data.Random.Choose.IO ( getSelectionsIO ) where++--------------------------------------------------------------------------------++import qualified Data.Random.Choose.Tree as Tree++import Data.Random.Choose.Tree (Tree)++import Control.Monad.Random   (evalRandIO)++import qualified Data.Foldable as Foldable++import Data.Function ((&))+import Data.List     (sort)++--------------------------------------------------------------------------------++getSelectionsIO :: forall a. (Ord a)+    => IO (Maybe a) -- ^ Producer of items to choose from; produces 'Nothing'+                    --   when there are no more items)+    -> Int          -- ^ /n/: Number of items to choose+    -> IO [a]       -- ^ /n/ of the items (or all of the items if fewer than+                    --   /n/ are produced)++-- ^ Selects /n/ items uniformly at random from an input stream.++--------------------------------------------------------------------------------++getSelectionsIO getItem limit = f 0 Tree.empty+  where++    -- We store each line of text along with its index (i) so that when we're+    -- done, we can sort by the index, thus outputting the selected items in+    -- the same order in which they were read.+    f :: Int -> Tree (Int, a) -> IO [a]+    f i tree = do+        lineMaybe <- getItem+        case lineMaybe of++            -- We read a line; insert it into the tree and recurse.+            Just line -> do+                tree' <- evalRandIO ( tree+                                    & Tree.insert (i, line)+                                    & Tree.applyLimit limit+                                    )+                f (i + 1) tree'++            -- We've reached the end of the input. Convert the tree to a list,+            -- sort it, and strip out the indices to return just the text.+            Nothing -> return $ snd <$> sort (Foldable.toList tree)
+ Data/Random/Choose/Tree.hs view
@@ -0,0 +1,115 @@+{- |++Defines the 'Tree' data structure and operations on it to implement the random+selection algorithm described in "Data.Random.Choose".++-}++module Data.Random.Choose.Tree+    ( Tree(..), empty, insert, applyLimit, evict, disambiguate ) where++--------------------------------------------------------------------------------++import Control.Monad.Random (Rand, RandomGen, getRandom)++--------------------------------------------------------------------------------++data Tree a = Nil | Tree+    { treeSize   :: Int    -- ^ Total number of items at this node and below+    , treeValues :: [a]    -- ^ Items at this node+    , treeLeft   :: Tree a -- ^ Left subtree (less likely for inclusion)+    , treeRight  :: Tree a -- ^ Right subtree (more likely for inclusion)+    }+-- ^ A binary tree with arbitrarily many values at each node.++instance Foldable Tree where++    foldr _ z Nil = z+    foldr f z (Tree size (x:xs) left right) =+        foldr f (f x z) (Tree (size - 1) xs left right)+    foldr f z (Tree size [] left right) =+        (\z -> foldr f z left) . (\z -> foldr f z right) $ z++    length Nil      = 0+    length t@Tree{} = treeSize t++    null Nil      = True+    null t@Tree{} = treeSize t == 0++empty :: Tree a+-- ^ A tree with no elements.++insert :: a -> Tree a -> Tree a+-- ^ Trivial insertion into the root of a tree, increasing its size by 1+--   and leaving its children unmodified.++applyLimit :: (RandomGen g)+    => Int -- ^ @limit@+    -> Tree a+    -> Rand g (Tree a)+-- ^ Remove items from the tree until its size is at most @limit@.+--   This may involve disambiguation if eviction takes place.++evict :: (RandomGen g) => Tree a -> Rand g (Tree a)+-- ^ Remove one item from the tree (or leave the tree unmodified if it is+--   already empty). This may involve disambiguation if there is not already+--   a clear leftmost item.++disambiguate :: (RandomGen g) => Tree a -> Rand g (Tree a)+-- ^ Perform disambiguation at the root level only, pushing items from+--   the root down into subtrees as necessary.++--------------------------------------------------------------------------------++empty = Nil++insert x Nil = Tree 1 [x] Nil Nil++insert x (Tree size xs left right) = Tree (size + 1) (x:xs) left right++applyLimit limit _ | limit <= 0 = pure Nil++applyLimit limit tree++    -- If the tree is small enough: We don't need to do anything.+    | length tree <= limit = pure tree++    -- If the tree is oversized: Remove an item from it, and recurse.+    | otherwise = applyLimit limit =<< evict tree++evict tree | length tree <= 1 = pure Nil++evict tree = do+    (Tree _ _ left right) <- disambiguate tree++    -- Evict from one of the subtrees, preferring to evict from the left.+    (left', right') <- if not . null $ left+                       then (\x -> (x, right)) <$> evict left+                       else (\x -> (left, x))  <$> evict right++    return $ Tree (length left' + length right') [] left' right'++-- For a tree with no items at the root, no disambiguation is possible+-- (remember that disambiguate operates at the root only).+disambiguate tree@(Tree _ [] _ _) = pure tree++-- For a tree which contains a single item and no children, no+-- disambiguation is required.+disambiguate tree@(Tree _ [_] Nil Nil) = pure tree++-- There is at least one item at the root that needs to be pushed down, to+-- disambiguate it (either from items in subtrees, or from other items at+-- the root).+disambiguate (Tree size (x:xs) left right) = do++    -- Randomly decide whether to push x into the left or right subtree.+    b <- getRandom+    let (left', right') = if b+                          then (insert x left, right)+                          else (left, insert x right)++    -- In tree', a single item from the original tree has been pushed down.+    let tree' = Tree size xs left' right'++    -- There still may be other items at the root, so recurse.+    disambiguate tree'
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ choose.cabal view
@@ -0,0 +1,30 @@+-- This file has been generated from package.yaml by hpack version 0.14.0.+--+-- see: https://github.com/sol/hpack++name:           choose+version:        0.1.0.0+synopsis:       Choose random elements from a stream.+description:    Provides an efficient mechanism to select /n/ items uniformly at random from an input stream, for some fixed /n/.+category:       Random+author:         Chris Martin <ch.martin@gmail.com>+maintainer:     Chris Martin <ch.martin@gmail.com>+license:        Apache-2.0+license-file:   license.txt+build-type:     Simple+cabal-version:  >= 1.10++library+  hs-source-dirs:+      .+  ghc-options: -Wall -fwarn-unused-imports+  build-depends:+      base >= 4.7 && < 5+    , MonadRandom+  exposed-modules:+      Data.Random.Choose+      Data.Random.Choose.Tree+      Data.Random.Choose.IO+  other-modules:+      Paths_choose+  default-language: Haskell2010
+ license.txt view
@@ -0,0 +1,13 @@+Copyright 2016 Chris Martin++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.