ROT(n) 编码器和解码器,但解码器不工作

ROT(n) encoder and decoder, but decoder not working

我知道有很多方法可以编写 ROT(n) 函数。 但我不想有一些 table 字符。

因此,我尝试编写一个简单的带解码器的 ROT(n) 作为练习项目。 编码功能工作正常。但是解码器不断将 'a' 更改为 'z'.

有人可以向我解释我做错了什么吗?

下面的 (Python3) 代码将所有内容更改为小写,忽略任何特殊字符。

import random
import string

shift = random.randint(1, 20)


# Encoder:
def encode(string):
    coded_string = []
    string = string.lower()
    for c in string:
        if ord(c) >= 97 and ord(c) <= 122:
            c = (ord(c) + shift) % 122
            if c <= 97:
                c += 97
            coded_string.append(chr(c))
            continue
        coded_string.append(c)
    return ''.join(coded_string)


# Decoder:
def decode(string):
    decoded_string = []
    for c in string:
        if ord(c) >= 97 and ord(c) <= 122:
            if ord(c) - shift <= 97:
                c = (ord(c) % 97) + (122 - shift)
                decoded_string.append(chr(c))
                continue
            c = ord(c) - shift
            decoded_string.append(chr(c))
            continue
        decoded_string.append(c)
    return ''.join(decoded_string)


# Test Function:
def tryout(text):
    test = decode(encode(text))
    try:
        assert test == text, 'Iznogoedh!'
    except AssertionError as AE:
        print(AE, '\t', test)
    else:
        print('Yes, good:', '\t', test)


# Random text generator:
def genRandomWord(n):
    random_word = ''
    for i in range(n):
        random_word += random.choice(string.ascii_lowercase)
    return random_word


# Some tests:
print(f'Shift: {shift}')
tryout('pokemon')
tryout("chip 'n dale rescue rangers")
tryout('Ziggy the Vulture or Zurg')
tryout('Fine # (*day%, to* code@ in Pyth0n3!')
tryout(genRandomWord(10))
tryout(genRandomWord(20))

示例输出:

Shift: 7
Yes, good:   pokemon
Iznogoedh!   chip 'n dzle rescue rzngers
Iznogoedh!   ziggy the vulture or zurg
Iznogoedh!   fine # (*dzy%, to* code@ in pyth0n3!
Yes, good:   qrwmfyogjg
Yes, good:   ihrcuvzyznlvghrtnuno

但是,忽略随机字符串测试,我预计:

Shift: 7
Yes, good:   pokemon
Yes, good:   chip 'n dale rescue rangers
Yes, good:   ziggy the vulture or zurg
Yes, good:   fine # (*day%, to* code@ in pyth0n3!

首先,您的 tryout() 测试函数忘记将输入小写,因此对于您的 Ziggy 示例,它实际上通过了;更正后的测试是:

# Test Function:
def tryout(text):
    test = decode(encode(text))
    try:
        assert test == text.lower(), 'Iznogoedh!'
    except AssertionError as AE:
        print(AE, '\t', test)
    else:
        print('Yes, good:', '\t', test)

错误在你的decode函数中;对于 7 的偏移,您可以看到 a -> h 的编码字母没有正确映射回来,而 i(来自 b)确实有效:

>>> decode('h')
'z'
>>> decode('i')
'b'

然而,错误更进一步;前 7 个字母均被误译; g 映射到 yf 映射到 x,等等。如果您使用较低的移位,则很容易看到:

>>> for encoded in 'abcd': print(decode(encoded), end=' ')
... else: print()
...
w x y z

那些应该映射回xyza。所以这是一个 off-by-one 错误,它在你的测试中:

if ord(c) - shift <= 97:

shift为3,而cd时,ord(c) - shift等于97,不应调整.将 <= 更改为 <:

if ord(c) - shift < 97:

所以固定的 decode() 函数变成:

def decode(string):
    decoded_string = []
    for c in string:
        if ord(c) >= 97 and ord(c) <= 122:
            if ord(c) - shift < 97:
                c = (ord(c) % 97) + (122 - shift)
                decoded_string.append(chr(c))
                continue
            c = ord(c) - shift
            decoded_string.append(chr(c))
            continue
        decoded_string.append(c)
    return ''.join(decoded_string)

您可能想在此处了解 % modulo 运算符,它可以帮助 'wrap around' 值适应一个范围,例如字母 az.

的值

如果你取 ASCII 代码点,减去 97,然后使用调整后的值(减或加移位,取决于编码或解码),然后用 % 26 包裹结果值,你总是出来'other side' 并且可以将结果加回 97:

>>> ord('a') - 97   # a is the 'zeroth' letter in the alphabet, z is the 25th
0
>>> ord('a') - 97 - shift   # shifted by 3 puts it outside the 0 - 25 range
-3
>>> (ord('a') - 97 - shift) % 26  # modulo 26 puts it back in the range, from the end
23
>>> chr((ord('a') - 97 - shift) % 26 + 97)  # add 97 back on to go back to the decoded letter
'x'

另一个 'trick' 是使用 bytes object,将您的输入编码为 UTF-8。 bytes 对象是整数序列,可以说已经被 ord() 函数处理过。只需循环并将移位应用于正确范围内的字节,然后将这些整数附加到列表中。然后,您可以从列表中创建一个新的 bytes 对象并解码回字符串:

def shift_by_n(n, value):
    as_bytes = value.lower().encode('utf8')
    encoded = []
    for v in as_bytes:
        if 97 <= v <= 122:
            v = ((v - 97 + n) % 26) + 97
        encoded.append(v)
    return bytes(encoded).decode('utf8')

以上函数对encodingdecoding都有效,只需将shift作为正值或负值传入即可:

def encode(string):
    return shift_by_n(shift, string)

def decode(string):
    return shift_by_n(-shift, string)

最后,您可以使用 str.translate() function which, given a translation table, makes all replacements for you. You can trivially build a ROT(n) translation table with the str.maketrans() static method 而不是测试每个字母。编码只是将字母表映射到相同的字母表,但从开头删除 shift 个字符并添加到末尾:

alphabet = 'abcdefghijklmnopqrstuvwxyz'

def encode(string):
    # take all letters except the first 'shift' characters, and
    # add those letters to the end instead
    rotated = alphabet[shift:] + alphabet[:shift]
    translate_map = str.maketrans(alphabet, rotated)
    return string.lower().translate(translate_map)

解码使用相同的 rotated 字符串,但交换 str.maketrans() 的参数顺序:

def decode(string):
    # take all letters except the first 'shift' characters, and
    # add those letters to the end instead
    rotated = alphabet[shift:] + alphabet[:shift]
    translate_map = str.maketrans(rotated, alphabet)
    return string.translate(translate_map)

要使上述函数也适用于 大写 字母,只需要将 alphabet.upper()rotated.upper() 结果连接到 alphabetrotated,分别在调用 str.maketrans() 时(并删除 encode() 中的 .lower() 调用)。我会把它留给 reader 来实施。