diff --git a/README.md b/README.md index 599a763..a9c5aa4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Project Euler](https://projecteuler.net) Solutions -[![solved: 50 problems](https://img.shields.io/badge/solved-50_problems-f93.svg)](./src) +[![solved: 51 problems](https://img.shields.io/badge/solved-51_problems-f93.svg)](./src) ![Python: 3.8](https://img.shields.io/badge/Python-3.8-3776ab.svg) [![Lint and Test](https://github.com/FranzDiebold/project-euler-solutions/workflows/Lint%20and%20Test/badge.svg)](https://github.com/FranzDiebold/project-euler-solutions/actions?query=workflow%3A%22Lint+and+Test%22) [![license: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](./LICENSE.md) diff --git a/src/common/numbers.py b/src/common/numbers.py new file mode 100644 index 0000000..74d78ed --- /dev/null +++ b/src/common/numbers.py @@ -0,0 +1,12 @@ +""" +Number utility functions. +""" + + +def decimal_to_binary(decimal_number: int, num_digits: int = 0) -> str: + """Get the binary representation of a given decimal number `decimal_number` as string.""" + binary_representation = '' + while decimal_number > 0: + binary_representation = str(decimal_number % 2) + binary_representation + decimal_number //= 2 + return binary_representation.rjust(num_digits, '0') diff --git a/src/common/primes.py b/src/common/primes.py index 32d4b34..2df03a7 100644 --- a/src/common/primes.py +++ b/src/common/primes.py @@ -55,6 +55,12 @@ def get_sorted_primes_list(threshold: int) -> List[int]: return [number for number in range(2, threshold) if is_prime(number)] +# pylint: disable=invalid-name +def get_sorted_n_digit_primes(n: int) -> List[int]: + """Get all primes with `n` digits as a sorted list.""" + return [number for number in range(pow(10, n - 1), pow(10, n)) if is_prime(number)] + + def get_primes_set(threshold: int) -> Set[int]: """Get all prime numbers up to a threshold `threshold` (exclusive) as set.""" return set(get_sorted_primes_list(threshold)) diff --git a/src/p036_double_base_palindromes.py b/src/p036_double_base_palindromes.py index 34960ef..866d9a1 100644 --- a/src/p036_double_base_palindromes.py +++ b/src/p036_double_base_palindromes.py @@ -12,21 +12,13 @@ from typing import Iterable from src.common.palindromes import is_palindromic_number - - -def _get_number_in_binary(number: int) -> str: - """Get the binary representation of a given decimal number `number` as string.""" - binary_representation = '' - while number > 0: - binary_representation = str(number % 2) + binary_representation - number //= 2 - return binary_representation +from src.common.numbers import decimal_to_binary def _get_double_base_palindromes(threshold: int) -> Iterable[int]: """Get numbers, which are paldindromic in base 10 and base 2.""" for number in range(threshold): - if is_palindromic_number(number) and is_palindromic_number(_get_number_in_binary(number)): + if is_palindromic_number(number) and is_palindromic_number(decimal_to_binary(number)): yield number diff --git a/src/p049_prime_permutations.py b/src/p049_prime_permutations.py index 871d427..86dffcf 100644 --- a/src/p049_prime_permutations.py +++ b/src/p049_prime_permutations.py @@ -13,21 +13,12 @@ """ # pylint: disable=invalid-name -from typing import List, Iterable, Tuple +from typing import Iterable, Tuple -from src.common.primes import is_prime +from src.common.primes import get_sorted_n_digit_primes from src.common.permutations import is_permutation -def get_sorted_n_digit_primes(n: int) -> List[int]: - """Get all primes with `n` digits as a sorted list.""" - n_digit_primes = [] - for i in range(pow(10, n - 1), pow(10, n)): - if is_prime(i): - n_digit_primes.append(i) - return n_digit_primes - - def get_n_digit_prime_arithmetic_sequences(n: int) -> Iterable[Tuple[int]]: """ Get arithmetic sequences made of three increasing numbers which have the following properties: diff --git a/src/p051_prime_digit_replacements.py b/src/p051_prime_digit_replacements.py new file mode 100644 index 0000000..c5037ed --- /dev/null +++ b/src/p051_prime_digit_replacements.py @@ -0,0 +1,84 @@ +""" +Problem 51: Prime digit replacements +https://projecteuler.net/problem=51 + +By replacing the 1st digit of the 2-digit number *3, it turns out that +six of the nine possible values: 13, 23, 43, 53, 73, and 83, are all prime. + +By replacing the 3rd and 4th digits of 56**3 with the same digit,this 5-digit number +is the first example having seven primes among the ten generated numbers, yielding the family: +56003, 56113, 56333, 56443, 56663, 56773, and 56993. +Consequently 56003, being the first member of this family, +is the smallest prime with this property. + +Find the smallest prime which, by replacing part of the number +(not necessarily adjacent digits) with the same digit, +is part of an eight prime value family. +""" + +from typing import Set, Iterable, List, Tuple + +from src.common.primes import get_sorted_n_digit_primes +from src.common.numbers import decimal_to_binary + + +def _get_number_family_primes(number_family: str, primes_set: Set[int]) -> Iterable[int]: + """ + Get all prime numbers for a given number family `number_family`. + For example, for `*3` it will yield `13`, `23`, `43`, `53`, `73`, and `83`. + """ + start_digit = 0 if number_family[0] != '*' else 1 + for i in range(start_digit, 10): + current_number = int(number_family.replace('*', str(i))) + if current_number in primes_set: + yield current_number + + +def _get_number_family(number_str: str, mask: int) -> str: + """Get the number family for a given number string `number_str` and a mask `mask`.""" + number_family = '' + number_str_len = len(number_str) + for idx, binary_mask in enumerate(decimal_to_binary(mask, number_str_len)): + number_family += number_str[idx] if binary_mask == '0' else '*' + return number_family + + +def _get_number_families(number: int) -> Iterable[str]: + """Get all number families for a given number `number`.""" + number_str = str(number) + for i in range(1, pow(2, len(number_str))): + yield _get_number_family(number_str, i) + + +def get_target_prime_family(target_prime_family_value: int) -> Tuple[int, str, List[int]]: + """Get a prime family for a given target value `target_prime_family_value`.""" + number_of_digits = 1 + while True: + print(f'Checking {number_of_digits} digit numbers...') + primes_list = get_sorted_n_digit_primes(number_of_digits) + primes_set = set(primes_list) + checked_prime_families: Set[str] = set() + + for prime_number in primes_list: + for prime_family in _get_number_families(prime_number): + if prime_family not in checked_prime_families: + checked_prime_families.add(prime_family) + prime_family_primes = list(_get_number_family_primes(prime_family, primes_set)) + prime_family_value = len(prime_family_primes) + if prime_family_value == target_prime_family_value: + return prime_family_primes[0], prime_family, prime_family_primes + + number_of_digits += 1 + + +def main() -> None: + """Main function.""" + target_prime_family_value = 8 + smallest_prime, prime_family, primes = get_target_prime_family(target_prime_family_value) + print(f'The smallest prime which, by replacing part of the number with the same digit, ' \ + f'is part of an {target_prime_family_value} prime value family is {smallest_prime}.') + print(f'The prime family is {prime_family} with the primes {primes}.') + + +if __name__ == '__main__': + main() diff --git a/test/common/test_numbers.py b/test/common/test_numbers.py new file mode 100644 index 0000000..2af361c --- /dev/null +++ b/test/common/test_numbers.py @@ -0,0 +1,39 @@ +""" +Number utility functions. +""" + +import pytest + + +@pytest.mark.parametrize('test_input_decimal_number,expected_result', [ + (1, '1'), + (2, '10'), + (3, '11'), + (4, '100'), + (5, '101'), + (6, '110'), + (7, '111'), + (42, '101010'), + (585, '1001001001'), +]) +def test_decimal_to_binary(test_input_decimal_number, expected_result): + # arrange + from src.common.numbers import decimal_to_binary + + # act + actual_result = decimal_to_binary(test_input_decimal_number) + + # assert + assert actual_result == expected_result + + +def test_decimal_to_binary_with_num_digits(): + # arrange + from src.common.numbers import decimal_to_binary + + # act + actual_result = decimal_to_binary(5, 6) + + # assert + expected_result = '000101' + assert actual_result == expected_result diff --git a/test/common/test_primes.py b/test/common/test_primes.py index 2792154..8597a66 100644 --- a/test/common/test_primes.py +++ b/test/common/test_primes.py @@ -66,6 +66,21 @@ def test_get_sorted_primes_list(): assert actual_result == expected_result +@pytest.mark.parametrize('test_input_n,expected_result', [ + (1, [2, 3, 5, 7]), + (2, [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]) +]) +def test_get_n_digit_primes(test_input_n, expected_result): + # arrange + from src.common.primes import get_sorted_n_digit_primes + + # act + actual_result = get_sorted_n_digit_primes(test_input_n) + + # assert + assert actual_result == expected_result + + def test_get_primes_set(): # arrange from src.common.primes import get_primes_set diff --git a/test/test_p036_double_base_palindromes.py b/test/test_p036_double_base_palindromes.py deleted file mode 100644 index d018fa2..0000000 --- a/test/test_p036_double_base_palindromes.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Problem 36: Double-base palindromes -http://projecteuler.net/problem=36 - -The decimal number, 585 = 1001001001 (binary), is palindromic in both bases. - -Find the sum of all numbers, less than one million, which are palindromic in base 10 and base 2. - -(Please note that the palindromic number, in either base, may not include leading zeros.) -""" - -import pytest - - -@pytest.mark.parametrize('test_input_number,expected_result', [ - (1, '1'), - (2, '10'), - (3, '11'), - (4, '100'), - (5, '101'), - (6, '110'), - (7, '111'), - (42, '101010'), - (585, '1001001001'), -]) -def test_get_number_in_binary(test_input_number, expected_result): - # arrange - from src.p036_double_base_palindromes import _get_number_in_binary - - # act - actual_result = _get_number_in_binary(test_input_number) - - # assert - assert actual_result == expected_result diff --git a/test/test_p049_prime_permutations.py b/test/test_p049_prime_permutations.py index 274a9d8..e87790d 100644 --- a/test/test_p049_prime_permutations.py +++ b/test/test_p049_prime_permutations.py @@ -15,21 +15,6 @@ import pytest -@pytest.mark.parametrize('test_input_n,expected_result', [ - (1, [2, 3, 5, 7]), - (2, [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]) -]) -def test_get_n_digit_primes(test_input_n, expected_result): - # arrange - from src.p049_prime_permutations import get_sorted_n_digit_primes - - # act - actual_result = get_sorted_n_digit_primes(test_input_n) - - # assert - assert actual_result == expected_result - - @pytest.mark.parametrize('test_input_n,expected_result', [ (1, []), (2, []), diff --git a/test/test_p051_prime_digit_replacements.py b/test/test_p051_prime_digit_replacements.py new file mode 100644 index 0000000..07c913e --- /dev/null +++ b/test/test_p051_prime_digit_replacements.py @@ -0,0 +1,91 @@ +""" +Problem 51: Prime digit replacements +https://projecteuler.net/problem=51 + +By replacing the 1st digit of the 2-digit number *3, it turns out that +six of the nine possible values: 13, 23, 43, 53, 73, and 83, are all prime. + +By replacing the 3rd and 4th digits of 56**3 with the same digit,this 5-digit number +is the first example having seven primes among the ten generated numbers, yielding the family: +56003, 56113, 56333, 56443, 56663, 56773, and 56993. +Consequently 56003, being the first member of this family, +is the smallest prime with this property. + +Find the smallest prime which, by replacing part of the number +(not necessarily adjacent digits) with the same digit, +is part of an eight prime value family. +""" + +import pytest + + +def test_get_number_family_primes(): + """ + Get all prime numbers for a given number family `number_family`. + For example, for `*3` it will yield `13`, `23`, `43`, `53`, `73`, and `83`. + """ + # arrange + from src.p051_prime_digit_replacements import _get_number_family_primes + + primes_set = { + 2, 3, 5, 7, + 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, + 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, + } + + # act + actual_result_iter = _get_number_family_primes('*3', primes_set) + + # assert + expected_result = {13, 23, 43, 53, 73, 83} + assert set(actual_result_iter) == expected_result + + +@pytest.mark.parametrize('test_input_number_str,test_input_mask,expected_result', [ + ('123', 1, '12*'), + ('123', 2, '1*3'), + ('123', 3, '1**'), + ('123', 4, '*23'), + ('123', 5, '*2*'), + ('123', 6, '**3'), + ('123', 7, '***'), +]) +def test_get_number_family(test_input_number_str, test_input_mask, expected_result): + """Get the number family for a given number string `number_str` and a mask `mask`.""" + # arrange + from src.p051_prime_digit_replacements import _get_number_family + + # act + actual_result = _get_number_family(test_input_number_str, test_input_mask) + + # assert + assert actual_result == expected_result + + +def test_get_number_families(): + """Get all number families for a given number `number`.""" + # arrange + from src.p051_prime_digit_replacements import _get_number_families + + # act + actual_result_iter = _get_number_families(123) + + # assert + expected_result = { '12*', '1*3', '1**', '*23', '*2*', '**3', '***' } + assert set(actual_result_iter) == expected_result + + +@pytest.mark.parametrize('test_input_target_prime_family_value,expected_result', [ + (6, (13, '*3', [13, 23, 43, 53, 73, 83])), + (7, (56003, '56**3', [56003, 56113, 56333, 56443, 56663, 56773, 56993])), +]) +def test_get_target_prime_family(test_input_target_prime_family_value, expected_result): + """Get a prime family for a given target value `target_prime_family_value`.""" + # arrange + from src.p051_prime_digit_replacements import get_target_prime_family + + # act + actual_result = get_target_prime_family(test_input_target_prime_family_value) + + # assert + assert actual_result == expected_result