packages feed

perfect-hash-generator 0.2.0.5 → 0.2.0.6

raw patch · 8 files changed

+76/−52 lines, 8 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.PerfectHash.Lookup: lookupPerfect :: (ToHashableChunks a, Unbox b) => LookupTable b -> a -> b
+ Data.PerfectHash.Construction: class Defaultable a
+ Data.PerfectHash.Hashing: class ToHashableChunks a
+ Data.PerfectHash.Hashing: toHashableChunks :: ToHashableChunks a => a -> [Int]
+ Data.PerfectHash.Lookup: lookup :: (ToHashableChunks a, Unbox b) => LookupTable b -> a -> b
- Data.PerfectHash.Construction: createMinimalPerfectHash :: (Unbox b, Defaultable b, ToHashableChunks a, Eq a, Hashable a) => [(a, b)] -> LookupTable b
+ Data.PerfectHash.Construction: createMinimalPerfectHash :: (Unbox b, Defaultable b, ToHashableChunks a, Eq a, Hashable a) => HashMap a b -> LookupTable b

Files

demo/ints/Main.hs view
@@ -2,6 +2,8 @@  import           System.Random                 (RandomGen, mkStdGen, random) +import           Data.HashMap.Strict           (HashMap)+import qualified Data.HashMap.Strict           as HashMap import           Data.IntSet                   (IntSet) import qualified Data.IntSet                   as IntSet import qualified Data.PerfectHash.Construction as Construction@@ -37,8 +39,8 @@       else a (count - 1) (IntSet.insert next_int current_set)  -intMapTuples :: [(Int, Int)]-intMapTuples = zip random_ints [1..]+intMapTuples :: HashMap Int Int+intMapTuples = HashMap.fromList $ zip random_ints [1..]   where     seed_value = RandIntAccum (mkStdGen 0) valueCount IntSet.empty     random_ints = IntSet.toList $ getUniqueRandomIntegers seed_value
demo/strings/Main.hs view
@@ -1,6 +1,7 @@ module Main where  import           Control.Monad                 (when)+import qualified Data.HashMap.Strict           as HashMap  import qualified Data.PerfectHash.Construction as Construction import qualified Data.PerfectHash.Lookup       as Lookup@@ -18,7 +19,7 @@    putStrLn $ unwords ["Words size:", show $ length word_index_tuples] -  let lookup_table = Construction.createMinimalPerfectHash word_index_tuples+  let lookup_table = Construction.createMinimalPerfectHash $ HashMap.fromList word_index_tuples    putStrLn $ unwords [       "Finished computing lookup table with"@@ -30,4 +31,4 @@     putStrLn $ unwords ["Vector G:", show $ Lookup.nonces lookup_table]     putStrLn $ unwords ["Vector V:", show $ Lookup.values lookup_table] -  Exercise.eitherExit $ Exercise.testLookups lookup_table word_index_tuples+  Exercise.eitherExit $ Exercise.testLookups lookup_table $ HashMap.fromList word_index_tuples
perfect-hash-generator.cabal view
@@ -2,16 +2,16 @@ -- -- see: https://github.com/sol/hpack ----- hash: df669bc3ba741914da19be7e67c73565365e07ef3a7f13c93f2d9c51330f48bb+-- hash: 5294c49846f25b6cb06c72295e7bffbdf2443d5e99a7f79652dd5176510b48b5  name:           perfect-hash-generator-version:        0.2.0.5+version:        0.2.0.6 synopsis:       Perfect minimal hashing implementation in native Haskell description:     A <https://en.wikipedia.org/wiki/Perfect_hash_function perfect hash function> for a set @S@ is a hash function that maps distinct elements in @S@ to a set of integers, with __no collisions__. A <https://en.wikipedia.org/wiki/Perfect_hash_function#Minimal_perfect_hash_function minimal perfect hash function> is a perfect hash function that maps @n@ keys to @n@ __consecutive__ integers, e.g. the numbers from @0@ to @n-1@.                 .                 In contrast with the <https://hackage.haskell.org/package/PerfectHash PerfectHash package>, which is a binding to a C-based library, this package is a fully-native Haskell implementation.                 .-                It is intended primarily for generating C code for embedded applications (compare to @<https://www.gnu.org/software/gperf/manual/gperf.html#Search-Structures gperf>@). The output of this tool is a pair of arrays that can be included in generated C code for __<https://en.wikipedia.org/wiki/C_dynamic_memory_allocation allocation>-free hash tables__.+                It is intended primarily for generating C code for embedded applications (compare to @<https://www.gnu.org/software/gperf/manual/gperf.html#Search-Structures gperf>@). The output of this tool is a pair of arrays that can be included in generated C code for __<https://en.wikipedia.org/wiki/C_dynamic_memory_allocation allocation-free> hash tables__.                 .                 Though lookups also perform reasonably well for Haskell applications, it hasn't been benchmarked thorougly with respect to other data structures.                 .@@ -21,6 +21,7 @@                 The library is written generically to hash both strings and raw integers according to the <http://isthe.com/chongo/tech/comp/fnv/ FNV-1a algorithm>. Integers are split by octets before hashing.                 .                 > import Data.PerfectHash.Construction (createMinimalPerfectHash)+                > import qualified Data.HashMap.Strict as HashMap                 >                 > tuples = [                 >    (1000, 1)@@ -28,7 +29,7 @@                 >  , (9876, 3)                 >  ]                 >-                > lookup_table = createMinimalPerfectHash tuples+                > lookup_table = createMinimalPerfectHash $ HashMap.fromList tuples                 .                 Generation of C code based on the arrays in @lookup_table@ is left as an exercise to the reader. Algorithm documentation in the "Data.PerfectHash.Hashing" and "Data.PerfectHash.Lookup" modules will be helpful.                 .
src/Data/PerfectHash/Construction.hs view
@@ -1,10 +1,13 @@ {-# OPTIONS_HADDOCK prune #-} --- | Constructs a minimal perfect hash.+-- | Constructs a minimal perfect hash from a map of key-value pairs. ----- Implementation was transliterated from Python on--- <http://stevehanov.ca/blog/index.php?id=119 Steve Hanov's Blog>--- and then refactored.+-- Implementation was adapted from+-- <http://stevehanov.ca/blog/index.php?id=119 Steve Hanov's Blog>.+--+-- A refactoring of that Python implementation may be found+-- <https://github.com/kostmo/perfect-hash-generator/blob/master/python/perfect-hash.py here>.+-- This Haskell implementation is transliterated from that refactoring. module Data.PerfectHash.Construction (     createMinimalPerfectHash   , Defaultable@@ -38,6 +41,7 @@ emptyLookupTable = NewLookupTable HashMap.empty HashMap.empty  +-- | Used to fill empty slots when promoting a HashMap to a Vector class Defaultable a where   getDefault :: a @@ -60,6 +64,9 @@ -- for every element in this multi-entry bucket, for the given nonce. -- -- Return a Nothing for a slot if it collides.+--+-- This function is able to fail fast if one of the elements of the bucket+-- yields a collision with the new nonce. attemptNonceRecursive :: Hashing.ToHashableChunks a =>      HashMapAndSize Int b   -> Int -- ^ nonce@@ -95,10 +102,10 @@      Int -- ^ nonce to attempt   -> HashMapAndSize Int b   -> [a] -- ^ colliding keys for this bucket-  -> ([Int], Int)+  -> ([(Int, a)], Int) -- ^ slots for each bucket, with the current nonce attempt findNonceForBucket nonce_attempt values_and_size bucket = -  maybe recursive_result (\x -> (x, nonce_attempt)) maybe_attempt_result+  maybe recursive_result (\x -> (zip x bucket, nonce_attempt)) maybe_attempt_result   where     recursive_result = findNonceForBucket (nonce_attempt + 1) values_and_size bucket     maybe_attempt_result = sequenceA $ attemptNonceRecursive@@ -117,17 +124,17 @@   -> (Int, [a])   -> LookupTable b handleMultiBuckets sized_words_dict old_lookup_table (computed_hash, bucket) =-  NewLookupTable new_g new_values+  NewLookupTable new_g new_values_dict   where     HashMapAndSize words_dict size = sized_words_dict      sized_vals_dict = HashMapAndSize (vals old_lookup_table) size-    (slots, nonce) = findNonceForBucket 1 sized_vals_dict bucket+    (slots_for_bucket, nonce) = findNonceForBucket 1 sized_vals_dict bucket -    new_g = HashMap.insert computed_hash nonce (redirs old_lookup_table)-    new_values = foldr fold_func (vals old_lookup_table) $ zip [0..] bucket+    new_g = HashMap.insert computed_hash nonce $ redirs old_lookup_table+    new_values_dict = foldr fold_func (vals old_lookup_table) slots_for_bucket -    fold_func (i, bucket_val) = HashMap.insert (slots !! i) $+    fold_func (slot_val, bucket_val) = HashMap.insert slot_val $       HashMap.lookupDefault (error "not found") bucket_val words_dict  @@ -173,17 +180,17 @@  -- | Generates a minimal perfect hash for a set of key-value pairs. ----- The keys must be 'Foldable's of 'ToNumeric' instances in order to be hashable.+-- The keys must be instances of 'Hashing.ToHashableChunks'. -- The values may be of arbitrary type. ----- /__N.b.__/ It is assumed that the input tuples list has no duplicate keys.+-- A 'HashMap' is required as input to guarantee that there are no duplicate keys. createMinimalPerfectHash :: (Vector.Unbox b, Defaultable b, Hashing.ToHashableChunks a, Eq a, Hashable a) =>-     [(a, b)] -- ^ key-value pairs+     HashMap a b -- ^ key-value pairs   -> Lookup.LookupTable b-createMinimalPerfectHash tuples =+     -- ^ output for use by 'LookupTable.lookup' or a custom code generator+createMinimalPerfectHash words_dict =   convertToVector $ NewLookupTable final_g final_values   where-    words_dict = HashMap.fromList tuples     size = HashMap.size words_dict      sorted_bucket_hash_tuples = preliminaryBucketPlacement words_dict@@ -215,6 +222,8 @@   where     f tuple = HashMap.insertWith (++) (snd tuple) [fst tuple] ++-- * Utility functions  -- | duplicates the argument into both members of the tuple duple :: a -> (a, a)
src/Data/PerfectHash/Hashing.hs view
@@ -1,22 +1,27 @@ {-# OPTIONS_HADDOCK prune #-}  {-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE Safe                 #-} {-# LANGUAGE TypeSynonymInstances #-}   -- | Implements the specialized hash function for -- this perfect hashing algorithm.+--+-- C code that makes use of the perfect hash table output must exactly+-- re-implement this 'hash' function. module Data.PerfectHash.Hashing where  import           Data.Binary          (encode) import           Data.Bits            (xor, (.&.)) import           Data.ByteString.Lazy (unpack) import           Data.Char            (ord)+import           Data.Foldable        (foldl') import           Data.Text            (Text) import qualified Data.Text            as T  --- | This choice of prime number was taken from the Python implementation+-- | This choice of prime number @0x01000193@ was taken from the Python implementation -- on <http://stevehanov.ca/blog/index.php?id=119 Steve Hanov's page>. primeFNV :: Int primeFNV = 0x01000193@@ -26,6 +31,8 @@ mask32bits = 0xffffffff  +-- | Mechanism for a key to be decomposed into units processable by the+-- <http://isthe.com/chongo/tech/comp/fnv/#FNV-1a FNV-1a> hashing algorithm. class ToHashableChunks a where   toHashableChunks :: a -> [Int] @@ -48,7 +55,7 @@   -- | Uses the \"FNV-1a\" algorithm from the--- <http://isthe.com/chongo/tech/comp/fnv/ FNV website>:+-- <http://isthe.com/chongo/tech/comp/fnv/#FNV-1a FNV website>: -- -- > hash = offset_basis -- > for each octet_of_data to be hashed@@ -66,7 +73,7 @@ hash nonce =    -- NOTE: This must be 'foldl', not 'foldr'-  foldl combine d . toHashableChunks+  foldl' combine d . toHashableChunks   where     d = if nonce == 0       then primeFNV
src/Data/PerfectHash/Lookup.hs view
@@ -1,18 +1,17 @@ {-# OPTIONS_HADDOCK prune #-}  -- | Note that what is referred to as a \"nonce\" in this library may be--- equivalently described as a \"salt\" by some.+-- better known as <https://en.wikipedia.org/wiki/Salt_(cryptography) \"salt\">. module Data.PerfectHash.Lookup (-    LookupTable (LookupTable)-  , nonces-  , values+    LookupTable (LookupTable, nonces, values)   , size   , encodeDirectEntry-  , lookupPerfect+  , lookup   ) where  import           Data.Vector.Unboxed      (Vector, (!)) import qualified Data.Vector.Unboxed      as Vector+import           Prelude                  hiding (lookup)  import qualified Data.PerfectHash.Hashing as Hashing @@ -33,12 +32,12 @@     -- Otherwise, the value shall be used as a nonce in a second application of     -- the hashing function to compute the index into the 'values' array.     ---    -- See the documentation of 'lookupPerfect' for details.+    -- See the documentation of 'lookup' for details.   , values :: Vector a     -- ^ An array of values of arbitrary type.     ---    -- The objective of the perfect hash is to efficiently obtain an index into-    -- this array, given the associated key for the value at that index.+    -- The objective of the perfect hash is to efficiently retrieve an index into+    -- this array, given the key associated with the value at that index.   }  @@ -69,11 +68,11 @@ --           respect to the length of the 'values' array. -- --     3. Use the result of (2) as the index into the 'values' array.-lookupPerfect :: (Hashing.ToHashableChunks a, Vector.Unbox b) =>+lookup :: (Hashing.ToHashableChunks a, Vector.Unbox b) =>      LookupTable b   -> a -- ^ key-  -> b-lookupPerfect lookup_table key =+  -> b -- ^ value+lookup lookup_table key =    values lookup_table ! v_key 
test-utils/Exercise.hs view
@@ -4,6 +4,8 @@  import           Control.Monad            (unless) import           Data.Foldable            (traverse_)+import           Data.HashMap.Strict      (HashMap)+import qualified Data.HashMap.Strict      as HashMap import qualified Data.Vector.Unboxed      as Vector  import qualified Data.PerfectHash.Hashing as Hashing@@ -12,10 +14,10 @@  testLookups :: (Show b, Eq b, Show a, Hashing.ToHashableChunks a, Vector.Unbox b) =>      Lookup.LookupTable b-  -> [(a, b)]+  -> HashMap a b   -> Either String () testLookups lookup_table =-  traverse_ check_entry+  traverse_ check_entry . HashMap.toList   where     check_entry (word, source_index) = unless (lookup_result == source_index) $       Left $ unwords [@@ -27,7 +29,7 @@         , show source_index         ]       where-        lookup_result = Lookup.lookupPerfect lookup_table word+        lookup_result = Lookup.lookup lookup_table word   -- | Generate a map of words from a file to their line numbers.
test/Main.hs view
@@ -4,6 +4,8 @@  import           Data.Either                    (isRight) import           Data.Hashable                  (Hashable)+import           Data.HashMap.Strict            (HashMap)+import qualified Data.HashMap.Strict            as HashMap import           Data.Text                      (Text) import qualified Data.Vector.Unboxed            as Vector import           Test.Framework                 (defaultMain, testGroup)@@ -26,32 +28,33 @@     computed_hash = Hashing.hash 0 key  -wordIndexTuplesString :: [(String, Int)]-wordIndexTuplesString = zip [+wordIndexTuplesString :: HashMap String Int+wordIndexTuplesString = HashMap.fromList $ zip [     "apple"   , "banana"   , "carrot"   ] [1..]  -wordIndexTuplesText :: [(Text, Int)]-wordIndexTuplesText = zip [+wordIndexTuplesText :: HashMap Text Int+wordIndexTuplesText = HashMap.fromList $ zip [     "alpha"   , "beta"   , "gamma"   ] [1..]  -intMapTuples :: [(Int, Int)]-intMapTuples = [-    (1000, 1)-  , (5555, 2)-  , (9876, 3)-  ]+intMapTuples :: HashMap Int Int+intMapTuples = HashMap.fromList $ zip [+    1000+  , 5555+  , 9876+  ] [1..]   testHashLookups :: (Show a, Show b, Eq b, Vector.Unbox b, Construction.Defaultable b, Hashing.ToHashableChunks a, Eq a, Hashable a) =>-  [(a, b)] -> IO ()+     HashMap a b+  -> IO () testHashLookups word_index_tuples =   assertBool "Perfect hash lookups failed to match the input" $ isRight test_result_either   where