+ >>> railfence_decipher('horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihrlhateihsnefttiaece', 3)
+ 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
+ >>> railfence_decipher('hresleogcseeemhetaocofrnrnerlhateihsnefttiaeceltvsatiigitxetifihoarspeslp', 5)
+ 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
+ >>> railfence_decipher('haspolsevsetgifrifrlatihnettaeelemtiocxernhorersleesgcptehaiaottneihesfic', 7)
+ 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
+ """
+ # find the number and size of the sections, including how many characters
+ # are missing for a full grid
+ n_sections = math.ceil(len(message) / ((height - 1) * 2))
+ padding_to_add = n_sections * (height - 1) * 2 - len(message)
+ # row_lengths are for the both up rows and down rows
+ row_lengths = [n_sections] * (height - 1) * 2
+ for i in range((height - 1) * 2 - 1, (height - 1) * 2 - (padding_to_add + 1), -1):
+ row_lengths[i] -= 1
+ # folded_rows are the combined row lengths in the middle of the railfence
+ folded_row_lengths = [row_lengths[0]]
+ for i in range(1, height-1):
+ folded_row_lengths += [row_lengths[i] + row_lengths[-i]]
+ folded_row_lengths += [row_lengths[height - 1]]
+ # find the rows that form the railfence grid
+ rows = []
+ row_start = 0
+ for i in folded_row_lengths:
+ rows += [message[row_start:row_start + i]]
+ row_start += i
+ # split the rows into the 'down_rows' (those that form the first column of
+ # a section) and the 'up_rows' (those that ofrm the second column of a
+ # section).
+ down_rows = [rows[0]]
+ up_rows = []
+ for i in range(1, height-1):
+ down_rows += [''.join([c for n, c in enumerate(rows[i]) if n % 2 == 0])]
+ up_rows += [''.join([c for n, c in enumerate(rows[i]) if n % 2 == 1])]
+ down_rows += [rows[-1]]
+ up_rows.reverse()
+ return ''.join(c for r in zip_longest(*(down_rows + up_rows), fillvalue='') for c in r)
+
+
+def hill_encipher(matrix, message_letters, fillvalue='a'):
+ """Hill cipher
+
+ >>> hill_encipher(np.matrix([[7,8], [11,11]]), 'hellothere')
+ 'drjiqzdrvx'
+ >>> hill_encipher(np.matrix([[6, 24, 1], [13, 16, 10], [20, 17, 15]]), \
+ 'hello there')
+ 'tfjflpznvyac'
+ """
+ n = len(matrix)
+ sanitised_message = sanitise(message_letters)
+ if len(sanitised_message) % n != 0:
+ padding = fillvalue[0] * (n - len(sanitised_message) % n)
+ else:
+ padding = ''
+ message = [ord(c) - ord('a') for c in sanitised_message + padding]
+ message_chunks = [message[i:i+n] for i in range(0, len(message), n)]
+ # message_chunks = chunks(message, len(matrix), fillvalue=None)
+ enciphered_chunks = [((matrix * np.matrix(c).T).T).tolist()[0]
+ for c in message_chunks]
+ return ''.join([chr(int(round(l)) % 26 + ord('a'))
+ for l in sum(enciphered_chunks, [])])
+
+def hill_decipher(matrix, message, fillvalue='a'):
+ """Hill cipher
+
+ >>> hill_decipher(np.matrix([[7,8], [11,11]]), 'drjiqzdrvx')
+ 'hellothere'
+ >>> hill_decipher(np.matrix([[6, 24, 1], [13, 16, 10], [20, 17, 15]]), \
+ 'tfjflpznvyac')
+ 'hellothereaa'