Skip to content

Commit 029ec46

Browse files
author
Sebastien Ollquist
committed
added local typing
1 parent 93fb555 commit 029ec46

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

Diff for: project_euler/problem_060/sol1.py

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""
2+
Project Euler Problem 60: https://projecteuler.net/problem=60
3+
4+
Prime Pair Sets
5+
6+
Problem: The primes 3, 7, 109, and 673, are quite remarkable. By taking any two
7+
primes and concatenating them in any order the result will always be prime.
8+
For example, taking 7 and 109, both 7109 and 1097 are prime. The sum of these four
9+
primes, 792, represents the lowest sum for a set of four primes with this property.
10+
11+
Question: Find the lowest sum for a set of five primes for which
12+
any two primes concatenate to produce another prime.
13+
14+
Explanation of the solution: the idea in finding a set of primes that satisfy the
15+
concatenation condition is to build a graph of primes where a node would represent a
16+
prime and an edge between two nodes would indicate that the two primes form a valid
17+
pair. Hence, the solution to the problem can be computed more efficiently since
18+
it does not require brute-forcing all possible combinations.
19+
"""
20+
21+
from itertools import combinations
22+
23+
24+
def sieve_of_eratosthenes(limit: int) -> set[int]:
25+
"""Generate primes up to a limit using the Sieve of Eratosthenes and return them
26+
as a set.
27+
28+
Parameters
29+
----------
30+
limit : int
31+
The upper limit to generate primes up to.
32+
33+
Returns
34+
----------
35+
set[int]
36+
A set of prime numbers up to the limit.
37+
"""
38+
sieve: list = [True] * (limit + 1)
39+
primes_set = set()
40+
for p in range(2, limit + 1):
41+
if sieve[p]:
42+
primes_set.add(p)
43+
for i in range(p * p, limit + 1, p):
44+
sieve[i] = False
45+
return primes_set
46+
47+
48+
def is_prime(n: int) -> bool:
49+
"""Checks whether a number is prime or not.
50+
51+
Parameters
52+
----------
53+
n : int
54+
The number to be checked.
55+
56+
Returns
57+
----------
58+
bool
59+
True if the number is prime, False otherwise.
60+
"""
61+
if n < 2:
62+
return False
63+
return all(n % i != 0 for i in range(3, int(n**0.5) + 1, 2))
64+
65+
66+
def valid_pair(p1: int, p2: int) -> bool:
67+
"""Checks whether a pair of primes concatenated both ways is prime or not.
68+
69+
Parameters
70+
----------
71+
p1 : int
72+
The first prime number.
73+
p2 : int
74+
The second prime number.
75+
76+
Returns
77+
----------
78+
bool
79+
True if the pair is valid, False otherwise.
80+
"""
81+
return is_prime(int(str(p1) + str(p2))) and is_prime(int(str(p2) + str(p1)))
82+
83+
84+
def build_graph(prime_set: set[int]) -> dict[int, set[int]]:
85+
"""Builds a graph of primes where each prime is a node and an edge exists
86+
between two primes if they form a valid pair.
87+
88+
Parameters
89+
----------
90+
prime_set : set[int]
91+
A set of prime numbers.
92+
93+
Returns
94+
----------
95+
dict[int, set[int]]
96+
A graph of primes.
97+
"""
98+
graph: dict[int, set[int]] = {p: set() for p in prime_set}
99+
for p1, p2 in combinations(prime_set, 2):
100+
if valid_pair(p1, p2):
101+
graph[p1].add(p2)
102+
graph[p2].add(p1)
103+
return graph
104+
105+
106+
def find_cliques(
107+
node: int, graph: dict[int, set[int]], clique: set[int], depth: int
108+
) -> list[set[int]]:
109+
"""The problem of finding a set of n primes that all satisfy
110+
the concatenation condition can be reduced to that of finding
111+
a clique of size n in the graph of primes.
112+
113+
Parameters
114+
----------
115+
node : int
116+
The node to be considered.
117+
graph : dict[int, set[int]]
118+
The graph of primes.
119+
clique : set[int]
120+
The set of primes that form a clique.
121+
depth : int
122+
The size of the clique.
123+
124+
Returns
125+
----------
126+
list[set[int]]
127+
A list of sets of primes that form cliques of size n.
128+
"""
129+
if depth == 1:
130+
return [clique]
131+
cliques: list[set[int]] = []
132+
for neighbour in graph[node]:
133+
if all(neighbour in graph[member] for member in clique):
134+
cliques.extend(
135+
find_cliques(neighbour, graph, clique | {neighbour}, depth - 1)
136+
)
137+
return cliques
138+
139+
140+
def solution(n: int = 5) -> int:
141+
"""Aims at finding the lowest sum for a set of five primes for which any two primes
142+
concatenate to produce another prime.
143+
144+
Parameters
145+
----------
146+
n : int
147+
The size of the set of primes (5 by default)
148+
149+
Returns
150+
----------
151+
int
152+
The lowest sum for a set of five primes for which any two primes concatenate to
153+
produce another prime.
154+
155+
>>> solution()
156+
26033
157+
"""
158+
limit: int = 10000 # the limit is arbitrary but is sufficient for the problem
159+
prime_set: set[int] = sieve_of_eratosthenes(limit)
160+
prime_graph: dict[int, set[int]] = build_graph(prime_set)
161+
162+
for prime in prime_set:
163+
cliques = find_cliques(prime, prime_graph, {prime}, depth=n)
164+
if cliques:
165+
return min(sum(clique) for clique in cliques)
166+
return -1
167+
168+
169+
if __name__ == "__main__":
170+
print(f"{solution() = }")

0 commit comments

Comments
 (0)