Implemented Bifid ciphers, solved 2016 week 7
[cipher-training.git] / bifid-ciphers.ipynb
diff --git a/bifid-ciphers.ipynb b/bifid-ciphers.ipynb
new file mode 100644 (file)
index 0000000..ad0906d
--- /dev/null
@@ -0,0 +1,1121 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "import matplotlib.pyplot as plt\n",
+    "import pandas as pd\n",
+    "import collections\n",
+    "import string\n",
+    "import numpy as np\n",
+    "from numpy import matrix\n",
+    "from numpy import linalg\n",
+    "%matplotlib inline\n",
+    "\n",
+    "from multiprocessing import Pool\n",
+    "\n",
+    "\n",
+    "from cipher import *\n",
+    "from cipherbreak import *\n",
+    "\n",
+    "c7b = open('2016/7b.ciphertext').read()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 108,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "c7bs = sanitise(c7b)\n",
+    "c7br = cat(reversed(c7bs))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def bifid_grid(keyword, wrap_alphabet, letter_mapping):\n",
+    "    cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)\n",
+    "    if letter_mapping is None:\n",
+    "        letter_mapping = {'j': 'i'}\n",
+    "    translation = ''.maketrans(letter_mapping)\n",
+    "    cipher_alphabet = cat(collections.OrderedDict.fromkeys(cipher_alphabet.translate(translation)))\n",
+    "    f_grid = {k: ((i // 5) + 1, (i % 5) + 1) \n",
+    "              for i, k in enumerate(cipher_alphabet)}\n",
+    "    r_grid = {((i // 5) + 1, (i % 5) + 1): k \n",
+    "              for i, k in enumerate(cipher_alphabet)}\n",
+    "    return translation, f_grid, r_grid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 156,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import pprint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 157,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "({106: 'i'},\n",
+      " {'a': (1, 4),\n",
+      "  'b': (2, 1),\n",
+      "  'c': (2, 2),\n",
+      "  'd': (2, 3),\n",
+      "  'e': (2, 4),\n",
+      "  'f': (2, 5),\n",
+      "  'g': (1, 2),\n",
+      "  'h': (3, 1),\n",
+      "  'i': (1, 1),\n",
+      "  'k': (3, 2),\n",
+      "  'l': (3, 3),\n",
+      "  'm': (3, 4),\n",
+      "  'n': (1, 5),\n",
+      "  'o': (3, 5),\n",
+      "  'p': (4, 1),\n",
+      "  'q': (4, 2),\n",
+      "  'r': (4, 3),\n",
+      "  's': (4, 4),\n",
+      "  't': (4, 5),\n",
+      "  'u': (1, 3),\n",
+      "  'v': (5, 1),\n",
+      "  'w': (5, 2),\n",
+      "  'x': (5, 3),\n",
+      "  'y': (5, 4),\n",
+      "  'z': (5, 5)},\n",
+      " {(1, 1): 'i',\n",
+      "  (1, 2): 'g',\n",
+      "  (1, 3): 'u',\n",
+      "  (1, 4): 'a',\n",
+      "  (1, 5): 'n',\n",
+      "  (2, 1): 'b',\n",
+      "  (2, 2): 'c',\n",
+      "  (2, 3): 'd',\n",
+      "  (2, 4): 'e',\n",
+      "  (2, 5): 'f',\n",
+      "  (3, 1): 'h',\n",
+      "  (3, 2): 'k',\n",
+      "  (3, 3): 'l',\n",
+      "  (3, 4): 'm',\n",
+      "  (3, 5): 'o',\n",
+      "  (4, 1): 'p',\n",
+      "  (4, 2): 'q',\n",
+      "  (4, 3): 'r',\n",
+      "  (4, 4): 's',\n",
+      "  (4, 5): 't',\n",
+      "  (5, 1): 'v',\n",
+      "  (5, 2): 'w',\n",
+      "  (5, 3): 'x',\n",
+      "  (5, 4): 'y',\n",
+      "  (5, 5): 'z'})\n"
+     ]
+    }
+   ],
+   "source": [
+    "pprint.pprint(bifid_grid('iguana', KeywordWrapAlphabet.from_a, None))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 158,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "({106: 'i'},\n",
+      " {'a': (1, 2),\n",
+      "  'b': (1, 3),\n",
+      "  'c': (1, 4),\n",
+      "  'd': (1, 5),\n",
+      "  'e': (2, 1),\n",
+      "  'f': (2, 2),\n",
+      "  'g': (2, 3),\n",
+      "  'h': (2, 4),\n",
+      "  'i': (2, 5),\n",
+      "  'k': (3, 1),\n",
+      "  'l': (3, 2),\n",
+      "  'm': (3, 3),\n",
+      "  'n': (3, 4),\n",
+      "  'o': (3, 5),\n",
+      "  'p': (4, 1),\n",
+      "  'q': (4, 2),\n",
+      "  'r': (4, 3),\n",
+      "  's': (4, 4),\n",
+      "  't': (4, 5),\n",
+      "  'u': (5, 1),\n",
+      "  'v': (5, 2),\n",
+      "  'w': (5, 3),\n",
+      "  'x': (5, 4),\n",
+      "  'y': (5, 5),\n",
+      "  'z': (1, 1)},\n",
+      " {(1, 1): 'z',\n",
+      "  (1, 2): 'a',\n",
+      "  (1, 3): 'b',\n",
+      "  (1, 4): 'c',\n",
+      "  (1, 5): 'd',\n",
+      "  (2, 1): 'e',\n",
+      "  (2, 2): 'f',\n",
+      "  (2, 3): 'g',\n",
+      "  (2, 4): 'h',\n",
+      "  (2, 5): 'i',\n",
+      "  (3, 1): 'k',\n",
+      "  (3, 2): 'l',\n",
+      "  (3, 3): 'm',\n",
+      "  (3, 4): 'n',\n",
+      "  (3, 5): 'o',\n",
+      "  (4, 1): 'p',\n",
+      "  (4, 2): 'q',\n",
+      "  (4, 3): 'r',\n",
+      "  (4, 4): 's',\n",
+      "  (4, 5): 't',\n",
+      "  (5, 1): 'u',\n",
+      "  (5, 2): 'v',\n",
+      "  (5, 3): 'w',\n",
+      "  (5, 4): 'x',\n",
+      "  (5, 5): 'y'})\n"
+     ]
+    }
+   ],
+   "source": [
+    "pprint.pprint(bifid_grid('z', KeywordWrapAlphabet.from_a, None))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 159,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "({113: 'p'},\n",
+      " {'a': (1, 4),\n",
+      "  'b': (2, 1),\n",
+      "  'c': (2, 2),\n",
+      "  'd': (2, 3),\n",
+      "  'e': (2, 4),\n",
+      "  'f': (2, 5),\n",
+      "  'g': (1, 2),\n",
+      "  'h': (3, 1),\n",
+      "  'i': (1, 1),\n",
+      "  'j': (3, 2),\n",
+      "  'k': (3, 3),\n",
+      "  'l': (3, 4),\n",
+      "  'm': (3, 5),\n",
+      "  'n': (1, 5),\n",
+      "  'o': (4, 1),\n",
+      "  'p': (4, 2),\n",
+      "  'r': (4, 3),\n",
+      "  's': (4, 4),\n",
+      "  't': (4, 5),\n",
+      "  'u': (1, 3),\n",
+      "  'v': (5, 1),\n",
+      "  'w': (5, 2),\n",
+      "  'x': (5, 3),\n",
+      "  'y': (5, 4),\n",
+      "  'z': (5, 5)},\n",
+      " {(1, 1): 'i',\n",
+      "  (1, 2): 'g',\n",
+      "  (1, 3): 'u',\n",
+      "  (1, 4): 'a',\n",
+      "  (1, 5): 'n',\n",
+      "  (2, 1): 'b',\n",
+      "  (2, 2): 'c',\n",
+      "  (2, 3): 'd',\n",
+      "  (2, 4): 'e',\n",
+      "  (2, 5): 'f',\n",
+      "  (3, 1): 'h',\n",
+      "  (3, 2): 'j',\n",
+      "  (3, 3): 'k',\n",
+      "  (3, 4): 'l',\n",
+      "  (3, 5): 'm',\n",
+      "  (4, 1): 'o',\n",
+      "  (4, 2): 'p',\n",
+      "  (4, 3): 'r',\n",
+      "  (4, 4): 's',\n",
+      "  (4, 5): 't',\n",
+      "  (5, 1): 'v',\n",
+      "  (5, 2): 'w',\n",
+      "  (5, 3): 'x',\n",
+      "  (5, 4): 'y',\n",
+      "  (5, 5): 'z'})\n"
+     ]
+    }
+   ],
+   "source": [
+    "pprint.pprint(bifid_grid('iguana', KeywordWrapAlphabet.from_a, {'q': 'p'}))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 87,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "# def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, \n",
+    "#                    letter_mapping=None, period=None):\n",
+    "#     translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)\n",
+    "    \n",
+    "#     t_message = message.translate(translation)\n",
+    "#     pairs0 = [f_grid[l] for l in t_message]\n",
+    "#     items = sum([list(p) for p in pairs0], [])\n",
+    "#     gap = len(message)\n",
+    "#     pairs1 = [(items[i//2], items[i//2+gap]) for i in range(0, len(items), 2)]\n",
+    "#     return cat(r_grid[p] for p in pairs1)\n",
+    "    "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 162,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, \n",
+    "                   letter_mapping=None, period=None, fillvalue=None):\n",
+    "    translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)\n",
+    "    \n",
+    "    t_message = message.translate(translation)\n",
+    "    pairs0 = [f_grid[l] for l in sanitise(t_message)]\n",
+    "    if period:\n",
+    "        chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]\n",
+    "        if len(chunked_pairs[-1]) < period and fillvalue:\n",
+    "            chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))\n",
+    "    else:\n",
+    "        chunked_pairs = [pairs0]\n",
+    "    \n",
+    "    pairs1 = []\n",
+    "    for c in chunked_pairs:\n",
+    "        items = sum(list(list(i) for i in zip(*c)), [])\n",
+    "        p = [(items[i], items[i+1]) for i in range(0, len(items), 2)]\n",
+    "        pairs1 += p\n",
+    "    \n",
+    "    return cat(r_grid[p] for p in pairs1)\n",
+    "    "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 163,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'nkklawamdkoedysipdesltirsnoesqlvvaloderbhel'"
+      ]
+     },
+     "execution_count": 163,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_encipher('this is a test message for the keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 83,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "ot, ofg, org = bifid_grid('iguana', KeywordWrapAlphabet.from_a, None)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 85,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[(1, 1),\n",
+       " (1, 5),\n",
+       " (2, 3),\n",
+       " (1, 1),\n",
+       " (1, 4),\n",
+       " (2, 2),\n",
+       " (1, 3),\n",
+       " (4, 3),\n",
+       " (4, 3),\n",
+       " (5, 4)]"
+      ]
+     },
+     "execution_count": 85,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "op0 = [ofg[l] for l in \"indiacurry\"]\n",
+    "op0"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 86,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[[(1, 1), (1, 5), (2, 3), (1, 1)],\n",
+       " [(1, 4), (2, 2), (1, 3), (4, 3)],\n",
+       " [(4, 3), (5, 4), (1, 4), (1, 4)]]"
+      ]
+     },
+     "execution_count": 86,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ocp = chunks(op0, 4, fillvalue=[[ofg['a']]])\n",
+    "acc = []\n",
+    "ocp"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 87,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[(1, 1),\n",
+       " (2, 1),\n",
+       " (1, 5),\n",
+       " (3, 1),\n",
+       " (1, 2),\n",
+       " (1, 4),\n",
+       " (4, 2),\n",
+       " (3, 3),\n",
+       " (4, 5),\n",
+       " (1, 1),\n",
+       " (3, 4),\n",
+       " (4, 4)]"
+      ]
+     },
+     "execution_count": 87,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "acc=[]\n",
+    "for c in ocp:\n",
+    "    items = sum(list(list(i) for i in zip(*c)), [])\n",
+    "    p = [(items[i], items[i+1]) for i in range(0, len(items), 2)]\n",
+    "    acc += p\n",
+    "acc"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 88,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'ibnhgaqltims'"
+      ]
+     },
+     "execution_count": 88,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(org[p] for p in acc)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 164,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, \n",
+    "                   letter_mapping=None, period=None, fillvalue=None):\n",
+    "    translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)\n",
+    "    \n",
+    "    t_message = message.translate(translation)\n",
+    "    pairs0 = [f_grid[l] for l in sanitise(t_message)]\n",
+    "    if period:\n",
+    "        chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]\n",
+    "        if len(chunked_pairs[-1]) < period and fillvalue:\n",
+    "            chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))\n",
+    "    else:\n",
+    "        chunked_pairs = [pairs0]\n",
+    "        \n",
+    "    pairs1 = []\n",
+    "    for c in chunked_pairs:\n",
+    "        items = [j for i in c for j in i]\n",
+    "        gap = len(c)\n",
+    "        p = [(items[i], items[i+gap]) for i in range(gap)]\n",
+    "        pairs1 += p\n",
+    "\n",
+    "    return cat(r_grid[p] for p in pairs1)    "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "({106: 'i'},\n",
+       " {'a': (1, 4),\n",
+       "  'b': (2, 1),\n",
+       "  'c': (2, 2),\n",
+       "  'd': (2, 3),\n",
+       "  'e': (2, 4),\n",
+       "  'f': (2, 5),\n",
+       "  'g': (1, 2),\n",
+       "  'h': (3, 1),\n",
+       "  'i': (1, 1),\n",
+       "  'k': (3, 2),\n",
+       "  'l': (3, 3),\n",
+       "  'm': (3, 4),\n",
+       "  'n': (1, 5),\n",
+       "  'o': (3, 5),\n",
+       "  'p': (4, 1),\n",
+       "  'q': (4, 2),\n",
+       "  'r': (4, 3),\n",
+       "  's': (4, 4),\n",
+       "  't': (4, 5),\n",
+       "  'u': (1, 3),\n",
+       "  'v': (5, 1),\n",
+       "  'w': (5, 2),\n",
+       "  'x': (5, 3),\n",
+       "  'y': (5, 4),\n",
+       "  'z': (5, 5)},\n",
+       " {(1, 1): 'i',\n",
+       "  (1, 2): 'g',\n",
+       "  (1, 3): 'u',\n",
+       "  (1, 4): 'a',\n",
+       "  (1, 5): 'n',\n",
+       "  (2, 1): 'b',\n",
+       "  (2, 2): 'c',\n",
+       "  (2, 3): 'd',\n",
+       "  (2, 4): 'e',\n",
+       "  (2, 5): 'f',\n",
+       "  (3, 1): 'h',\n",
+       "  (3, 2): 'k',\n",
+       "  (3, 3): 'l',\n",
+       "  (3, 4): 'm',\n",
+       "  (3, 5): 'o',\n",
+       "  (4, 1): 'p',\n",
+       "  (4, 2): 'q',\n",
+       "  (4, 3): 'r',\n",
+       "  (4, 4): 's',\n",
+       "  (4, 5): 't',\n",
+       "  (5, 1): 'v',\n",
+       "  (5, 2): 'w',\n",
+       "  (5, 3): 'x',\n",
+       "  (5, 4): 'y',\n",
+       "  (5, 5): 'z'})"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_grid('iguana', KeywordWrapAlphabet.from_a, None)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 139,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'ibidonhprm'"
+      ]
+     },
+     "execution_count": 139,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_encipher(\"indiajelly\", 'iguana')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 140,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'indiaielly'"
+      ]
+     },
+     "execution_count": 140,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_decipher('ibidonhprm', 'iguana')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 137,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'ibnhgaqltm'"
+      ]
+     },
+     "execution_count": 137,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_encipher(\"indiacurry\", 'iguana', period=4)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 138,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'indiacurry'"
+      ]
+     },
+     "execution_count": 138,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_decipher(\"ibnhgaqltm\", 'iguana', period=4)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 144,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'ibnhgaqltzml'"
+      ]
+     },
+     "execution_count": 144,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_encipher(\"indiacurry\", 'iguana', period=4, fillvalue='x')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 146,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'indiacurryxx'"
+      ]
+     },
+     "execution_count": 146,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_decipher(\"ibnhgaqltzml\", 'iguana', period=4)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 101,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "p0 = [(1, 1), (2, 1), (1, 5), (3, 1)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 103,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[1, 1, 2, 1, 1, 5, 3, 1]"
+      ]
+     },
+     "execution_count": 103,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "t0 = [j for i in p0 for j in i]\n",
+    "t0"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 104,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[(1, 1), (1, 5), (2, 3), (1, 1)]"
+      ]
+     },
+     "execution_count": 104,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "[(t0[i], t0[i+4]) for i in range(4)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 130,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'martinwehavemadeadreadfulmistakeandihavebeentooslowtoadmitthattomyselfihavehadavisitfromthewomanfromthesyndicateandiconfrontedheraboutthesourceofthetemplatessheconfirmedmyworstfearsandnowiwanttocrawlawayanddiewhathavewedoneoursoftwarehasledtosomuchsufferingwhenitwasdesignedtodotheoppositeiaskedherhowthecabinetofficecouldpossiblyhaveauthorisedthisandshelaughedandexplainedthatthesyndicatenolongerworkedforthebritishgovernmentcallitprivateenterpriseshesaidwehavealwaysbeengoodatthatmyhorrormusthavebeenwrittenallovermyfaceshedidntseemsurprisedatmyreactionbutequallyshedidnttakeitwellandcivilitywasabandonediaskedherhowitcouldbelegalletalonemoraltodowhattheyproposedandheranswerwasthatitwasnecessaryisaidwewouldnthelpthemandshesaiditwasnecessarythatwedidisaidiwouldntbeabletofacemyfamilyandfriendsifwecooperatedandshesaidiwouldnthavetoworryaboutthatforlongonewayoranotherthepdssyndicateweregoingtomakesurewebothdisappearedlookingbackicanseethatfromthestartthiswholethinghasactedtodrawusintoitscentreandnowiamattheeventhorizonalmostunabletoescapeitspullbutithinkwehaveonelastchanceiamsureshewillbevisitingyouaswellshethinkswehavenochoicebutithinkachoiceisallwehavewhateveryoudoholdoutforbettertermsshehastobelievethatyouareonsideandmotivatedbygreedsothatshewontworryaboutanyqualmsyoumighthaveconvinceherthatyouwillconvincemetocooperatetellherthatyouthinkyoushouldworkfromthecollectiveinosloandthatyouwantpaymentviathebankinswitzerlandigottheimpressionthatmoneyisnotaproblemwithmoneyinaswissbankandtheexpertiseandconnectivityaffordedbythecollectiveithinkwehaveachancetoescapeandtotrytostopthemperhapswewillsurvivethisperhapswecanbringthemdown'"
+      ]
+     },
+     "execution_count": 130,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_decipher(c7bs, 'ligo', KeywordWrapAlphabet.from_a, period=4, fillvalue=None)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 147,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'martin we have made a dreadful mistake and i have been too slow to admit that to myself i have had a visit from the woman from the syndicate and i confronted her about the source of the templates she confirmed my worst fears and now i want to crawl away and die what have we done our software has led to so much suffering when it was designed to do the opposite i asked her how the cabinet office could possibly have authorised this and she laughed and explained that the syndicate no longer worked for the british government call it private enterprises he said we have always been good at that my horror must have been written all over myfaces he didnt seem surprised at my reaction but equally she didnt take it well and civility was abandoned i asked her how it could be legal let alone moral to do what they proposed and her answer was that it was necessary i said we wouldnt help them and she said it was necessary that we did i said i wouldnt be able to face my family and friends if we cooperated and she said i wouldnt have to worry about that for long one way or another the pds syndicate were going to make sure we both disappeared looking back i can see that from the start this whole thing has acted to draw us into its centre and now i am at the event horizon almost unable to escape its pull but i think we have one last chance i am sure she will be visiting you as well she thinks we have no choice but i think a choice is all we have whatever you do hold out for better terms she has to believe that you are on side and motivated by greed so that she wont worry about any qualms you might have convince her that you will convince me to cooperate tell her that you think you should work from the collective in oslo and that you want payment via the bank in switzerland i got the impression that money is not a problem with money in a swiss bank and the expertise and connectivity afforded by the collective i think we have a chance to escape and to try to stop them perhaps we will survive this perhaps we can bring them down'"
+      ]
+     },
+     "execution_count": 147,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wcat(segment(bifid_decipher(c7bs, 'ligo', KeywordWrapAlphabet.from_a, period=4)))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 49,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "p0 = [(2, 1), (3, 3), (3, 3), (5, 1), (1, 4)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[2, 1, 3, 3, 3, 3, 5, 1, 1, 4]"
+      ]
+     },
+     "execution_count": 54,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "items = sum([list(p) for p in p0], [])\n",
+    "items"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[(2, 3), (1, 5), (3, 1), (3, 1), (3, 4)]"
+      ]
+     },
+     "execution_count": 55,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "gap=5\n",
+    "[(items[i//2], items[i//2+gap]) for i in range(0, len(items), 2)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 91,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "c7bs = sanitise(c7b)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 115,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def bifid_break_mp(message, wordlist=keywords, fitness=Pletters,\n",
+    "                     number_of_solutions=1, chunksize=500):\n",
+    "    \"\"\"Breaks a keyword substitution cipher using a dictionary and\n",
+    "    frequency analysis\n",
+    "\n",
+    "    >>> keyword_break_mp(keyword_encipher('this is a test message for the ' \\\n",
+    "          'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), \\\n",
+    "          wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS\n",
+    "    (('elephant', <KeywordWrapAlphabet.from_last: 2>), -52.834575011...)\n",
+    "    >>> keyword_break_mp(keyword_encipher('this is a test message for the ' \\\n",
+    "          'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), \\\n",
+    "          wordlist=['cat', 'elephant', 'kangaroo'], \\\n",
+    "          number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE\n",
+    "    [(('elephant', <KeywordWrapAlphabet.from_last: 2>), -52.834575011...), \n",
+    "    (('elephant', <KeywordWrapAlphabet.from_largest: 3>), -52.834575011...)]\n",
+    "    \"\"\"\n",
+    "    with Pool() as pool:\n",
+    "        helper_args = [(message, word, wrap, fitness)\n",
+    "                       for word in wordlist\n",
+    "                       for wrap in KeywordWrapAlphabet]\n",
+    "        # Gotcha: the helper function here needs to be defined at the top level\n",
+    "        #   (limitation of Pool.starmap)\n",
+    "        breaks = pool.starmap(bifid_break_worker, helper_args, chunksize)\n",
+    "        if number_of_solutions == 1:\n",
+    "            return max(breaks, key=lambda k: k[1])\n",
+    "        else:\n",
+    "            return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions]\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 116,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def bifid_break_worker(message, keyword, wrap_alphabet, fitness):\n",
+    "    plaintext = bifid_decipher(message, keyword, wrap_alphabet)\n",
+    "    fit = fitness(plaintext)\n",
+    "    logger.debug('Keyword break attempt using key {0} (wrap={1}) gives fit of '\n",
+    "                 '{2} and decrypt starting: {3}'.format(keyword, \n",
+    "                     wrap_alphabet, fit, sanitise(plaintext)[:50]))\n",
+    "    return (keyword, wrap_alphabet), fit"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 107,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'enamokkneogiyegrkcuzbgsydkoqoswiwvtgbolrkfuzbgsyskdqusgnttqetuonyegyfkbsteqeycgbudbvqadcepgqrsbbeaeoqilrqcsosfcdyrbztiuirvqrtcmesbkudboeytksofknyegrambmctvxeogttfggemokopiqwinutqhdtoftsgsathnemlteprmrqbstdolsaklrueucsvncyhgqqcsfqlutsdxzthnfuvotauhnyegosloeogwrrqclelbonknefqkrkofvoqhxttvrimhyttvyqosrlegqtbeyroeuegbtnqkeecgqepblclvutouaehtoekceqgpeobwaohndxlstmvnvalttupquvoieruortugrhfsyqosnsqcesrvhtcrarvqnshkudbistnbwuootauhtqefiolctxqoqhdsmrneettrtberyybtnqblqxgrtrveqyfoboxwaehwlplqrcbiowhirimheyotqcciorhitvdaqkdurimhtheslelwxuooneagedooqlfibrlfhesrpxtgrrbkarhrxqobtogwaehwrimheyotlviqpesoyfkbsteqoviiynstnvhltfgarqostqdfotktpfhlrnadyegrtencsrtltknqffyqsnietwrixhogeqgsooihkiuhtwtonotknenquarhtnerycgicadbschhthlbeurbzpmqkqbsoebweprtepkhtsqhrslbrrefycoisnkumuedqrseqtshftqvftvkntlgbyqbyqosyotbucvtetvqoawhtgniucoadpiqomkeysgcyroeuegbefwimtedcpqcyogakzhbnepcztbqoviiotcnguiocdaipkqbkebwyqosepuqsrecnfsvedoipoqkmyegsqdsaogktgniurofkwfldeoupsqerncsfevoyqosxcoueswnfoqkspkbnyrheuqxotktarhasoiofnqhtokluoqivslbqhbcveqberoaboskskcsfslctsgkapbqftvuiilvcsbkaqlbrrubwtgtnkdboskylzlqqyetqekleuebrsyspnbrcfqspstdntrndsichescznbprqealulhteroctydoocwanhtprnocothnypoqysosniltodcsfyvvolwaehwdpiqoskedwonrtentycobwanelolftbnneksonwlhrsqcqqcqrsopeckrcountigqtldycoisnkuotktanhetwklvshfspkboxkqaolkmyegdqossdcorqoszoowaohabmityghiurocczicttqkifbboekyorhehedkxrobesooztmbqnsbtsqcnkgqnxcorruhokkaergezpctbpdnntqqshcqaehrfyvenwqtttbgsrcfvqasarhfksbkufvvawlacteqtfnvctvhspkqwyocnrtormzdnckouekbomkttarlptmroyketcbrusoeqystofbqoskctenquekbkybgtfrbktbxsqesswgeysggrqfsswgeedoovxaiqvdabaginnrptyqoqesomudleoylstoqtodcoawhkybgeukqdgogzxeiexrinpchoawheewgepth'"
+      ]
+     },
+     "execution_count": 107,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "c7bs = sanitise(c7b)\n",
+    "c7br = cat(reversed(c7bs))\n",
+    "c7br"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 148,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(('ligo', <KeywordWrapAlphabet.from_a: 1>), -2505.924490942904)"
+      ]
+     },
+     "execution_count": 148,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_break_mp(c7bs, wordlist=['gravity', 'ligo'])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 102,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'ksotagstmczesqstldwfasoehepicltaryruvgstiwbtylwzlanehrdmthhlqzeohpsdytllgdxfcetbiislqetoukobnterkoyonsetkodhtymxuefpdtnhnsulwnnurhiotctcrwhprbssrdblxanlrxadxgxatetsegdoeuhawberbaswolpqkrkpfcxufohcyefaeabqtkbrykbdonghsbaodvongcfdvngmeslhetnytkocenotirklsatenkdeeyoentbryuoleqoefcuxpqbsirotbogkvtqbrqgyoqamkninrfottolgmynbsekmeouwueklcpqrekyylvronsntcotrrctdvoyfthkgalscldypooicxrtpdyttohfoqtprrbgtwepsycwpswuylkedbiglsylbctcfecisumvrbyparteagabdqouuttohvbtcdtxczusxrtleburbtkapmsfctmokootcoibkclbetoaralxzdnlpanenadkhhgtsldnyrupnqsravhtpohmplgtaacuhfpttdebroaqhedgvooyyneoebrfudfodroyklhsheqheeqhdinusytmhdqqedsbbdzcylgukonttacivvvcrprteautwrxhdmczntnixhzbeasmboscsyeqdtsxxeiodohrnofaidlbabrobumelkaeuvnlylglqnfpeqklhwlqeselhameievlbeawrlnllyetoeolencrduoghqqoeqyhlqidrrvndrrwnfhmottqllpayunortyoeariuhharstrhnsfaaoqlipqkohcrnldicnlshnysnrtdbiggeonatcseqosygonehtdutacbzdbstdcrzttntrbtunrotcuewfslftkgdruetlrxrgbmvegfsqieybgqtfxaxrvqbybcaibhoeoolnpxnrdatsspxotloerkwotcheutoerrufnprncktohsqarexdccnbtbekxtqtcrtbaqsottmbltovlurbeoolrtpksbtpyvrasrbtkfricyeremtpaqunucemnterrmdpoldyaicofeoppgepxtdaioeesqqysthohkeotktnstxntgtmhqvyhoythkeanebdshlmtrnqhrunweeeozctiofegqolrfmmdbdacryrmqrypwuwvcntdyqksdamrsglgisanncrmoerfveprpkgbagkerobderbnbatdnedugenxarredlduheegqbrycawnknbnylomloetokdolqreyuecvufbrpsmsptadqthkuotdvknhuyetldctwotidfglrgspmhokusbvtpvbeietnxqbirwhspqetmyotewabodyfppaoacoequgsrrgtyqfsnegmtfvzelmfwosrutwpvnamanthhgcnepsepeguldrhaenwolwhhohnsrlpxnsdhenvadcbyqicaboptfaoknvcmeoerqbtlyovbbddommednhlmrlcyaeflratepytylrnpcantuepwpxxanfthsdnowzirihfqqcedlewwhkcelinfwniiohclardlbqqokzrwvdswbufemcsqqveqnbruynipgrbaytsqkkkyodugayche'"
+      ]
+     },
+     "execution_count": 102,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_decipher(c7bs, 'capris', KeywordWrapAlphabet.from_a )"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 104,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'dnrobchyoikvkwtucnsiganmyoanfatckweqpwxwehfdpoxqdvtworugsnpsxanmycvirrgedmuihpienwfnpwyzdffaqsxsoutvbvqlovoplenqrnktqlernruwptiswodnhaxomrtwbmpunnbnolcrvsasmcblsvawegytshecczeperucfeqlrcosbfdhdkcaeipnsligmsnapleqpoutoqgtcobyzorcehvvkkrhoxngkavnolkcrncshwetpocgnysxewemonekysintysuqrlntykpvsckgenbecfybfmycsrtzrxfckstgbirtnmnhdhcetfvuttwptpbigplyctfisoxttslhhbvtnotminnusfcqobdtnlhnpbnhxhpirckoatchwiaofaloprrbyonlouscekwlsnboyciswcoosyirodpwsgghcqhofeuosmtqfhqsztrsscwxfzwrnhhnphtpvwnurikaeoxgftoyodrmaqnosloukciskmbycicidsgeofsbrfodhsrtskwtpbtgnsmnyncrvsrhtwrsiolhlylrnntvrkenffvgfypknntxryqqgonspndmocrhcpfrnbgerwqncuiciqverfealtrnoomnwdnenepldpgatsbbffsaogoynownnotoxbeckllrnonbknycsensfleqbctonegzaccsifmnuezvlfqsurrgwviiuwrdcgoininoyseugowzkpntfctreowotnetkswlctahmgrpoypohsnoqnqooogboctspteznllttcurbcbiewnslipsshliwsybvalnlosrrtpcfzepwsnonktceovhhrnqfeilrkmsrlseoinyfsfunulffevuofwrenqbrettkrtkbnkelciibvsysafkrisombfmsqgwlfbnekikdyreipgzshnonclsrzgyceeanhlvcluucfsomraasehigprxbmfiftdnhgxhfbnzopntrsgcudoigstteflifktxiewbaqltwevuznctfgxhgtihgeruomubtgsvaxgoysnontudtebmabnbvsnsrqffslnigcfnemqgnhfckqpncoewfrnmbictedfkwaateghravmulprcftrqngazecectflxcicnhhwllbrwpoiitgqvknmogoeckpsqtvofttwbhpsntarshokvnigrlypropqiitoznlghoknmifdedtapmpisprrsfewfnqttitvnkoaltfmfqicnsaedyxuaklctantctroesoorbkkmpktnwnoulfnmrvougqstqkfdtttvarerhylgfoimeanomleupotornghfryklsyfgtnxdnnsttsoitriyisennougsveatellinkpetkaxodulaihaoebevagqodofghminllhpelcnszrbagvzsclxphrvplbhossnckahwaeroakctnekodiwgfinquogmgraqpgqafienectgnwoeusnilosiptysyfxuydrdxsgtcoucislntosflhttnogzrlntrwkrxiokklphbagvsufdlxpuhpnoqwuukirefqweuxtrtbtnnwrshqneawdghirfaturhpwesptebb'"
+      ]
+     },
+     "execution_count": 104,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bifid_decipher(c7br, 'trinket', KeywordWrapAlphabet.from_a)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.5.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}