From d6a04fbe42947ef0f9d9e26077c2b9b99069f4d1 Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Sun, 25 May 2014 11:39:57 +0200 Subject: [PATCH] Solves problem 59 --- README.markdown | 5 +- e_49.py | 8 -- e_57.py | 30 ++++ e_58.py | 31 ++++ e_59.py | 255 +++++++++++++++++++++++++++++++++ e_59_cipher1.txt | 1 + projecteuler.sublime-workspace | 50 +++++-- 7 files changed, 356 insertions(+), 24 deletions(-) create mode 100644 e_57.py create mode 100644 e_58.py create mode 100644 e_59.py create mode 100644 e_59_cipher1.txt diff --git a/README.markdown b/README.markdown index e406396..822ec62 100644 --- a/README.markdown +++ b/README.markdown @@ -71,11 +71,14 @@ So you may find some of the code here quite ugly. And this is the case :). Why o 54 - How many hands does Player 1 win in radndom hands of Poker? - < 1 sec
55 - How many Lychrel numbers are there below ten-thousand? - < 1 sec
56 - Considering natural numbers of the form, a^b, finding the maximum digital sum. > 3 sec
+59 - Decrypt XOR ASCII - 23.4s
67 - Using an efficient algorithm find the maximal sum in the triangle? - 0.027
## In progress: 47 - Find the first four consecutive integers to have four distinct primes factors.
+57 - How many fractions contain a numerator with more digits than denominator?
+58 - What is the side length of the square spiral for which the ratio of primes along both diagonals first falls below 10%?
97 - Find the last ten digits of the non-Mersenne prime: 28433 � 2^7830457 + 1.
## Contact @@ -87,4 +90,4 @@ You can contact me at julien at lengrand dot fr, or on my [current website](http ![Current Project Euler status](http://projecteuler.net/profile/jlengrand.png) -Last update : 22/05/2014 +Last update : 25/05/2014 diff --git a/e_49.py b/e_49.py index 358b8be..d856bfa 100644 --- a/e_49.py +++ b/e_49.py @@ -87,12 +87,4 @@ def findPrimePerms(prime): if __name__ == '__main__': primes = fourLengthPrime() print getPrimesPermutations(primes, 3) - - #itertools.combinations(lst, i)] - #getPrimesPermutations(primes, 3) - # try: - # while primes.next(): - # print primes.next() - # except StopIteration: - # print "Reached end of list" #print "Answer : %d " % (last_ten()) \ No newline at end of file diff --git a/e_57.py b/e_57.py new file mode 100644 index 0000000..00576b6 --- /dev/null +++ b/e_57.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +''' +Created on 10 feb. 2012 + +@author: Julien Lengrand-Lambert + +DESCRIPTION: Solves problem 57 of Project Euler +It is possible to show that the square root of two can be expressed +as an infinite continued fraction. + +sqrt 2 = 1 + 1/(2 + 1/(2 + 1/(2 + ... ))) = 1.414213... + +By expanding this for the first four iterations, we get: + +1 + 1/2 = 3/2 = 1.5 +1 + 1/(2 + 1/2) = 7/5 = 1.4 +1 + 1/(2 + 1/(2 + 1/2)) = 17/12 = 1.41666... +1 + 1/(2 + 1/(2 + 1/(2 + 1/2))) = 41/29 = 1.41379... + +The next three expansions are 99/70, 239/169, and 577/408, +but the eighth expansion, 1393/985, is the first example where the number +of digits in the numerator exceeds the number of digits in the denominator. + +In the first one-thousand expansions, how many fractions contain a numerator +with more digits than denominator? +''' + +if __name__ == '__main__': + print "plouf" + #print "Answer : %d " % (last_ten()) \ No newline at end of file diff --git a/e_58.py b/e_58.py new file mode 100644 index 0000000..c3cfc42 --- /dev/null +++ b/e_58.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +''' +Created on 10 feb. 2012 + +@author: Julien Lengrand-Lambert + +DESCRIPTION: Solves problem 58 of Project Euler +Starting with 1 and spiralling anticlockwise in the following way, +a square spiral with side length 7 is formed. + +37 36 35 34 33 32 31 +38 17 16 15 14 13 30 +39 18 5 4 3 12 29 +40 19 6 1 2 11 28 +41 20 7 8 9 10 27 +42 21 22 23 24 25 26 +43 44 45 46 47 48 49 + +It is interesting to note that the odd squares lie along the bottom right diagonal, +but what is more interesting is that 8 out of the 13 numbers lying along both +diagonals are prime; that is, a ratio of 8/13 ≈ 62%. + +If one complete new layer is wrapped around the spiral above, a square spiral +with side length 9 will be formed. If this process is continued, +what is the side length of the square spiral for which the ratio of primes +along both diagonals first falls below 10%? +''' + +if __name__ == '__main__': + print "plouf" + #print "Answer : %d " % (last_ten()) \ No newline at end of file diff --git a/e_59.py b/e_59.py new file mode 100644 index 0000000..f6f1568 --- /dev/null +++ b/e_59.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +''' +Created on 10 feb. 2012 + +@author: Julien Lengrand-Lambert + +DESCRIPTION: Solves problem 59 of Project Euler +Each character on a computer is assigned a unique code +and the preferred standard is ASCII (American Standard Code for Information Interchange). +For example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107. + +A modern encryption method is to take a text file, convert the bytes to ASCII, +then XOR each byte with a given value, taken from a secret key. +The advantage with the XOR function is that using the same encryption key +on the cipher text, restores the plain text; for example, 65 XOR 42 = 107, +then 107 XOR 42 = 65. + +For unbreakable encryption, the key is the same length as the plain text message, +and the key is made up of random bytes. +The user would keep the encrypted message and the encryption key in different locations, +and without both "halves", it is impossible to decrypt the message. + +Unfortunately, this method is impractical for most users, +so the modified method is to use a password as a key. If the password is +shorter than the message, which is likely, the key is repeated cyclically +throughout the message. The balance for this method is using a sufficiently +long password key for security, but short enough to be memorable. + +Your task has been made easy, as the encryption key consists of three lower +case characters. Using cipher1.txt (right click and 'Save Link/Target As...'), +a file containing the encrypted ASCII codes, and the knowledge that the plain + text must contain common English words, decrypt the message and find + the sum of the ASCII values in the original text. +''' +import itertools +import string + +base = 16 # hey, its hex! +printable_letters = string.printable # list of all printable characters + +#frequency of english letters +en_freq = {'a': 0.08167, + 'b': 0.01492, + 'c':0.02782, + 'd':0.04253, + 'e':0.12702, + 'f':0.02228, + 'g':0.02015, + 'h':0.06094, + 'i':0.06966, + 'j':0.00153, + 'k':0.00772, + 'l':0.04025, + 'm':0.02406, + 'n':0.06749, + 'o':0.07507, + 'p':0.01929, + 'q':0.00095, + 'r':0.05987, + 's':0.06327, + 't':0.09056, + 'u':0.02758, + 'v':0.00978, + 'w':0.02360, + 'x':0.00150, + 'y':0.01974, + 'z':0.00074} + + +def _xorhex(a, b): + """ + XORs a against b and returns the result + a and b are expected to be int values + + XOR Truth Table + Input Output + A B + 0 0 0 + 0 1 1 + 1 0 1 + 1 1 0 + """ + long_res = a ^ b + string_res = '%x' % long_res # returns a string version + return long_res + + +def load_data(filename): + "Loads the data of the filename into a table" + file = open(filename, "r") + stuff = file.read() + return [int(val) for val in stuff.split(',')] + +def all_passwords(range, length): + ''' + Returns all possible passwords given a range of ascii_values, and the length of the password + ''' + return list(set(itertools.combinations(range*3, length))) + +def apply_password(value, password): + """ + Applies a given password to the message. + """ + long_pass = repeat_password(password, len(value)) + return [_xorhex(a, b) for a, b in zip(value, long_pass)] + + +def repeat_password(password, length): + """ + Repeats a password to match the requested size. + Ex : [1, 2, 3], 7 => [1, 2, 3, 1, 2, 3, 1] + """ + repeat = length / len(password) + rest = length % len(password) + + return password * repeat + password[:rest] + +def all_solutions(data, pass_range, length): + """ + Prints all possible solutions + """ + res = [] + all_pass = all_passwords(pass_range, length) + total = len(all_pass) + print total + idx = 0 + for password in all_pass: + idx += 1 + print "%d / %d" % (idx, total) + sol = apply_password(data, password) + res.append([to_ascii(sol), password]) + return res + +def to_ascii(data): + """ + Turns a list of ascii values into a proper string + """ + res = "" + for el in data: + res += chr(el) + return res + +def discard_non_printable(solutions): + """ + We discard from a list of hex all the elements that cannot be printed. + #FIXME: Do better + """ + res = [] + for val in solutions: + + is_printable = True + for v in val[0]: + if not(v in printable_letters): + is_printable = False + + if is_printable: + res.append(val) + + return res + +def letter_freq(work_val): + """ + Calculate the frequency of the elements in the string val + Returns a dictionary of the form {element: frequency} for all elements of val. + Only present elements will be present in the dictionary (frequency > 0) + """ + counts = map(work_val.count ,work_val) # gets count of each element of hex_val + freqs = [(float(x) / len(work_val)) for x in counts] + #freqs = [x for x in counts] + return dict(zip(work_val,freqs)) # transforms into dict of unique values associated to frequencies + +def chi2(ref_dict, test_dict, length): + """ + Performs a chi2 adequation testing. + + Given a reference dictionary and a corresponding dctionary to be tested, returns a score corresponding to + the similitude between the test data and the reference data. + The lower the score, the higher the similitude. + + Dictionaries are expected to be under the {value:frequency} form. + Example : chi2({a:0.08, b:0.92}, {a:0.80, b:0.20}, 12) + + length corresponds to the number of elements needed to acquire test_dict + + Returns a float value + + """ + my_keys = ref_dict.keys() + score = 0 + for m in my_keys: + if test_dict.has_key(m): # testing if occurence happened + freq_val = length * test_dict[m] + else: + freq_val = 0 + ref_val = length * ref_dict[m] + score += pow(freq_val - ref_val, 2) / ref_val + return score + +def strip_string_to_lowercase(s): + """ + Returns s in lowercase, with all its special characters stripped + Taken from here: http://stackoverflow.com/questions/638893/what-is-the-most-efficient-way-in-python-to-convert-a-string-to-all-lowercase-st#639325 + """ + tmpStr = s.lower().strip() + retStrList = [] + for x in tmpStr: + if x in string.ascii_lowercase: + retStrList.append(x) + + return ''.join(retStrList) + +def close_to_english(element): + """ + Returns a score of likeliness for element to be english + The computed score is the chi2 adequation test. + + The closest to 0, the most probable the sentence is to be in english + Table can be found here, solutions at row 25 (english is 26 degrees of liberty). + http://www.medcalc.org/manual/chi-square-table.php to get the values to compare to + """ + # cleans the hex val of special characters, and puts to lowercase + stripped_el = strip_string_to_lowercase(element) + length_el = len(stripped_el) + #calculates the letters frequency of element + freqs = letter_freq(stripped_el) + + return chi2(en_freq, freqs, length_el) + +def find_most_probable(data, pass_range, length): + solutions = all_solutions(data, pass_range, length) + # discards easy fuck ups + solutions = discard_non_printable(solutions) + + best_candidate = "" + best_chi = 1000000 + for sol in solutions: + score = close_to_english(sol[0]) + if score < best_chi: + best_chi = score + best_candidate = sol + + return best_candidate + +def get_solution(data, pass_range, length): + solution = find_most_probable(data, ascii_range, 3)[0] + sum = 0 + for val in solution: + sum+=ord(val) + return sum + + +if __name__ == '__main__': + data = load_data("e_59_cipher1.txt") + ascii_range = range(ord('a'), ord('z') + 1 ) + print get_solution(data, ascii_range, 3) \ No newline at end of file diff --git a/e_59_cipher1.txt b/e_59_cipher1.txt new file mode 100644 index 0000000..c3ce21e --- /dev/null +++ b/e_59_cipher1.txt @@ -0,0 +1 @@ +79,59,12,2,79,35,8,28,20,2,3,68,8,9,68,45,0,12,9,67,68,4,7,5,23,27,1,21,79,85,78,79,85,71,38,10,71,27,12,2,79,6,2,8,13,9,1,13,9,8,68,19,7,1,71,56,11,21,11,68,6,3,22,2,14,0,30,79,1,31,6,23,19,10,0,73,79,44,2,79,19,6,28,68,16,6,16,15,79,35,8,11,72,71,14,10,3,79,12,2,79,19,6,28,68,32,0,0,73,79,86,71,39,1,71,24,5,20,79,13,9,79,16,15,10,68,5,10,3,14,1,10,14,1,3,71,24,13,19,7,68,32,0,0,73,79,87,71,39,1,71,12,22,2,14,16,2,11,68,2,25,1,21,22,16,15,6,10,0,79,16,15,10,22,2,79,13,20,65,68,41,0,16,15,6,10,0,79,1,31,6,23,19,28,68,19,7,5,19,79,12,2,79,0,14,11,10,64,27,68,10,14,15,2,65,68,83,79,40,14,9,1,71,6,16,20,10,8,1,79,19,6,28,68,14,1,68,15,6,9,75,79,5,9,11,68,19,7,13,20,79,8,14,9,1,71,8,13,17,10,23,71,3,13,0,7,16,71,27,11,71,10,18,2,29,29,8,1,1,73,79,81,71,59,12,2,79,8,14,8,12,19,79,23,15,6,10,2,28,68,19,7,22,8,26,3,15,79,16,15,10,68,3,14,22,12,1,1,20,28,72,71,14,10,3,79,16,15,10,68,3,14,22,12,1,1,20,28,68,4,14,10,71,1,1,17,10,22,71,10,28,19,6,10,0,26,13,20,7,68,14,27,74,71,89,68,32,0,0,71,28,1,9,27,68,45,0,12,9,79,16,15,10,68,37,14,20,19,6,23,19,79,83,71,27,11,71,27,1,11,3,68,2,25,1,21,22,11,9,10,68,6,13,11,18,27,68,19,7,1,71,3,13,0,7,16,71,28,11,71,27,12,6,27,68,2,25,1,21,22,11,9,10,68,10,6,3,15,27,68,5,10,8,14,10,18,2,79,6,2,12,5,18,28,1,71,0,2,71,7,13,20,79,16,2,28,16,14,2,11,9,22,74,71,87,68,45,0,12,9,79,12,14,2,23,2,3,2,71,24,5,20,79,10,8,27,68,19,7,1,71,3,13,0,7,16,92,79,12,2,79,19,6,28,68,8,1,8,30,79,5,71,24,13,19,1,1,20,28,68,19,0,68,19,7,1,71,3,13,0,7,16,73,79,93,71,59,12,2,79,11,9,10,68,16,7,11,71,6,23,71,27,12,2,79,16,21,26,1,71,3,13,0,7,16,75,79,19,15,0,68,0,6,18,2,28,68,11,6,3,15,27,68,19,0,68,2,25,1,21,22,11,9,10,72,71,24,5,20,79,3,8,6,10,0,79,16,8,79,7,8,2,1,71,6,10,19,0,68,19,7,1,71,24,11,21,3,0,73,79,85,87,79,38,18,27,68,6,3,16,15,0,17,0,7,68,19,7,1,71,24,11,21,3,0,71,24,5,20,79,9,6,11,1,71,27,12,21,0,17,0,7,68,15,6,9,75,79,16,15,10,68,16,0,22,11,11,68,3,6,0,9,72,16,71,29,1,4,0,3,9,6,30,2,79,12,14,2,68,16,7,1,9,79,12,2,79,7,6,2,1,73,79,85,86,79,33,17,10,10,71,6,10,71,7,13,20,79,11,16,1,68,11,14,10,3,79,5,9,11,68,6,2,11,9,8,68,15,6,23,71,0,19,9,79,20,2,0,20,11,10,72,71,7,1,71,24,5,20,79,10,8,27,68,6,12,7,2,31,16,2,11,74,71,94,86,71,45,17,19,79,16,8,79,5,11,3,68,16,7,11,71,13,1,11,6,1,17,10,0,71,7,13,10,79,5,9,11,68,6,12,7,2,31,16,2,11,68,15,6,9,75,79,12,2,79,3,6,25,1,71,27,12,2,79,22,14,8,12,19,79,16,8,79,6,2,12,11,10,10,68,4,7,13,11,11,22,2,1,68,8,9,68,32,0,0,73,79,85,84,79,48,15,10,29,71,14,22,2,79,22,2,13,11,21,1,69,71,59,12,14,28,68,14,28,68,9,0,16,71,14,68,23,7,29,20,6,7,6,3,68,5,6,22,19,7,68,21,10,23,18,3,16,14,1,3,71,9,22,8,2,68,15,26,9,6,1,68,23,14,23,20,6,11,9,79,11,21,79,20,11,14,10,75,79,16,15,6,23,71,29,1,5,6,22,19,7,68,4,0,9,2,28,68,1,29,11,10,79,35,8,11,74,86,91,68,52,0,68,19,7,1,71,56,11,21,11,68,5,10,7,6,2,1,71,7,17,10,14,10,71,14,10,3,79,8,14,25,1,3,79,12,2,29,1,71,0,10,71,10,5,21,27,12,71,14,9,8,1,3,71,26,23,73,79,44,2,79,19,6,28,68,1,26,8,11,79,11,1,79,17,9,9,5,14,3,13,9,8,68,11,0,18,2,79,5,9,11,68,1,14,13,19,7,2,18,3,10,2,28,23,73,79,37,9,11,68,16,10,68,15,14,18,2,79,23,2,10,10,71,7,13,20,79,3,11,0,22,30,67,68,19,7,1,71,8,8,8,29,29,71,0,2,71,27,12,2,79,11,9,3,29,71,60,11,9,79,11,1,79,16,15,10,68,33,14,16,15,10,22,73 diff --git a/projecteuler.sublime-workspace b/projecteuler.sublime-workspace index bce4f59..39c82cb 100644 --- a/projecteuler.sublime-workspace +++ b/projecteuler.sublime-workspace @@ -3,9 +3,33 @@ { "selected_items": [ + [ + "sol", + "solution" + ], + [ + "all_", + "all_pass" + ], + [ + "asci", + "ascii_range" + ], [ "all", - "all_permutations" + "all_passwords" + ], + [ + "find", + "find_most_probable" + ], + [ + "all_p", + "all_pass" + ], + [ + "rep", + "repeat_password" ], [ "ele", @@ -35,10 +59,6 @@ "ee", "election_id" ], - [ - "find", - "find_by_uuid" - ], [ "ca", "candidates" @@ -477,6 +497,14 @@ }, "file_history": [ + "/home/jll/Documents/01_perso/03_project_euler/e_59.py", + "/home/jll/Documents/01_perso/03_project_euler/README.markdown", + "/home/jll/Documents/01_perso/03_project_euler/e_59_cipher1.txt", + "/home/jll/Documents/01_perso/03_project_euler/e_18.py", + "/home/jll/Documents/01_perso/03_project_euler/e_13.data", + "/home/jll/Documents/01_perso/03_project_euler/e_18.data", + "/home/jll/Documents/01_perso/03_project_euler/e_22.data", + "/home/jll/Documents/01_perso/03_project_euler/e_22.py", "/home/jll/Documents/01_perso/03_project_euler/e_47.py", "/home/jll/Documents/01_perso/00_myelections/app/controllers/application_controller.rb", "/home/jll/Documents/01_perso/00_myelections/app/views/new_election_admin_mailer/new_election_admin_email.text.erb", @@ -596,15 +624,7 @@ "/home/jll/Documents/01_perso/18_blog/welcome-in-my-world/index.html", "/home/jll/Documents/01_perso/18_blog/posts.md", "/home/jll/Documents/01_perso/18_blog/404.md", - "/home/jll/Documents/01_perso/18_blog/_config.yml", - "/home/jll/Documents/01_perso/18_blog/_posts/2012-03-05-computer-vision-companies.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2011-10-17-17.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2011-10-17-home.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2011-10-13-job-space.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2012-09-07-ivolution-development-status-36.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2012-06-11-remove-all-your-thumbs-files.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2012-06-04-is-descartes-the-father-of-agile-development.markdown", - "/home/jll/Documents/01_perso/18_blog/_posts/2012-02-22-computer-vision-companies.markdown" + "/home/jll/Documents/01_perso/18_blog/_config.yml" ], "find": { @@ -821,7 +841,7 @@ }, "output.exec": { - "height": 236.0 + "height": 253.0 }, "output.jenkinsIndicator": {