Skip to content

Commit

Permalink
Add solution for problem 51.
Browse files Browse the repository at this point in the history
  • Loading branch information
FranzDiebold committed Jun 21, 2020
1 parent 8adef7b commit 88c5cde
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 71 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
12 changes: 12 additions & 0 deletions src/common/numbers.py
Original file line number Diff line number Diff line change
@@ -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')
6 changes: 6 additions & 0 deletions src/common/primes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
12 changes: 2 additions & 10 deletions src/p036_double_base_palindromes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
13 changes: 2 additions & 11 deletions src/p049_prime_permutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
84 changes: 84 additions & 0 deletions src/p051_prime_digit_replacements.py
Original file line number Diff line number Diff line change
@@ -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()
39 changes: 39 additions & 0 deletions test/common/test_numbers.py
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions test/common/test_primes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 0 additions & 34 deletions test/test_p036_double_base_palindromes.py

This file was deleted.

15 changes: 0 additions & 15 deletions test/test_p049_prime_permutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, []),
Expand Down
91 changes: 91 additions & 0 deletions test/test_p051_prime_digit_replacements.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 88c5cde

Please sign in to comment.