Skip to content

Commit

Permalink
Add solution for problem 58.
Browse files Browse the repository at this point in the history
  • Loading branch information
FranzDiebold committed Jun 30, 2020
1 parent 0c9e55b commit b4912b5
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 16 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: 57 problems](https://img.shields.io/badge/solved-57_problems-f93.svg)](./src)
[![solved: 58 problems](https://img.shields.io/badge/solved-58_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
37 changes: 37 additions & 0 deletions src/common/number_spiral.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Number spiral utilities.
21 22 23 24 25
20 7 8 9 10
19 6 1 2 11
18 5 4 3 12
17 16 15 14 13
"""

from typing import Iterable, Tuple


def get_spiral_diagonal_values_with_size() -> Iterable[Tuple[int, int]]:
"""
Get the diagonal values of a number spiral.
Return tuples of `(<diagonal_value>, <spiral_size>)`.
"""
value = 1
current_half_size = 0
yield value, (2 * current_half_size + 1)
while True:
current_half_size += 1
for _ in range(4):
value += 2 * current_half_size
yield value, (2 * current_half_size + 1)


def get_spiral_diagonal_values_up_to_size(size: int) -> Iterable[int]:
"""Get the diagonal values for a `size` by `size` spiral."""
if size < 1 or size % 2 == 0:
raise ValueError('The size must be a positive odd number.')

for current_value, current_size in get_spiral_diagonal_values_with_size():
if current_size > size:
break
yield current_value
18 changes: 5 additions & 13 deletions src/p028_number_spiral_diagonals.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,18 @@
What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral formed in the same way?
"""

from typing import Iterable
from src.common.number_spiral import get_spiral_diagonal_values_up_to_size


def get_spiral_diagonal_values(size: int) -> Iterable[int]:
"""Get the diagonal values for a `size` by `size` spiral."""
if size < 1 or size % 2 == 0:
raise ValueError('The size must be a positive odd number.')

value = 1
yield value
for current_size in range(1, (size // 2) + 1):
for _ in range(4):
value += 2*current_size
yield value
def get_spiral_diagonal_values_sum(spiral_size: int) -> int:
"""Get the sum of the numbers on the diagonals in number spiral of size `spiral_size`."""
return sum(get_spiral_diagonal_values_up_to_size(spiral_size))


def main() -> None:
"""Main function."""
spiral_size = 1001
diagonal_sum = sum(get_spiral_diagonal_values(spiral_size))
diagonal_sum = get_spiral_diagonal_values_sum(spiral_size)
print(f'The sum of the numbers on the diagonals in a {spiral_size:,} by {spiral_size:,} ' \
f'spiral is {diagonal_sum:,}.')

Expand Down
62 changes: 62 additions & 0 deletions src/p058_spiral_primes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Problem 58: Spiral primes
https://projecteuler.net/problem=58
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%?
"""

from typing import Iterable, Tuple

from src.common.number_spiral import get_spiral_diagonal_values_with_size
from src.common.primes import is_prime


def get_spiral_diagonal_prime_ratios() -> Iterable[Tuple[int, int, int]]:
"""
For increasing number spiral sizes get the ratios of primes along the diagonals.
Returns tuples `(<spiral_size>, <number_of_primes_along_diagonals>, <diagonal_numbers_count>)`.
"""
number_of_primes = 0
total_number = 0
for value, size in get_spiral_diagonal_values_with_size():
total_number += 1
if value == size * size:
yield size, number_of_primes, total_number
else:
if is_prime(value):
number_of_primes += 1


def main() -> None:
"""Main function."""
threshold_inverse = 10
spiral_diagonal_prime_ratios_iter = get_spiral_diagonal_prime_ratios()
next(spiral_diagonal_prime_ratios_iter)
for size, number_of_primes, total_number in spiral_diagonal_prime_ratios_iter:
if number_of_primes * threshold_inverse < total_number:
print(f'The side length of the square spiral for which the ratio of primes ' \
f'along both diagonals first falls below 10% is {size:,}.')
print(f'This ratio is {number_of_primes:,} / {total_number:,}.')
break


if __name__ == '__main__':
main()
48 changes: 48 additions & 0 deletions test/common/test_number_spiral.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
Number spiral utilities.
21 22 23 24 25
20 7 8 9 10
19 6 1 2 11
18 5 4 3 12
17 16 15 14 13
"""


def test_get_spiral_diagonal_values_with_size():
# arrange
from src.common.number_spiral import get_spiral_diagonal_values_with_size

# act
actual_result_iter = get_spiral_diagonal_values_with_size()

# assert
expected_result = [
(1, 1),
(3, 3),
(5, 3),
(7, 3),
(9, 3),
(13, 5),
(17, 5),
(21, 5),
(25, 5),
(31, 7),
(37, 7),
(43, 7),
(49, 7),
]
for expected_value in expected_result:
assert next(actual_result_iter) == expected_value


def test_get_spiral_diagonal_values_up_to_size():
# arrange
from src.common.number_spiral import get_spiral_diagonal_values_up_to_size

# act
actual_result_iter = get_spiral_diagonal_values_up_to_size(7)

# assert
expected_result = [1, 3, 5, 7, 9, 13, 17, 21, 25, 31, 37, 43, 49]
assert list(actual_result_iter) == expected_result
4 changes: 2 additions & 2 deletions test/test_p028_number_spiral_diagonals.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

def test_get_spiral_diagonal_values_sum():
# arrange
from src.p028_number_spiral_diagonals import get_spiral_diagonal_values
from src.p028_number_spiral_diagonals import get_spiral_diagonal_values_sum

# act
actual_result = sum(get_spiral_diagonal_values(5))
actual_result = get_spiral_diagonal_values_sum(5)

# assert
expected_result = 101
Expand Down
42 changes: 42 additions & 0 deletions test/test_p058_spiral_primes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Problem 58: Spiral primes
https://projecteuler.net/problem=58
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%?
"""


def test_get_spiral_diagonal_prime_ratios():
# arrange
from src.p058_spiral_primes import get_spiral_diagonal_prime_ratios

# act
actual_result_iter = get_spiral_diagonal_prime_ratios()

# assert
expected_result = [
(1, 0, 1),
(3, 3, 5),
(5, 5, 9),
(7, 8, 13),
]
for expected_value in expected_result:
assert next(actual_result_iter) == expected_value

0 comments on commit b4912b5

Please sign in to comment.