------------------------------------------------------------------------ -- | -- Module : ALife.Creatur.Genetics.Code -- Copyright : (c) Amy de Buitléir 2011-2013 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- Encoding schemes for genes. -- ------------------------------------------------------------------------ {-# LANGUAGE UnicodeSyntax #-} module ALife.Creatur.Genetics.CodeInternal where import ALife.Creatur.Util (ilogBase, isPowerOf, reverseLookup) import Codec.Gray (grayCodes) import Prelude hiding (cycle) -- | An encoding scheme. data Code a b = Code { cSize ∷ Int, cTable ∷ [(a,[b])] } deriving Show -- | Encodes a value as a sequence of bits. encode ∷ Eq a ⇒ Code a b → a → Maybe [b] encode = flip lookup . cTable ---- | Given a list of encoding schemes paired with genes, encode all of the ---- genes. Unencodable genes will be skipped. --encodeAll ∷ Eq a ⇒ [(Code a, a)] → [b] --encodeAll ps = foldr encodeNext [] ps encodeNext ∷ Eq a ⇒ (Code a b, a) → [b] → [b] encodeNext (c, a) bs = maybe bs (bs ++) (encode c a) -- | Returns the value corresponding to a sequence of bits. decode ∷ Eq b ⇒ Code a b → [b] → Maybe a decode = flip reverseLookup . cTable --decodeAll _ [] = [] --decodeAll bs (c:cs) = g:gs' -- where g = decode c bs1 -- (bs1, bs2) = splitAt (cSize c) bs -- gs' = decodeAll bs2 cs decodeNext ∷ Eq b ⇒ Code a b → [b] → (Maybe a, [b]) decodeNext c bs = (decode c bs1, bs2) where (bs1, bs2) = splitAt (cSize c) bs -- | Convert a list of bits to a string of @0@s and @1@s. asBits ∷ [Bool] → String asBits = map (\b → if b then '1' else '0') -- | Constructs a Gray code for the specified values, using the minimum number -- of bits required to encode all of the values. -- -- If the number of values provided is not a perfect square, some codes will -- not be used; calling @decode@ with those values will return @Nothing@. -- You can find out if this will be the case by calling @'excessGrayCodes'@. -- For example @mkGrayCode [\'a\',\'b\',\'c\']@ would assign the code -- @00@ to @'a'@, @01@ to @'b'@, and @11@ to @'c'@, leaving @10@ unassigned. -- To avoid having unassigned codes, you can repeat a value in the input -- list so the example above could be modified to -- @mkGrayCode [\'a\',\'a\',\'b\',\'c\']@, which would assign the codes -- @00@ and @01@ to 'a', @11@ to @'b'@, and @10@ to @'c'@. -- -- A Gray code maps values to codes in a way that guarantees that the codes -- for two consecutive values will differ by only one bit. This feature -- can be useful in evolutionary programming because the genes resulting -- from a crossover operation will be similar to the inputs. This helps to -- ensure that offspring are similar to their parents, as any radical -- changes from one generation to the next are the result of mutation -- alone. mkGrayCode ∷ [a] → Code a Bool mkGrayCode xs = Code k (zip xs cs) where n = grayCodeLength $ length xs k = (length . head) cs cs = grayCodes n -- | @'grayCodeLength' n@ returns the number of bits required to encode @n@ -- values. grayCodeLength ∷ Int → Int grayCodeLength n = if n `isPowerOf` 2 then k else k + 1 where k = ilogBase (2 ∷ Int) n -- | @'grayCodeCapacity' n@ returns the number of values that can be encoded -- using @n@ bits. The number of values that can be encoded in n bits is -- 2^n. grayCodeCapacity ∷ Int → Int grayCodeCapacity n = 2^n