+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# def beaufort_encipher(message, keyword):\n",
+ "# \"\"\"Beaufort encipher\n",
+ "\n",
+ "# >>> beaufort_encipher('inhisjournaldatedtheidesofoctober', 'arcanaimperii')\n",
+ "# 'sevsvrusyrrxfayyxuteemazudmpjmmwr'\n",
+ "# \"\"\"\n",
+ "# shifts = [pos(l) for l in sanitise(keyword)]\n",
+ "# pairs = zip(message, cycle(shifts))\n",
+ "# return cat([unpos(k - pos(l)) for l, k in pairs])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'sevsvrusyrrxfayyxuteemazudmpjmmwr'"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# beaufort_encipher('inhisjournaldatedtheidesofoctober', 'arcanaimperii')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# beaufort_decipher = beaufort_encipher"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# beaufort_decipher('sevsvrusyrrxfayyxuteemazudmpjmmwr', 'arcanaimperii')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# def beaufort_sub_break(message, fitness=Pletters):\n",
+ "# best_shift = 0\n",
+ "# best_fit = float('-inf')\n",
+ "# for key in range(26):\n",
+ "# plaintext = [unpos(key - pos(l)) for l in message]\n",
+ "# fit = fitness(plaintext)\n",
+ "# logger.debug('Beaufort sub break attempt using key {0} gives fit of {1} '\n",
+ "# 'and decrypt starting: {2}'.format(key, fit,\n",
+ "# plaintext[:50]))\n",
+ "# if fit > best_fit:\n",
+ "# best_fit = fit\n",
+ "# best_key = key\n",
+ "# logger.info('Beaufort sub break best fit: key {0} gives fit of {1} and '\n",
+ "# 'decrypt starting: {2}'.format(best_key, best_fit, \n",
+ "# cat([unpos(best_key - pos(l)) for l in message[:50]])))\n",
+ "# return best_key, best_fit"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# def beaufort_frequency_break(message, max_key_length=20, fitness=Pletters):\n",
+ "# \"\"\"Breaks a Beaufort cipher with frequency analysis\n",
+ "\n",
+ "# >>> beaufort_frequency_break(beaufort_encipher(sanitise(\"It is time to \" \\\n",
+ "# \"run. She is ready and so am I. I stole Daniel's pocketbook this \" \\\n",
+ "# \"afternoon when he left his jacket hanging on the easel in the \" \\\n",
+ "# \"attic. I jump every time I hear a footstep on the stairs, \" \\\n",
+ "# \"certain that the theft has been discovered and that I will \" \\\n",
+ "# \"be caught. The SS officer visits less often now \" \\\n",
+ "# \"that he is sure\"), 'florence')) # doctest: +ELLIPSIS\n",
+ "# ('florence', -307.5473096791...)\n",
+ "# \"\"\"\n",
+ "# def worker(message, key_length, fitness):\n",
+ "# splits = every_nth(message, key_length)\n",
+ "# key = cat([unpos(beaufort_sub_break(s)[0]) for s in splits])\n",
+ "# plaintext = beaufort_decipher(message, key)\n",
+ "# fit = fitness(plaintext)\n",
+ "# return key, fit\n",
+ "# sanitised_message = sanitise(message)\n",
+ "# results = starmap(worker, [(sanitised_message, i, fitness)\n",
+ "# for i in range(1, max_key_length+1)])\n",
+ "# return max(results, key=lambda k: k[1])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('arcanaimperii', -1506.8637359274674)"
+ ]
+ },
+ "execution_count": 45,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "kb, sb = beaufort_frequency_break(cb)\n",
+ "kb, sb"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "In his Journal dated the Ides of October in the year of the consulships of Caecelius Tullius Capito\n",
+ "Pomponianus Plotius Firmus and Gaius Cornelius Gallicanus, Agricola wrote “The mystery of the battle\n",
+ "at Camulodonum is at last solved. Calgacus may be a Barbarian now, but he was a Roman citizen then,\n",
+ "who betrayed us all for love of a barbarian. It has taken all my skills as a leader of men to keep\n",
+ "him alive. The Legionnaires spend their evenings designing new and cruel ways to execute him in\n",
+ "revenge for the shame he brought upon us, but his life is precious. It is the only card left to play\n",
+ "in our search for salvation and the return of the stolen Aquilae. If we can also recover the Codex\n",
+ "then perhaps its loss can be concealed and our lives will be spared.” Releasing the Roman traitor\n",
+ "Calgacus must have stuck in the proud Agricola’s throat, but he made a pact with the remaining\n",
+ "Caledonii and travelled north to exchange the prisoner for the Aquilae and the Codex. But the\n",
+ "cunning Caledonian tribesman set another trap and presented Agricola with a forgery cunningly\n",
+ "assembled with pages from the books stolen when the tribe ransacked Mons Graupius. For too long the\n",
+ "sons of Rome had underestimated the people in Britannia and while the Aquila of the Legion had been\n",
+ "restored by the exchange, their honour was not. Agricola faced a return to Rome, humiliation and\n",
+ "almost certain death. The sixth chapter of my tale of woe is guarded by lightning, bull and oak.\n"
+ ]
+ }
+ ],
+ "source": [
+ "pub = depunctuate(cb)\n",
+ "pb = beaufort_decipher(scb, kb)\n",
+ "print(lcat(tpack(repunctuate(pb, pub).split())))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []