--- /dev/null
+"name",500,250,100,50,20\r
+"Pletters",2442,2425,2550,2461,2067\r
+"cosine_similarity + euclidean_scaled",2487,2492,2513,2455,1731\r
+"cosine_similarity + normalised",2503,2573,2454,2435,1680\r
+"l1 + euclidean_scaled",2495,2477,2506,2419,1812\r
+"l1 + normalised",2488,2521,2465,2466,1895\r
+"l2 + euclidean_scaled",2524,2467,2529,2470,1763\r
+"l2 + normalised",2566,2545,2481,2433,1756\r
+"l3 + euclidean_scaled",2444,2458,2560,2426,1582\r
+"l3 + normalised",2476,2524,2463,2354,1433\r
--- /dev/null
+import random
+import collections
+from cipher import *
+from cipherbreak import *
+import itertools
+import csv
+
+corpus = sanitise(''.join([open('shakespeare.txt', 'r').read(),
+ open('sherlock-holmes.txt', 'r').read(),
+ open('war-and-peace.txt', 'r').read()]))
+corpus_length = len(corpus)
+
+euclidean_scaled_english_counts = norms.euclidean_scale(english_counts)
+
+metrics = [{'func': norms.l1, 'invert': True, 'name': 'l1'},
+ {'func': norms.l2, 'invert': True, 'name': 'l2'},
+ {'func': norms.l3, 'invert': True, 'name': 'l3'},
+ {'func': norms.cosine_similarity, 'invert': False, 'name': 'cosine_similarity'}]
+ # {'func': norms.harmonic_mean, 'invert': True, 'name': 'harmonic_mean'},
+ # {'func': norms.geometric_mean, 'invert': True, 'name': 'geometric_mean'}]
+scalings = [{'corpus_frequency': normalised_english_counts,
+ 'scaling': norms.normalise,
+ 'name': 'normalised'},
+ {'corpus_frequency': euclidean_scaled_english_counts,
+ 'scaling': norms.euclidean_scale,
+ 'name': 'euclidean_scaled'}]
+message_lengths = [2000, 1000, 500, 250, 100, 50, 20]
+
+trials = 5000
+
+scores = {}
+
+
+def make_frequency_compare_function(target_frequency, frequency_scaling, metric, invert):
+ def frequency_compare(text):
+ counts = frequency_scaling(frequencies(text))
+ if invert:
+ score = -1 * metric(target_frequency, counts)
+ else:
+ score = metric(target_frequency, counts)
+ return score
+ return frequency_compare
+
+def scoring_functions():
+ return [{'func': make_frequency_compare_function(s['corpus_frequency'],
+ s['scaling'], m['func'], m['invert']),
+ 'name': '{} + {}'.format(m['name'], s['name'])}
+ for m in metrics
+ for s in scalings] + [{'func': Pletters, 'name': 'Pletters'}]
+
+def eval_scores():
+ [eval_one_score(f, l)
+ for f in scoring_functions()
+ for l in message_lengths]
+
+def eval_one_score(scoring_function, message_length):
+ print(scoring_function['name'], message_length, ': ', end='', flush=True)
+ if scoring_function['name'] not in scores:
+ scores[scoring_function['name']] = collections.defaultdict(int)
+ for _ in range(trials):
+ sample_start = random.randint(0, corpus_length - message_length)
+ sample = corpus[sample_start:(sample_start + message_length)]
+ multiplier = random.choice([x for x in range(1, 26, 2) if x != 13])
+ adder = random.randint(0, 25)
+ one_based = random.choice([True, False])
+ key = (multiplier, adder, one_based)
+ ciphertext = affine_encipher(sample, multiplier, adder, one_based)
+ found_key, _ = affine_break(ciphertext, scoring_function['func'])
+ if found_key == key:
+ scores[scoring_function['name']][message_length] += 1
+ print(scores[scoring_function['name']][message_length], '/', trials)
+ return scores[scoring_function['name']][message_length]
+
+def show_results():
+ with open('affine_break_parameter_trials.csv', 'w') as f:
+ writer = csv.DictWriter(f, ['name'] + message_lengths,
+ quoting=csv.QUOTE_NONNUMERIC)
+ writer.writeheader()
+ for scoring in sorted(scores.keys()):
+ scores[scoring]['name'] = scoring
+ writer.writerow(scores[scoring])
+
+print('Starting...')
+eval_scores()
+show_results()