除非性能很关键,否则我建议对这样的项目使用位向量表示。正如您所发现的,随机访问单个位在打包形式时会有些痛苦,但Data.Vector
为此类任务提供了丰富的功能。
import Data.Bits
import qualified Data.Vector as V
type BitVector = V.Vector Bool
unpack :: (Bits a) => a -> BitVector
unpack w = V.generate (bitSize w) (testBit w)
pack :: (Bits a) => BitVector -> a
pack v = V.ifoldl' set 0 v
where
set w i True = w `setBit` i
set w _ _ = w
mkPermutationVector :: Int -> V.Vector Int
mkPermutationVector d = V.generate (2^d) b
where
b i | i < 2^(d-1) = 2*i
| otherwise = let i' = i-2^(d-1)
in 2*i'+1
permute :: Int -> BitVector -> BitVector
permute d v = V.backpermute v (mkPermutationVector d)
请注意,这如何让您通过密切转录数学描述来指定排列。这大大降低了出错的可能性,并且比乱七八糟的代码更容易编写。
要使用您的示例向量进行测试(以 10 为底):
*Main> import Data.Word
*Main Data.Word> let permute16 = pack . permute 4 . unpack :: Word16 -> Word16
*Main Data.Word> permute16 43690
65280
现在,通过使用位向量作为表示,您会失去很多使用 Haskell 类型免费获得的东西,例如Num
实例。但是,您始终可以Num
为您的表示实现操作;这是一个开始:
plus :: BitVector -> BitVector -> BitVector
plus as bs = V.tail sums
where
(sums, carries) = V.unzip sumsAndCarries
sumsAndCarries = V.scanl' fullAdd (False, False) (V.zip as bs)
fullAdd (_, cin) (a, b) = ((a /= b) /= cin
, (a && b) || (cin && (a /= b)))
您可能还会发现 Levent Erkok 的sbv
包很有用,尽管我不确定它是否公开了与backpermute
您的特定问题一样方便的功能。
更新:我认为这是一个有趣的问题,所以我继续将代码充实为一个库:bit-vector。