From b246d56acd6c4048d123d7935943cee901a821d4 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Sat, 13 Jul 2019 23:58:38 -0400 Subject: [PATCH] Moved 1 to 5 to Python and learned a ton. --- .gitignore | 4 +- python/e001.py | 11 +++++ python/e002.py | 24 +++++++++++ python/e003.py | 9 ++++ python/e004.py | 66 +++++++++++++++++++++++++++++ python/e005.py | 29 +++++++++++++ python/e006.py | 5 +++ python/lib_fibonacci.py | 44 ++++++++++++++++++++ python/lib_fibonacci_tests.py | 35 ++++++++++++++++ python/lib_misc.py | 78 +++++++++++++++++++++++++++++++++++ python/lib_misc_tests.py | 47 +++++++++++++++++++++ python/lib_prime.py | 27 ++++++++++++ python/lib_prime_tests.py | 25 +++++++++++ 13 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 python/e001.py create mode 100644 python/e002.py create mode 100644 python/e003.py create mode 100644 python/e004.py create mode 100644 python/e005.py create mode 100644 python/e006.py create mode 100644 python/lib_fibonacci.py create mode 100644 python/lib_fibonacci_tests.py create mode 100644 python/lib_misc.py create mode 100644 python/lib_misc_tests.py create mode 100644 python/lib_prime.py create mode 100644 python/lib_prime_tests.py diff --git a/.gitignore b/.gitignore index 2bea37b..5cf56ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ todo.txt -euler.sublime-workspace -euler.sublime-project +euler-python.sublime-project +euler-python.sublime-workspace *.swp __pycache__ .ipynb_checkpoints diff --git a/python/e001.py b/python/e001.py new file mode 100644 index 0000000..42267db --- /dev/null +++ b/python/e001.py @@ -0,0 +1,11 @@ +def get_sum_of_natural_dividable_by_3_and_5_below(m): + return sum([x for x in range(m) if x % 3 == 0 or x % 5 == 0]) + + +def euler_001(): + return get_sum_of_natural_dividable_by_3_and_5_below(1000) + + +assert(get_sum_of_natural_dividable_by_3_and_5_below(10) == 23) +assert(euler_001() == 233168) +print("e001.py: {}".format(euler_001())) diff --git a/python/e002.py b/python/e002.py new file mode 100644 index 0000000..fa23414 --- /dev/null +++ b/python/e002.py @@ -0,0 +1,24 @@ +from lib_fibonacci import fibonacci_generator_smaller + + +def euler_002(): + fs = [f for f in fibonacci_generator_smaller(4000000) if f % 2 == 0] + return sum(fs) + + +assert(euler_002() == 4613732) +print("e002.py: {}".format(euler_002())) + + +def euler_002_simple(): + """ Sometimes it is nice to keep it simple. Instead of using three + library functions just implement one straightforward function. """ + r, a, b = 0, 0, 1 + while b < 4000000: + if b % 2 == 0: + r += b + a, b = b, a + b + return r + + +assert(euler_002() == euler_002_simple()) diff --git a/python/e003.py b/python/e003.py new file mode 100644 index 0000000..94bc087 --- /dev/null +++ b/python/e003.py @@ -0,0 +1,9 @@ +from lib_prime import prime_factors + + +def euler_003(): + return prime_factors(600851475143)[-1] + + +assert(euler_003() == 6857) +print("e003.py: {}".format(euler_003())) diff --git a/python/e004.py b/python/e004.py new file mode 100644 index 0000000..62c3d6f --- /dev/null +++ b/python/e004.py @@ -0,0 +1,66 @@ +from lib_misc import is_palindrome + + +def euler_004(): + r = 0 + for a in range(999, 99, -1): + for b in range(a, r // a, -1): + if is_palindrome(a * b) and a * b > r: + r = a * b + return r + + +def euler_004_original(): + """ The solution that I came up with originally. """ + r = 0 + for a in range(999, 99, -1): + if a * a < r: + break + for b in range(a, 99, -1): + c = a * b + if c > r and is_palindrome(c): + r = c + return r + + +def euler_004_forum(): + """ + A solution from etatsui in the project Euler forum. + It is impressive to see how much fast it is relying on + mathematical tricks: + 11(9091a + 910b + 100c) = mn; + Let: + 11 * 10 < m < 11 * 90 + """ + for a in range(9, 0, -1): + for b in range(9, -1, -1): + for c in range(9, -1, -1): + num = 9091 * a + 910 * b + 100 * c + for m in range(90, 9, -1): + if num % m == 0: + if num / m > 999: + break + else: + result = num * 11 + return result + + +assert(euler_004() == 906609) +print("e004.py: {}".format(euler_004())) + + +def time_tests(): + from timeit import timeit + assert(euler_004() == 906609) + assert(euler_004_original() == 906609) + assert(euler_004_forum() == 906609) + + print(timeit(lambda: euler_004(), number=100)) + print(timeit(lambda: euler_004_original(), number=100)) + print(timeit(lambda: euler_004_forum(), number=100)) + + +# time_tests() +# 0.5044660240000667 +# 0.7896412069985672 +# 0.04794573199978913 diff --git a/python/e005.py b/python/e005.py new file mode 100644 index 0000000..ad0ec27 --- /dev/null +++ b/python/e005.py @@ -0,0 +1,29 @@ +from lib_prime import prime_factors_count +from lib_misc import product + + +def get_number_divisible(n): + """ Returns the lowest number that is divisible + by all numbers from 1 to n. We calculate the factors + for all numbers and make sure that the minimum number + for each factor is included in the solution. """ + f = {} + for i in range(2, n + 1): + for prime, count in prime_factors_count(i).items(): + try: + f[prime] = max(f[prime], count) + except KeyError: + f[prime] = count + n = product([prime**count for prime, count in f.items()]) + return n + + +assert(get_number_divisible(10) == 2520) + + +def euler_005(): + return get_number_divisible(20) + + +assert(euler_005() == 232792560) +print("e005.py: {}".format(euler_005())) diff --git a/python/e006.py b/python/e006.py new file mode 100644 index 0000000..373b303 --- /dev/null +++ b/python/e006.py @@ -0,0 +1,5 @@ +def euler_006(): + return 0 + +assert(euler_006() == 233168) +print("e006.py: {}".format(euler_006())) diff --git a/python/lib_fibonacci.py b/python/lib_fibonacci.py new file mode 100644 index 0000000..4d2623a --- /dev/null +++ b/python/lib_fibonacci.py @@ -0,0 +1,44 @@ +def fibonacci_generator(): + """ + Fibonacci generator function that starts with 1, 1, 2, 3, ... + + :returns: generator that yields fibonacci numbers + """ + a = 0 + b = 1 + yield b + while True: + a, b = b, (a + b) + yield b + + +def fibonacci_generator_smaller(n): + """ + + :param n: generator yields all fibonacci numbers smaller n + :returns: generator that yields fibonacci numbers + + """ + g = fibonacci_generator() + x = next(g) + while x < n: + yield x + x = next(g) + + +def fibonacci_nth(n): + """ + + :param n: index of fibonacci that should be returned + :returns: nth fibonacci number + + """ + if n < 1: + return 0 + g = fibonacci_generator() + i = 1 + x = next(g) + while i < n: + i += 1 + x = next(g) + return x diff --git a/python/lib_fibonacci_tests.py b/python/lib_fibonacci_tests.py new file mode 100644 index 0000000..d6b3a94 --- /dev/null +++ b/python/lib_fibonacci_tests.py @@ -0,0 +1,35 @@ +import unittest +try: + from .lib_fibonacci import fibonacci_generator + from .lib_fibonacci import fibonacci_generator_smaller + from .lib_fibonacci import fibonacci_nth +except ModuleNotFoundError: + from lib_fibonacci import fibonacci_generator + from lib_fibonacci import fibonacci_generator_smaller + from lib_fibonacci import fibonacci_nth + + +class TestFibonacciMethods(unittest.TestCase): + + def test_fibonacci_generator(self): + g = fibonacci_generator() + fs = [next(g) for _ in range(10)] + self.assertEqual(fs, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) + self.assertNotEqual(fs, [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) + + def test_fibonacci_generator_smaller(self): + g = fibonacci_generator_smaller(20) + self.assertEqual(list(g), [1, 1, 2, 3, 5, 8, 13]) + + def test_fibonacci_nth(self): + self.assertEqual(fibonacci_nth(0), 0) + self.assertEqual(fibonacci_nth(1), 1) + self.assertEqual(fibonacci_nth(2), 1) + self.assertEqual(fibonacci_nth(3), 2) + self.assertEqual(fibonacci_nth(4), 3) + self.assertEqual(fibonacci_nth(5), 5) + self.assertEqual(fibonacci_nth(6), 8) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/lib_misc.py b/python/lib_misc.py new file mode 100644 index 0000000..ccba0f8 --- /dev/null +++ b/python/lib_misc.py @@ -0,0 +1,78 @@ +def get_digits_reversed(n): + """ + Returns a list of digits for n. + """ + digits, rest = [], n + while rest != 0: + digits.append(rest % 10) + rest //= 10 + return digits + + +def is_palindrome_string(n): + """ + Checks whether n is a palindrome number + with a string based algorithm. + + :param n: number to check + :returns: boolean + + """ + s = str(n) + return s == s[::-1] + + +def is_palindrome_integer(n): + """ + Checks whether n is a palindrome number + with an integer based algorithm. + + :param n: number to check + :returns: boolean + """ + digits = get_digits_reversed(n) + digits_count = len(digits) + for i in range(digits_count // 2): + if digits[i] != digits[digits_count - i - 1]: + return False + return True + + +def time_is_palindrom(): + """ I just want to check which option is faster. """ + from timeit import timeit + print(timeit(lambda: is_palindrome_integer(82445254428), number=100000)) + print(timeit(lambda: is_palindrome_string(82445254428), number=100000)) + # > 0.42330633100027626 + # > 0.07605635199979588 + + +is_palindrome = is_palindrome_string + + +def get_item_counts(l): + """ + Counts how often each element is part of the list. + + :param l: a list + :returns: a dictionary + """ + d = {} + for e in l: + try: + d[e] += 1 + except KeyError: + d[e] = 1 + return d + + +def product(l): + """ + Calculates the product of all items in the list. + + :param l: a list + :returns: a number that is the product of all elements in the list + """ + from functools import reduce + import operator + return reduce(operator.mul, l, 1) diff --git a/python/lib_misc_tests.py b/python/lib_misc_tests.py new file mode 100644 index 0000000..dd0375c --- /dev/null +++ b/python/lib_misc_tests.py @@ -0,0 +1,47 @@ +import unittest +try: + from .lib_misc import is_palindrome_integer + from .lib_misc import is_palindrome_string + from .lib_misc import get_digits_reversed + from .lib_misc import get_item_counts + from .lib_misc import product +except ModuleNotFoundError: + from lib_misc import is_palindrome_integer + from lib_misc import is_palindrome_string + from lib_misc import get_digits_reversed + from lib_misc import get_item_counts + from lib_misc import product + + +class TestPrimeMethods(unittest.TestCase): + + def test_is_palindrome_integer(self): + self.assertEqual(is_palindrome_integer(2), True) + self.assertEqual(is_palindrome_integer(888), True) + self.assertEqual(is_palindrome_integer(9009), True) + self.assertEqual(is_palindrome_integer(23), False) + self.assertEqual(is_palindrome_integer(9008), False) + + def test_is_palindrome_string(self): + self.assertEqual(is_palindrome_string(2), True) + self.assertEqual(is_palindrome_string(888), True) + self.assertEqual(is_palindrome_string(9009), True) + self.assertEqual(is_palindrome_string(23), False) + self.assertEqual(is_palindrome_string(9008), False) + + def test_get_digits_reversed(self): + self.assertEqual(get_digits_reversed(3), [3]) + self.assertEqual(get_digits_reversed(1234), [4, 3, 2, 1]) + self.assertEqual(get_digits_reversed(1000), [0, 0, 0, 1]) + + def test_get_item_counts(self): + self.assertEqual(get_item_counts([]), {}) + self.assertEqual(get_item_counts([1, 1, 3]), {1: 2, 3: 1}) + + def test_product(self): + self.assertEqual(product([2, 4, 8]), 64) + self.assertEqual(product([]), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/lib_prime.py b/python/lib_prime.py new file mode 100644 index 0000000..2f57935 --- /dev/null +++ b/python/lib_prime.py @@ -0,0 +1,27 @@ +try: + from lib_misc import get_item_counts +except ModuleNotFoundError: + from .lib_misc import get_item_counts + + +def prime_factors(n): + # TODO: Look into using a prime wheel instead. + factors = [] + rest = n + divisor = 2 + while rest % divisor == 0: + factors.append(divisor) + rest //= divisor + divisor = 3 + while divisor * divisor <= rest: + while rest % divisor == 0: + factors.append(divisor) + rest //= divisor + divisor += 2 + if rest != 1: + factors.append(rest) + return factors + + +def prime_factors_count(n): + return get_item_counts(prime_factors(n)) diff --git a/python/lib_prime_tests.py b/python/lib_prime_tests.py new file mode 100644 index 0000000..1621ea4 --- /dev/null +++ b/python/lib_prime_tests.py @@ -0,0 +1,25 @@ +import unittest +try: + from .lib_prime import prime_factors + from .lib_prime import prime_factors_count +except ModuleNotFoundError: + from lib_prime import prime_factors + from lib_prime import prime_factors_count + + +class TestPrimeMethods(unittest.TestCase): + + def test_prime_factors(self): + self.assertEqual(prime_factors(2), [2]) + self.assertEqual(prime_factors(5), [5]) + self.assertEqual(prime_factors(10), [2, 5]) + self.assertEqual(prime_factors(13), [13]) + self.assertEqual(prime_factors(147), [3, 7, 7]) + + def test_prime_factors_count(self): + self.assertEqual(prime_factors_count(2), {2: 1}) + self.assertEqual(prime_factors_count(147), {3: 1, 7: 2}) + + +if __name__ == '__main__': + unittest.main()