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

-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":
{