密码文本字母频率替换:比较两个字典的键值并修改文本

2 投票
3 回答
2916 浏览
提问于 2025-04-16 08:26

我看过一些类似的话题,但找不到完全符合我想要实现的解决方案。

我有一段密文,需要根据每个字母在文本中出现的频率进行简单的字母替换。我已经有一个函数可以规范化文本(变成小写,去掉非字母字符,统计字母出现的次数,然后计算每个字母的相对频率。字母作为字典的键,频率作为值。

我还有一个单独的字典,里面是字母A-Z的预期频率(k=字母,v=频率),但我对接下来该怎么做有点困惑。

我认为我需要做的是,拿着规范化后的密文、预期字母频率字典[d1]和密文字母频率字典[d2],然后像下面这样逐个处理(部分伪代码):

for word in text:
    for item in word:
        for k,v in d2.items():
            if d2[v] == d1[v]:
                replace any instance of d2[k] with d1[k] in text
    decoded_text=open('decoded_text.txt', 'w')
    decoded_text.write(str('the decoded text')

在这里,我想对文本说:“如果d2中的值和d1中的值匹配,就把文本中所有的d2[k]替换成d1[k]。”

我意识到我可能在基本的Python逻辑上犯了不少错误(我对Python还比较陌生),但我这样做的方向对吗?

提前谢谢你们!

更新:

感谢大家的建议。我决定尝试Karl Knechtel的方法,并对其进行了一些修改以适应我的代码。不过,我在实现上仍然遇到了一些问题。

我已经做了一个解码函数,用来处理相关的密文文件。这个函数调用了之前写的统计函数,返回一个字典(字母:频率,频率是浮点数)。这导致“制作大写版本”的代码无法正常工作,因为k和v是浮点数,不能使用.upper这个属性。所以,调用这个解码函数返回的是密文字母的频率,然后是仍然编码的密文本身。

def sorted_histogram(a_dict):
    return [x[1] for x in sorted(a_dict.items(), key=itemgetter(1))]

def decode(filename):
    text=open(filename).read()
    cipher=text.lower()

    cipher_dict=count(filename)

    english_histogram = sorted_histogram(english_dict)
    cipher_histogram = sorted_histogram(cipher_dict)

    mapping = dict(zip(english_histogram, cipher_histogram)

    translated = ''.join(
    mapping.get(c, c)
    for c in cipher
    )
    return translated

3 个回答

0
#!/usr/bin/env python
from operator import itemgetter
import string

def frequency(text):
    d = {}
    for letter in text:
        try:
            d[letter] += 1
        except:
            d[letter] = 1
    return d

def alphabet():
    for alpha in string.letters: yield alpha

def cipher(text):
    expected = frequency(text)
    flist = sorted(expected.iteritems(), key=itemgetter(1), reverse=True)
    alphabet_generator = alphabet()
    for char, freq in flist:
        text = text.replace(char, alphabet_generator.next())
    return (text, expected)

def decipher(text, expected):
    nal = [ x[0] for x in sorted(expected.iteritems(), key=itemgetter(1), \
            reverse=True) ]
    normal_alphabet = ''.join(nal)
    transtable = string.maketrans(string.letters[:len(normal_alphabet)], \
                                  normal_alphabet)
    return text.translate(transtable)

使用方法:

if __name__ == '__main__':
    s = "SUMMERTIMEANDTHELIVINGISEASYFISHESJUMPING"
    ciphered, expected = cipher(s)
    print s
    print ciphered
    print decipher(ciphered, expected)

# SUMMERTIMEANDTHELIVINGISEASYFISHESJUMPING
# ciddbpjadbfekjhbnaqaegacbfcrlachbcmidoaeg
# SUMMERTIMEANDTHELIVINGISEASYFISHESJUMPING
0

首先要注意,频率很可能不会完全匹配,除非你的消息非常长。所以你可能需要手动调整一下,才能得到准确的消息。不过如果频率差不多的话……

你可以获取两个字典(字母)的键,并按照它们的值(频率)进行排序:

letters_in_frequency_order = sorted(d1.keys(), key=lambda x: d1[x])

然后把它们转换成字符串:

normal_alphabet = "".join(letters_in_frequency_order)

接着用这些字符串来翻译你的内容:

import string
transtable = string.maketrans(cypher_alphabet, normal_alphabet)
cyphertext.translate(transtable)
0

你其实不想做你现在想的事情,因为样本中的字符频率通常和参考数据中的频率分布不一致。你真正想做的是找到最常见的字符,把它替换成'e',然后是下一个最常见的字符,替换成't',依此类推。

所以我们要做的事情是:

  1. (我假设你已经会这部分)构建一个字典,记录密文中每个字母的实际频率。

  2. 定义一个函数,这个函数接收一个{字母: 频率}的字典,并生成一个按频率排序的字母列表。

  3. 我们获取参考中的字母,按频率排序(也就是说,现在我们有了英语中最常见字母的有序列表),以及样本中的字母(同样处理)。

  4. 假设样本中最常见的字母对应英语中最常见的字母,依此类推:我们创建一个新的字典,把第一个列表中的字母映射到第二个列表中的字母。(我们也可以创建一个翻译表,用于 str.translate。)我们会制作同一个字典的大写和小写版本(我假设你原来的字典只有小写),然后把它们合并在一起。

  5. 我们使用这个映射来翻译密文,其他字符(空格、标点符号等)保持不变。

因此:

# 2.
import operator
def sorted_histogram(a_dict):
  return [
    x[1] # the value
    for x in sorted(a_dict.items(), key=operator.itemgetter(1))
    # of each dict item, sorted by value (i.e. the [1] element of each item).
  ]

# 3.
english_histogram = sorted_histogram(english_dict)
cipher_histogram = sorted_histogram(cipher_dict)

# 4.
# Make the lowercase version
mapping = dict(zip(english_histogram, cipher_histogram))
# Make the uppercase version, and merge it in at the same time.
mapping.update(dict(
  (k.upper(), v.upper()) for (k, v) in zip(english_histogram, cipher_histogram)
))

# 5.
translated = ''.join( # make this list of characters, and string them together:
  mapping.get(c, c) # the mapped result, if possible; otherwise the original
  for c in cipher
)

# 6. Do whatever you want with 'translated' - write to file, etc.

撰写回答