-- | This module is not used by the main library for constructing perfect hashes; -- it just provides convenience functions for use by the tests and demonstration executables. module Exercise where import Control.Monad (unless) import Data.Foldable (traverse_) import qualified Data.Map as Map import Data.Map (Map) import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet import System.Random (RandomGen, mkStdGen, random) import qualified Data.PerfectHash.Hashing as Hashing import qualified Data.PerfectHash.Lookup as Lookup -- | genericized to facilitate benchmarking testLookupsHelper :: (Show b, Eq b, Show a, Hashing.ToHashableChunks a) => (a -> b) -- ^ lookup function -> Map a b -> Either String () testLookupsHelper lookup_function = traverse_ check_entry . Map.toList where check_entry (word, source_index) = unless (lookup_result == source_index) $ Left $ unwords [ "Result for key" , show word , "had incorrect index" , show lookup_result , "; should have been" , show source_index ] where lookup_result = lookup_function word testHashMapLookups :: (Show b, Eq b, Show a, Ord a, Hashing.ToHashableChunks a) => Map a b -> Either String () testHashMapLookups hash_map = testLookupsHelper (\x -> Map.findWithDefault (error "not found") x hash_map) hash_map testPerfectLookups :: (Show b, Eq b, Show a, Hashing.ToHashableChunks a) => Lookup.LookupTable b -> Map a b -> Either String () testPerfectLookups = testLookupsHelper . Lookup.lookup -- | Generate a map of words from a file to their line numbers. -- -- Intended for use with @\"/usr/share/dict/words\"@. wordsFromFile :: FilePath -> IO [(String, Int)] wordsFromFile path = do file_lines <- readFile path return $ zip (lines file_lines) [1..] -- * Random integers data RandIntAccum t = RandIntAccum t -- ^ random number generator Int -- ^ max count IntSet -- ^ accumulated unique random numbers mkIntMapTuples :: Int -> Map Int Int mkIntMapTuples valueCount = Map.fromList $ zip random_ints [1..] where seed_value = RandIntAccum (mkStdGen 0) valueCount IntSet.empty random_ints = IntSet.toList $ getUniqueRandomIntegers seed_value -- | Since computing the size of the set is O(N), we -- maintain the count separately. getUniqueRandomIntegers :: RandomGen t => RandIntAccum t -> IntSet getUniqueRandomIntegers (RandIntAccum std_gen count current_set) = if count == 0 then current_set else getUniqueRandomIntegers newstate where (next_int, next_std_gen) = random std_gen a = RandIntAccum next_std_gen newstate = if IntSet.member next_int current_set then a count current_set else a (count - 1) (IntSet.insert next_int current_set) -- * Other utilities eitherExit :: Either String b -> IO () eitherExit x = case x of Left err -> error err Right _ -> return ()