从机器的角度来看,转子是两个东西:
- 它是从一个符号到另一个符号的函数
- 它可以进一步成为另一个功能
我们可以为它写一个类似的类型:
newtype Symbol = Symbol {representation :: Int}
data Rotor = Rotor {
transformation :: Symbol -> Symbol,
next :: Rotor
}
如果机器知道反射的对称性,它可能类似于
data Rotor = Rotor {
forward :: Symbol -> Symbol,
backward :: Symbol -> Symbol,
next :: Rotor
}
(你也可以使用类似的东西[(Symbol -> Symbol,Symbol -> Symbol)]
)
现在,我们如何构造一个Rotor
?让我们来定义一个示例转子 IC。
rotorICdefinition = map symbol $ "DMTWSILRUYQNKFEJCAZBPGXOHV"
现在,符号需要有 type Char -> Symbol
。这样的事情应该做。
symbol :: Char -> Symbol
symbol x = Symbol $ ord x - ord 'A'
里面有一堆魔法ord
。它已经知道字母表的顺序。例如:
Prelude Data.Char> map ord "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]
接下来,我们希望能够根据其定义制作转子
rotorIC :: Rotor
rotorIC = makeRotor rotorICdefinition
所以makeRotor
应该有类型
makeRotor :: [Symbol] -> Rotor
我们可以将其定义为
makeRotor definition = makeRotor' 0
where
makeRotor' steps = Rotor {
forward = forwardLookup steps,
backward = reverseLookup steps,
next = makeRotor' ((steps+1) `mod` symbolModulus)
}
forwardLookupTable = array (minBound, maxBound) (zip symbols definition)
reverseLookupTable = array (minBound, maxBound) (zip definition symbols)
forwardLookup = lookup forwardLookupTable
reverseLookup = lookup reverseLookupTable
lookup lookupTable steps = (lookupTable !) . Symbol . (`mod` symbolModulus) . (+ steps) . representation
这里发生了很多事情。我们正在制作无限的转子流,每一个都比前一个旋转了一步,从一个旋转了 0 步的转子开始。steps
inmakeRotor'
正在跟踪它旋转了多少步。包括Rotor
和转换forward
,backward
其中考虑了转子旋转了多少步。转子是相同的next
,但已经多转了一步。为了避免最终溢出整数,我们将其模mod
数取为存在的符号数symbolModulus
。(有更有效的方法可以做到这一点)。这两个查找基于构建一次的查找表,将范围内的每个符号映射(minBound, maxBound)
到根据definition
. 这lookup
本身只是获取输入,添加步数,取模数作为符号数,然后返回查找表中该位置的内容。
这要求我们定义新出现minBound
的 、maxBound
、symbols
和symbolModulus
:
instance Bounded (Symbol) where
minBound = symbol 'A'
maxBound = symbol 'Z'
symbolModulus = (representation maxBound) - (representation minBound) + 1
-- This could have some other definition
symbols = map symbol $ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
我们将添加一个小 UI,并将整个程序放在一起:
module Main (
main
) where
import Data.Char
import Data.Array -- requires array package
import System.IO
main = go rotorIC
where go rotor = do
putStr "Input : "
hFlush stdout
command <- getLine
case command of
"next" -> go (next rotor)
[] -> return ()
text -> case all (inRange (char minBound, char maxBound)) text of
True -> do
putStrLn . ("Forward : " ++) $ map (char . forward rotor . symbol) text
putStrLn . ("Backward: " ++)$ map (char . backward rotor . symbol) text
go rotor
_ -> do
putStrLn "Not all of the input was symbols"
go rotor
newtype Symbol = Symbol {representation :: Int} deriving (Eq, Ord, Ix)
symbol :: Char -> Symbol
symbol x = Symbol $ ord x - ord 'A'
char :: Symbol -> Char
char x = chr $ representation x + ord 'A'
instance Bounded (Symbol) where
minBound = symbol 'A'
maxBound = symbol 'Z'
symbolModulus = (representation maxBound) - (representation minBound) + 1
-- This could have some other definition
symbols = map symbol $ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
data Rotor = Rotor {
forward :: Symbol -> Symbol,
backward :: Symbol -> Symbol,
next :: Rotor
}
rotorICdefinition = map symbol $ "DMTWSILRUYQNKFEJCAZBPGXOHV"
rotorIC :: Rotor
rotorIC = makeRotor rotorICdefinition
makeRotor :: [Symbol] -> Rotor
makeRotor definition = makeRotor' 0
where
makeRotor' steps = Rotor {
forward = forwardLookup steps,
backward = reverseLookup steps,
next = makeRotor' ((steps+1) `mod` symbolModulus)
}
forwardLookupTable = array (minBound, maxBound) (zip symbols definition)
reverseLookupTable = array (minBound, maxBound) (zip definition symbols)
forwardLookup = lookup forwardLookupTable
reverseLookup = lookup reverseLookupTable
lookup lookupTable steps = (lookupTable !) . Symbol . (`mod` symbolModulus) . (+ steps) . representation
现在我们可以通过几个例子来运行。字母表前六个字母的正向变换是我们的开始rotorICdefinition
:
Input : ABCDEFG
Forward : DMTWSIL
Backward: RTQAONV
如果我们把 的开头放入rotorICdefinition
,我们会得到字母表的前六个字母作为后向变换:
Input : DMTWSIL
Forward : WKBXZUN
Backward: ABCDEFG
如果我们在转子上进行下一步,我们会得到一些非常不同的东西:
Input : next
Input : ABCDEFG
Forward : MTWSILR
Backward: TQAONVY
但是如果我们在 'A' 之前输入一个开头的字母,我们会再次回到我们的定义:
Input : ZABCDEF
Forward : DMTWSIL
Backward: RTQAONV
在转子上进行下一步 25 次后,我们又回到了开始的地方:
Input : next
(25 times total)
Input : next
Input : ABCDEFG
Forward : DMTWSIL
Backward: RTQAONV