Tweaked the keyword break slides
[cipher-training.git] / slides / keyword-break.html
index b3c0a2c36d590678acf5ae196d4b985fa6900943..49160bbb4fe394a3a5f9d88a05a797c62b732372 100644 (file)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head>
-    <title>Affine ciphers</title>
+    <title>Breaking keyword ciphers</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     <style type="text/css">
       /* Slideshow styles */
@@ -51,7 +51,7 @@ a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t |
 --|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|--
 k | e | y | w | o | r | d | a | b | c | f | g | h | i | j | l | m | n | p | q | s | t | u | v | x | z
 
-----
+---
 
 # Duplicate and extend your `affine_break()` function
 
@@ -77,6 +77,7 @@ Thread-safe shared-memory code is hard.
 Python's Global Interpreter Lock prevents shooting yourself in the foot.
 
 Where you want true parallelism, need different threads (Python processes).
+
 * Thread-safe shared-memory code is hard.
 
 The `multiprocessing` library makes this easier.
@@ -85,11 +86,33 @@ But before we get there, a couple of diversions...
 
 ---
 
-# `map()`
+# DRYing code
+
+Three cipher breaking tasks so far.
+
+All working on the same principle:
+
+```
+find a way to enumerate all the possible keys
+initialise 'best so far'
+for each key:
+    decipher message with this key
+    score it
+    if it's better than the best so far:
+        update best so far
+```
+
+Repetition of code is a bad smell.
+
+Separate the 'try all keys, keep the best' logic from the 'score this one key' logic.
+
+---
+
+# map()
 
 A common task is to apply a function to each item in a sequence, returning a sequence of the results.
 
-```python```
+```python
 def double(x):
     return x * 2
 
@@ -107,11 +130,13 @@ How can we use this for keyword cipher breaking?
 
 Define a function that takes a possible key (keyword and cipher type) and returns the key and its fitness.
 
+* (Also pass in the message and the fitness function)
+
 Use `map()` and `max()` to find the best key
 
 ---
 
-# `print()`
+# Arity of print()
 
 How many arguments does this take?
 
@@ -124,7 +149,7 @@ How do you write a function that takes this many arguments?
 ## Positional, keyword
 
 * Common or garden parameters, as you're used to.
-* `def keyword_encipher(message, keyword, wrap_alphabet=0):`
+* `def keyword_encipher(message, keyword, Keyword_wrap_alphabet.from_a):`
 
 ## Excess positional
 * `def mean(x, *xs):`
@@ -143,6 +168,35 @@ First number goes in `x`, remaining go in the tuple `xs`
 
 What does `Pool.starmap()` do?
 
+---
+
+```python
+from multiprocessing import Pool
+
+def keyword_break_mp(message, wordlist=keywords, fitness=Pletters, chunksize=500):
+    helper_args = [??? for word in wordlist] # One tuple for each possible key
+    with Pool() as pool:
+        breaks = pool.starmap(keyword_break_worker, helper_args, chunksize) 
+        return max(breaks, key=lambda k: k[1])
+
+def keyword_break_worker(???):
+    ???
+    return (key, fitness)
+```
+
+* Gotcha: the function in `Pool.starmap()` must be defined at the top level
+    * This is definitely a "feature"
+
+---
+
+# Performance and chunksize
+
+Try the multiprocessing keyword break. Is it using all the resources?
+
+Setting `chunksize` is an art.
+
+## Map-reduce as a general pattern for multiprocessing
+
     </textarea>
     <script src="http://gnab.github.io/remark/downloads/remark-0.6.0.min.js" type="text/javascript">
     </script>