Создайте тип Key
и используйте умные конструкторы
Основным источником эталона, по-видимому, являются повторные проверки того, что r
обратим, и вычисление его обратного. Имеет смысл разделить ваши операции (например, encipher
) на два этапа: сначала check , затем фактически шифрование . Таким образом, вы можете написать проверочную часть только один раз.
Одним из способов достижения этого является определение нового типа CaesarKey
, который гарантированно содержит только допустимых ключей. Мы можем гарантировать этот инвариант, используя умные конструкторы , следующим образом:
{-# LANGUAGE RecordWildCards #-} -- for the Key{..} syntax below
-- invariant: r and q are inverses mod 26.
-- To ensure this invariant, we only export the 'caesarKey' smart constructor,
-- and not the underlying 'Key' constructor
data CaesarKey = Key { r :: Integer, s :: Integer, q :: Integer }
caesarKey :: Integer -> Integer -> Maybe CaesarKey
caesarKey r s = Key r s <$> invertMod r 26
-- ciphers
encipher :: CaesarKey -> Integer -> Integer
encipher Key{..} p = mod (r * p + s) 26
decipher :: CaesarKey -> Integer -> Integer
decipher Key{..} c = mod (q * (c - s)) 26
encipherString :: CaesarKey -> String -> String
encipherString key = digitsToText . map (encipher key) . textToDigits
decipherString :: CaesarKey -> String -> String
decipherString key = digitsToText . map (decipher key) . textToDigits
Определить invert
по клавишам
Теперь мы можем воспользоваться наблюдением Даниэля, что decipher
это просто encipher
, но определено для другого ключа (а именно «обратный ключ»). Итак, давайте определим операцию для инвертирования ключей:
-- turns a key suitable for encoding into one suitable for decoding, and vice versa.
-- @invert (invert key) = key@
invert :: CaesarKey -> CaesarKey
invert (Key r s q) = Key q ((26-q)*s) r
и теперь мы можем выбросить функции decipher
и decipherString
, так как они не нужны (т.е. вместо них предпочтительнее использовать invert
).
Сделать allKeys
функцию
Концептуально, мы можем разделить bruteForceCaesarDecipher
на две задачи: во-первых, сгенерировать все возможные ключи; во-вторых, расшифруйте текст каждой клавишей. Давайте реализуем это в коде:
allKeys :: [CaesarKey]
allKeys = catMaybes $ caesarKey <$> [1,3..25] <*> [1,3..25]
bruteForceCaesar :: String -> [String]
bruteForceCaesar str = [encipherString key str | key <- allKeys]
Помимо предоставления более простого для понимания кода (на мой взгляд), разделение кода таким образом имеет преимущество в том, что мы создаем список ключей один раз , а не перестраиваем ключи для каждой строки, которую мы хотим декодировать.
Обратите внимание также на несколько других небольших изменений:
Я использовал catMaybes :: [Maybe a] -> [a]
, чтобы выбросить Nothing
ключи
Я последовал совету Даниила о том, как сделать bruteForceCaesar
более эффективным.
Полный код здесь .