From 8935852ac1fdeb987610f603e5c4bf9099344b85 Mon Sep 17 00:00:00 2001 From: Franz Diebold Date: Sat, 20 Jun 2020 09:03:37 +0200 Subject: [PATCH] Add solution for problem 49. --- README.md | 2 +- src/common/permutations.py | 17 +++++++- src/p049_prime_permutations.py | 64 ++++++++++++++++++++++++++++ test/common/test_permutations.py | 35 +++++++++++++++ test/test_p049_prime_permutations.py | 58 +++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/p049_prime_permutations.py create mode 100644 test/test_p049_prime_permutations.py diff --git a/README.md b/README.md index 240ca9c..0242ede 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Project Euler](https://projecteuler.net) Solutions -[![solved: 48 problems](https://img.shields.io/badge/solved-48_problems-f93.svg)](./src) +[![solved: 49 problems](https://img.shields.io/badge/solved-49_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/permutations.py b/src/common/permutations.py index 872707f..fcccd4a 100644 --- a/src/common/permutations.py +++ b/src/common/permutations.py @@ -3,8 +3,9 @@ """ # pylint: disable=invalid-name -from typing import Iterable, List, TypeVar +from typing import Iterable, List, TypeVar, Dict, Union import copy +from collections import defaultdict from src.common.calculations import calculate_product @@ -38,3 +39,17 @@ def get_permutations(sorted_items: List[T]) -> Iterable[List[T]]: """Get all permutations of a given list of sorted items `sorted_items`.""" for n in range(1, get_number_of_permutations(sorted_items) + 1): yield get_nth_lexicographic_permutation(n, sorted_items) + + +def _get_characters_map(content: str) -> Dict[str, int]: + """Get the characters of a given string `content` as a map, + i.e. `abbccc` -> `{ 'a': 1, 'b': 2, 'c': 3 }`.""" + characters_map = defaultdict(int) + for character in content: + characters_map[character] += 1 + return characters_map + + +def is_permutation(a: Union[int, str], b: Union[int, str]) -> bool: + """Check if `a` is a permutation of `b`.""" + return _get_characters_map(str(a)) == _get_characters_map(str(b)) diff --git a/src/p049_prime_permutations.py b/src/p049_prime_permutations.py new file mode 100644 index 0000000..871d427 --- /dev/null +++ b/src/p049_prime_permutations.py @@ -0,0 +1,64 @@ +""" +Problem 49: Prime permutations +https://projecteuler.net/problem=49 + +The arithmetic sequence, 1487, 4817, 8147, in which each of the terms increases by 3330, +is unusual in two ways: (i) each of the three terms are prime, and, +(ii) each of the 4-digit numbers are permutations of one another. + +There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes, +exhibiting this property, but there is one other 4-digit increasing sequence. + +What 12-digit number do you form by concatenating the three terms in this sequence? +""" +# pylint: disable=invalid-name + +from typing import List, Iterable, Tuple + +from src.common.primes import is_prime +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: + (i) each of the three terms are prime, and, + (ii) each of the 4-digit numbers are permutations of one another, and, + (iii) they are equidistant. + """ + n_digit_primes_list = get_sorted_n_digit_primes(n) + n_digit_primes_set = set(n_digit_primes_list) + num_n_digit_primes = len(n_digit_primes_list) + for idx1 in range(num_n_digit_primes): + prime_1 = n_digit_primes_list[idx1] + for idx2 in range(idx1 + 1, num_n_digit_primes): + prime_2 = n_digit_primes_list[idx2] + prime_3 = prime_2 + (prime_2 - prime_1) + if prime_3 in n_digit_primes_set and \ + is_permutation(prime_1, prime_2) and \ + is_permutation(prime_2, prime_3): + yield (prime_1, prime_2, prime_3) + + +def main() -> None: + """Main function.""" + n = 4 + arithmetic_sequences = get_n_digit_prime_arithmetic_sequences(n) + next(arithmetic_sequences) + relevant_arithmetic_sequence = next(arithmetic_sequences) + concatenated_number = ''.join([str(number) for number in relevant_arithmetic_sequence]) + print(f'The arithmetic sequence is {relevant_arithmetic_sequence} ' \ + f'and the corresponding 12-digit number is {concatenated_number}.') + + +if __name__ == '__main__': + main() diff --git a/test/common/test_permutations.py b/test/common/test_permutations.py index 8a8f426..5f3c07e 100644 --- a/test/common/test_permutations.py +++ b/test/common/test_permutations.py @@ -57,3 +57,38 @@ def test_get_permutations(): [2, 1, 0], ] assert list(actual_result) == expected_result + + +@pytest.mark.parametrize('test_input_content,expected_result', [ + ('', {}), + ('a', {'a': 1}), + ('abbccc', {'a': 1, 'b': 2, 'c': 3}), +]) +def test_get_characters_map(test_input_content, expected_result): + # arrange + from src.common.permutations import _get_characters_map + + # act + actual_result = _get_characters_map(test_input_content) + + # assert + assert actual_result == expected_result + + +@pytest.mark.parametrize('test_input_a,test_input_b,expected_result', [ + ('', '', True), + ('a', 'a', True), + ('a', 'b', False), + ('abc', 'acb', True), + ('abc', 'abcc', False), + (12345, 24351, True), +]) +def test_is_permutation(test_input_a, test_input_b, expected_result): + # arrange + from src.common.permutations import is_permutation + + # act + actual_result = is_permutation(test_input_a, test_input_b) + + # assert + assert actual_result == expected_result diff --git a/test/test_p049_prime_permutations.py b/test/test_p049_prime_permutations.py new file mode 100644 index 0000000..274a9d8 --- /dev/null +++ b/test/test_p049_prime_permutations.py @@ -0,0 +1,58 @@ +""" +Problem 49: Prime permutations +https://projecteuler.net/problem=49 + +The arithmetic sequence, 1487, 4817, 8147, in which each of the terms increases by 3330, +is unusual in two ways: (i) each of the three terms are prime, and, +(ii) each of the 4-digit numbers are permutations of one another. + +There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes, +exhibiting this property, but there is one other 4-digit increasing sequence. + +What 12-digit number do you form by concatenating the three terms in this sequence? +""" + +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, []), + (3, []), +]) +def test_get_n_digit_prime_arithmetic_sequences_no_sequences(test_input_n, expected_result): + # arrange + from src.p049_prime_permutations import get_n_digit_prime_arithmetic_sequences + + # act + actual_result_iter = get_n_digit_prime_arithmetic_sequences(test_input_n) + + # assert + assert list(actual_result_iter) == expected_result + + +def test_get_n_digit_prime_arithmetic_sequences_first_4_digit_sequence(): + # arrange + from src.p049_prime_permutations import get_n_digit_prime_arithmetic_sequences + + # act + actual_result_iter = get_n_digit_prime_arithmetic_sequences(4) + + # assert + expected_result = (1487, 4817, 8147) + assert next(actual_result_iter) == expected_result