作为学校项目的一部分,我正在Haskell中实现一些密码算法。你可能知道这涉及很多低级别的小提琴。现在我被困在一个特殊的子程序上,这让我很头疼。该例程是256位的置换,其工作原理如下:
输入:256位块。
然后,输入块中的所有偶数位(0,2,...)被视为输出块中的前128位。而奇数位被认为是输出块中的128个最后位。更具体地说,输出中 i'th 位的公式为(a i 是输入中的 i'th 位块,而b是输出):
b i = a 2i
b i + 2 d-1 = a 2i + 1
i 从0到2 d-1 -1,d = 8.
作为一个玩具示例,假设我们使用了一个简化版本的例程,该例程使用16位块而不是256位。然后,以下位串将被置换如下:
1010 1010 1010 1010 - > 1111 1111 0000 0000
我无法为此功能提供干净的实现。特别是我一直在尝试使用ByteString - > ByteString签名,但这种强迫我使用Word8的粒度。但是输出字节串中的每个字节都是所有其他字节中的位的函数,这需要一些非常混乱的操作。
对于如何解决这个问题,我会非常感激。
答案 0 :(得分:4)
这应该有效:
import Data.List
import Data.Function
map fst $ sortBy (compare `on` snd) $ zip yourList $ cycle [0,1]
一点解释: 由于sortBy保留原始顺序,我们可以将每个值在偶数位置配对“0”,将每个值在奇数位置配对为“1”,然后我们只需对该对的第二个值进行排序。因此,偶数位置的所有值都将放在奇数位置的值之前,但它们的顺序将被保留。
克里斯
答案 1 :(得分:4)
如果你想要一个有效的实现,我认为你不能避免使用字节。这是一个示例解决方案。它假定ByteString中始终存在偶数个字节。我对拆箱或严格调整不太熟悉,但我认为如果你想要非常高效,这些是必要的。
import Data.ByteString (pack, unpack, ByteString)
import Data.Bits
import Data.Word
-- the main attraction
packString :: ByteString -> ByteString
packString = pack . packWords . unpack
-- main attraction equivalent, in [Word8]
packWords :: [Word8] -> [Word8]
packWords ws = evenPacked ++ unevenPacked
where evenBits = map packEven ws
unevenBits = map packUneven ws
evenPacked = consumePairs packNibbles evenBits
unevenPacked = consumePairs packNibbles unevenBits
-- combines 2 low nibbles (first 4 bytes) into a (high nibble, low nibble) word
-- assumes that only the low nibble of both arguments can be non-zero.
packNibbles :: Word8 -> Word8 -> Word8
packNibbles w1 w2 = (shiftL w1 4) .|. w2
packEven w = packBits w [0, 2, 4, 6]
packUneven w = packBits w [1, 3, 5, 7]
-- packBits 254 [0, 2, 4, 6] = 14
-- packBits 254 [1, 3, 5, 7] = 15
packBits :: Word8 -> [Int] -> Word8
packBits w is = foldr (.|.) 0 $ map (packBit w) is
-- packBit 255 0 = 1
-- packBit 255 1 = 1
-- packBit 255 2 = 2
-- packBit 255 3 = 2
-- packBit 255 4 = 4
-- packBit 255 5 = 4
-- packBit 255 6 = 8
-- packBit 255 7 = 8
packBit :: Word8 -> Int -> Word8
packBit w i = shiftR (w .&. 2^i) ((i `div` 2) + (i `mod` 2))
-- sort of like map, but halves the list in size by consuming two elements.
-- Is there a clearer way to write this with built-in function?
consumePairs :: (a -> a -> b) -> [a] -> [b]
consumePairs f (x : x' : xs) = f x x' : consumePairs f xs
consumePairs _ [] = []
consumePairs _ _ = error "list must contain even number of elements"
答案 2 :(得分:3)
除非性能至关重要,否则我建议对这样的项目使用位向量表示。正如您所发现的那样,当它们处于打包状态时,随机访问各个位是一种痛苦,但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)
请注意,这可以让您通过密切抄录数学描述来指定排列。这大大降低了出错的可能性,并且比bit-twiddly代码更令人愉快。
使用示例向量进行测试(在基数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。