Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 00857b7

Browse files
authoredApr 1, 2023
Merge branch 'TheAlgorithms:master' into master
2 parents 0fd0583 + 84b6852 commit 00857b7

File tree

9 files changed

+423
-76
lines changed

9 files changed

+423
-76
lines changed
 

‎DIRECTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,8 @@
937937
* [Sol1](project_euler/problem_091/sol1.py)
938938
* Problem 092
939939
* [Sol1](project_euler/problem_092/sol1.py)
940+
* Problem 094
941+
* [Sol1](project_euler/problem_094/sol1.py)
940942
* Problem 097
941943
* [Sol1](project_euler/problem_097/sol1.py)
942944
* Problem 099
@@ -1017,6 +1019,8 @@
10171019
* [Sol1](project_euler/problem_587/sol1.py)
10181020
* Problem 686
10191021
* [Sol1](project_euler/problem_686/sol1.py)
1022+
* Problem 800
1023+
* [Sol1](project_euler/problem_800/sol1.py)
10201024

10211025
## Quantum
10221026
* [Bb84](quantum/bb84.py)

‎data_structures/linked_list/doubly_linked_list.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ def insert_at_nth(self, index: int, data):
8181
....
8282
IndexError: list index out of range
8383
"""
84-
if not 0 <= index <= len(self):
84+
length = len(self)
85+
86+
if not 0 <= index <= length:
8587
raise IndexError("list index out of range")
8688
new_node = Node(data)
8789
if self.head is None:
@@ -90,7 +92,7 @@ def insert_at_nth(self, index: int, data):
9092
self.head.previous = new_node
9193
new_node.next = self.head
9294
self.head = new_node
93-
elif index == len(self):
95+
elif index == length:
9496
self.tail.next = new_node
9597
new_node.previous = self.tail
9698
self.tail = new_node
@@ -131,15 +133,17 @@ def delete_at_nth(self, index: int):
131133
....
132134
IndexError: list index out of range
133135
"""
134-
if not 0 <= index <= len(self) - 1:
136+
length = len(self)
137+
138+
if not 0 <= index <= length - 1:
135139
raise IndexError("list index out of range")
136140
delete_node = self.head # default first node
137-
if len(self) == 1:
141+
if length == 1:
138142
self.head = self.tail = None
139143
elif index == 0:
140144
self.head = self.head.next
141145
self.head.previous = None
142-
elif index == len(self) - 1:
146+
elif index == length - 1:
143147
delete_node = self.tail
144148
self.tail = self.tail.previous
145149
self.tail.next = None

‎digital_image_processing/edge_detection/canny.py

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,105 +18,126 @@ def gen_gaussian_kernel(k_size, sigma):
1818
return g
1919

2020

21-
def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255):
22-
image_row, image_col = image.shape[0], image.shape[1]
23-
# gaussian_filter
24-
gaussian_out = img_convolve(image, gen_gaussian_kernel(9, sigma=1.4))
25-
# get the gradient and degree by sobel_filter
26-
sobel_grad, sobel_theta = sobel_filter(gaussian_out)
27-
gradient_direction = np.rad2deg(sobel_theta)
28-
gradient_direction += PI
29-
30-
dst = np.zeros((image_row, image_col))
31-
21+
def suppress_non_maximum(image_shape, gradient_direction, sobel_grad):
3222
"""
3323
Non-maximum suppression. If the edge strength of the current pixel is the largest
3424
compared to the other pixels in the mask with the same direction, the value will be
3525
preserved. Otherwise, the value will be suppressed.
3626
"""
37-
for row in range(1, image_row - 1):
38-
for col in range(1, image_col - 1):
27+
destination = np.zeros(image_shape)
28+
29+
for row in range(1, image_shape[0] - 1):
30+
for col in range(1, image_shape[1] - 1):
3931
direction = gradient_direction[row, col]
4032

4133
if (
42-
0 <= direction < 22.5
34+
0 <= direction < PI / 8
4335
or 15 * PI / 8 <= direction <= 2 * PI
4436
or 7 * PI / 8 <= direction <= 9 * PI / 8
4537
):
4638
w = sobel_grad[row, col - 1]
4739
e = sobel_grad[row, col + 1]
4840
if sobel_grad[row, col] >= w and sobel_grad[row, col] >= e:
49-
dst[row, col] = sobel_grad[row, col]
41+
destination[row, col] = sobel_grad[row, col]
5042

51-
elif (PI / 8 <= direction < 3 * PI / 8) or (
52-
9 * PI / 8 <= direction < 11 * PI / 8
43+
elif (
44+
PI / 8 <= direction < 3 * PI / 8
45+
or 9 * PI / 8 <= direction < 11 * PI / 8
5346
):
5447
sw = sobel_grad[row + 1, col - 1]
5548
ne = sobel_grad[row - 1, col + 1]
5649
if sobel_grad[row, col] >= sw and sobel_grad[row, col] >= ne:
57-
dst[row, col] = sobel_grad[row, col]
50+
destination[row, col] = sobel_grad[row, col]
5851

59-
elif (3 * PI / 8 <= direction < 5 * PI / 8) or (
60-
11 * PI / 8 <= direction < 13 * PI / 8
52+
elif (
53+
3 * PI / 8 <= direction < 5 * PI / 8
54+
or 11 * PI / 8 <= direction < 13 * PI / 8
6155
):
6256
n = sobel_grad[row - 1, col]
6357
s = sobel_grad[row + 1, col]
6458
if sobel_grad[row, col] >= n and sobel_grad[row, col] >= s:
65-
dst[row, col] = sobel_grad[row, col]
59+
destination[row, col] = sobel_grad[row, col]
6660

67-
elif (5 * PI / 8 <= direction < 7 * PI / 8) or (
68-
13 * PI / 8 <= direction < 15 * PI / 8
61+
elif (
62+
5 * PI / 8 <= direction < 7 * PI / 8
63+
or 13 * PI / 8 <= direction < 15 * PI / 8
6964
):
7065
nw = sobel_grad[row - 1, col - 1]
7166
se = sobel_grad[row + 1, col + 1]
7267
if sobel_grad[row, col] >= nw and sobel_grad[row, col] >= se:
73-
dst[row, col] = sobel_grad[row, col]
74-
75-
"""
76-
High-Low threshold detection. If an edge pixel’s gradient value is higher
77-
than the high threshold value, it is marked as a strong edge pixel. If an
78-
edge pixel’s gradient value is smaller than the high threshold value and
79-
larger than the low threshold value, it is marked as a weak edge pixel. If
80-
an edge pixel's value is smaller than the low threshold value, it will be
81-
suppressed.
82-
"""
83-
if dst[row, col] >= threshold_high:
84-
dst[row, col] = strong
85-
elif dst[row, col] <= threshold_low:
86-
dst[row, col] = 0
68+
destination[row, col] = sobel_grad[row, col]
69+
70+
return destination
71+
72+
73+
def detect_high_low_threshold(
74+
image_shape, destination, threshold_low, threshold_high, weak, strong
75+
):
76+
"""
77+
High-Low threshold detection. If an edge pixel’s gradient value is higher
78+
than the high threshold value, it is marked as a strong edge pixel. If an
79+
edge pixel’s gradient value is smaller than the high threshold value and
80+
larger than the low threshold value, it is marked as a weak edge pixel. If
81+
an edge pixel's value is smaller than the low threshold value, it will be
82+
suppressed.
83+
"""
84+
for row in range(1, image_shape[0] - 1):
85+
for col in range(1, image_shape[1] - 1):
86+
if destination[row, col] >= threshold_high:
87+
destination[row, col] = strong
88+
elif destination[row, col] <= threshold_low:
89+
destination[row, col] = 0
8790
else:
88-
dst[row, col] = weak
91+
destination[row, col] = weak
8992

93+
94+
def track_edge(image_shape, destination, weak, strong):
9095
"""
9196
Edge tracking. Usually a weak edge pixel caused from true edges will be connected
9297
to a strong edge pixel while noise responses are unconnected. As long as there is
9398
one strong edge pixel that is involved in its 8-connected neighborhood, that weak
9499
edge point can be identified as one that should be preserved.
95100
"""
96-
for row in range(1, image_row):
97-
for col in range(1, image_col):
98-
if dst[row, col] == weak:
101+
for row in range(1, image_shape[0]):
102+
for col in range(1, image_shape[1]):
103+
if destination[row, col] == weak:
99104
if 255 in (
100-
dst[row, col + 1],
101-
dst[row, col - 1],
102-
dst[row - 1, col],
103-
dst[row + 1, col],
104-
dst[row - 1, col - 1],
105-
dst[row + 1, col - 1],
106-
dst[row - 1, col + 1],
107-
dst[row + 1, col + 1],
105+
destination[row, col + 1],
106+
destination[row, col - 1],
107+
destination[row - 1, col],
108+
destination[row + 1, col],
109+
destination[row - 1, col - 1],
110+
destination[row + 1, col - 1],
111+
destination[row - 1, col + 1],
112+
destination[row + 1, col + 1],
108113
):
109-
dst[row, col] = strong
114+
destination[row, col] = strong
110115
else:
111-
dst[row, col] = 0
116+
destination[row, col] = 0
117+
118+
119+
def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255):
120+
# gaussian_filter
121+
gaussian_out = img_convolve(image, gen_gaussian_kernel(9, sigma=1.4))
122+
# get the gradient and degree by sobel_filter
123+
sobel_grad, sobel_theta = sobel_filter(gaussian_out)
124+
gradient_direction = PI + np.rad2deg(sobel_theta)
125+
126+
destination = suppress_non_maximum(image.shape, gradient_direction, sobel_grad)
127+
128+
detect_high_low_threshold(
129+
image.shape, destination, threshold_low, threshold_high, weak, strong
130+
)
131+
132+
track_edge(image.shape, destination, weak, strong)
112133

113-
return dst
134+
return destination
114135

115136

116137
if __name__ == "__main__":
117138
# read original image in gray mode
118139
lena = cv2.imread(r"../image_data/lena.jpg", 0)
119140
# canny edge detection
120-
canny_dst = canny(lena)
121-
cv2.imshow("canny", canny_dst)
141+
canny_destination = canny(lena)
142+
cv2.imshow("canny", canny_destination)
122143
cv2.waitKey(0)

‎digital_image_processing/morphological_operations/dilation_operation.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
1+
from pathlib import Path
2+
13
import numpy as np
24
from PIL import Image
35

46

5-
def rgb2gray(rgb: np.array) -> np.array:
7+
def rgb_to_gray(rgb: np.ndarray) -> np.ndarray:
68
"""
79
Return gray image from rgb image
8-
>>> rgb2gray(np.array([[[127, 255, 0]]]))
10+
>>> rgb_to_gray(np.array([[[127, 255, 0]]]))
911
array([[187.6453]])
10-
>>> rgb2gray(np.array([[[0, 0, 0]]]))
12+
>>> rgb_to_gray(np.array([[[0, 0, 0]]]))
1113
array([[0.]])
12-
>>> rgb2gray(np.array([[[2, 4, 1]]]))
14+
>>> rgb_to_gray(np.array([[[2, 4, 1]]]))
1315
array([[3.0598]])
14-
>>> rgb2gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]]))
16+
>>> rgb_to_gray(np.array([[[26, 255, 14], [5, 147, 20], [1, 200, 0]]]))
1517
array([[159.0524, 90.0635, 117.6989]])
1618
"""
1719
r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2]
1820
return 0.2989 * r + 0.5870 * g + 0.1140 * b
1921

2022

21-
def gray2binary(gray: np.array) -> np.array:
23+
def gray_to_binary(gray: np.ndarray) -> np.ndarray:
2224
"""
2325
Return binary image from gray image
24-
>>> gray2binary(np.array([[127, 255, 0]]))
26+
>>> gray_to_binary(np.array([[127, 255, 0]]))
2527
array([[False, True, False]])
26-
>>> gray2binary(np.array([[0]]))
28+
>>> gray_to_binary(np.array([[0]]))
2729
array([[False]])
28-
>>> gray2binary(np.array([[26.2409, 4.9315, 1.4729]]))
30+
>>> gray_to_binary(np.array([[26.2409, 4.9315, 1.4729]]))
2931
array([[False, False, False]])
30-
>>> gray2binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]]))
32+
>>> gray_to_binary(np.array([[26, 255, 14], [5, 147, 20], [1, 200, 0]]))
3133
array([[False, True, False],
3234
[False, True, False],
3335
[False, True, False]])
3436
"""
3537
return (gray > 127) & (gray <= 255)
3638

3739

38-
def dilation(image: np.array, kernel: np.array) -> np.array:
40+
def dilation(image: np.ndarray, kernel: np.ndarray) -> np.ndarray:
3941
"""
4042
Return dilated image
4143
>>> dilation(np.array([[True, False, True]]), np.array([[0, 1, 0]]))
@@ -61,14 +63,13 @@ def dilation(image: np.array, kernel: np.array) -> np.array:
6163
return output
6264

6365

64-
# kernel to be applied
65-
structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])
66-
67-
6866
if __name__ == "__main__":
6967
# read original image
70-
image = np.array(Image.open(r"..\image_data\lena.jpg"))
71-
output = dilation(gray2binary(rgb2gray(image)), structuring_element)
68+
lena_path = Path(__file__).resolve().parent / "image_data" / "lena.jpg"
69+
lena = np.array(Image.open(lena_path))
70+
# kernel to be applied
71+
structuring_element = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])
72+
output = dilation(gray_to_binary(rgb_to_gray(lena)), structuring_element)
7273
# Save the output image
7374
pil_img = Image.fromarray(output).convert("RGB")
7475
pil_img.save("result_dilation.png")

‎physics/grahams_law.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"""
2+
Title: Graham's Law of Effusion
3+
4+
Description: Graham's law of effusion states that the rate of effusion of a gas is
5+
inversely proportional to the square root of the molar mass of its particles:
6+
7+
r1/r2 = sqrt(m2/m1)
8+
9+
r1 = Rate of effusion for the first gas.
10+
r2 = Rate of effusion for the second gas.
11+
m1 = Molar mass of the first gas.
12+
m2 = Molar mass of the second gas.
13+
14+
(Description adapted from https://en.wikipedia.org/wiki/Graham%27s_law)
15+
"""
16+
17+
from math import pow, sqrt
18+
19+
20+
def validate(*values: float) -> bool:
21+
"""
22+
Input Parameters:
23+
-----------------
24+
effusion_rate_1: Effustion rate of first gas (m^2/s, mm^2/s, etc.)
25+
effusion_rate_2: Effustion rate of second gas (m^2/s, mm^2/s, etc.)
26+
molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.)
27+
molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.)
28+
29+
Returns:
30+
--------
31+
>>> validate(2.016, 4.002)
32+
True
33+
>>> validate(-2.016, 4.002)
34+
False
35+
>>> validate()
36+
False
37+
"""
38+
result = len(values) > 0 and all(value > 0.0 for value in values)
39+
return result
40+
41+
42+
def effusion_ratio(molar_mass_1: float, molar_mass_2: float) -> float | ValueError:
43+
"""
44+
Input Parameters:
45+
-----------------
46+
molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.)
47+
molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.)
48+
49+
Returns:
50+
--------
51+
>>> effusion_ratio(2.016, 4.002)
52+
1.408943
53+
>>> effusion_ratio(-2.016, 4.002)
54+
ValueError('Input Error: Molar mass values must greater than 0.')
55+
>>> effusion_ratio(2.016)
56+
Traceback (most recent call last):
57+
...
58+
TypeError: effusion_ratio() missing 1 required positional argument: 'molar_mass_2'
59+
"""
60+
return (
61+
round(sqrt(molar_mass_2 / molar_mass_1), 6)
62+
if validate(molar_mass_1, molar_mass_2)
63+
else ValueError("Input Error: Molar mass values must greater than 0.")
64+
)
65+
66+
67+
def first_effusion_rate(
68+
effusion_rate: float, molar_mass_1: float, molar_mass_2: float
69+
) -> float | ValueError:
70+
"""
71+
Input Parameters:
72+
-----------------
73+
effusion_rate: Effustion rate of second gas (m^2/s, mm^2/s, etc.)
74+
molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.)
75+
molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.)
76+
77+
Returns:
78+
--------
79+
>>> first_effusion_rate(1, 2.016, 4.002)
80+
1.408943
81+
>>> first_effusion_rate(-1, 2.016, 4.002)
82+
ValueError('Input Error: Molar mass and effusion rate values must greater than 0.')
83+
>>> first_effusion_rate(1)
84+
Traceback (most recent call last):
85+
...
86+
TypeError: first_effusion_rate() missing 2 required positional arguments: \
87+
'molar_mass_1' and 'molar_mass_2'
88+
>>> first_effusion_rate(1, 2.016)
89+
Traceback (most recent call last):
90+
...
91+
TypeError: first_effusion_rate() missing 1 required positional argument: \
92+
'molar_mass_2'
93+
"""
94+
return (
95+
round(effusion_rate * sqrt(molar_mass_2 / molar_mass_1), 6)
96+
if validate(effusion_rate, molar_mass_1, molar_mass_2)
97+
else ValueError(
98+
"Input Error: Molar mass and effusion rate values must greater than 0."
99+
)
100+
)
101+
102+
103+
def second_effusion_rate(
104+
effusion_rate: float, molar_mass_1: float, molar_mass_2: float
105+
) -> float | ValueError:
106+
"""
107+
Input Parameters:
108+
-----------------
109+
effusion_rate: Effustion rate of second gas (m^2/s, mm^2/s, etc.)
110+
molar_mass_1: Molar mass of the first gas (g/mol, kg/kmol, etc.)
111+
molar_mass_2: Molar mass of the second gas (g/mol, kg/kmol, etc.)
112+
113+
Returns:
114+
--------
115+
>>> second_effusion_rate(1, 2.016, 4.002)
116+
0.709752
117+
>>> second_effusion_rate(-1, 2.016, 4.002)
118+
ValueError('Input Error: Molar mass and effusion rate values must greater than 0.')
119+
>>> second_effusion_rate(1)
120+
Traceback (most recent call last):
121+
...
122+
TypeError: second_effusion_rate() missing 2 required positional arguments: \
123+
'molar_mass_1' and 'molar_mass_2'
124+
>>> second_effusion_rate(1, 2.016)
125+
Traceback (most recent call last):
126+
...
127+
TypeError: second_effusion_rate() missing 1 required positional argument: \
128+
'molar_mass_2'
129+
"""
130+
return (
131+
round(effusion_rate / sqrt(molar_mass_2 / molar_mass_1), 6)
132+
if validate(effusion_rate, molar_mass_1, molar_mass_2)
133+
else ValueError(
134+
"Input Error: Molar mass and effusion rate values must greater than 0."
135+
)
136+
)
137+
138+
139+
def first_molar_mass(
140+
molar_mass: float, effusion_rate_1: float, effusion_rate_2: float
141+
) -> float | ValueError:
142+
"""
143+
Input Parameters:
144+
-----------------
145+
molar_mass: Molar mass of the first gas (g/mol, kg/kmol, etc.)
146+
effusion_rate_1: Effustion rate of first gas (m^2/s, mm^2/s, etc.)
147+
effusion_rate_2: Effustion rate of second gas (m^2/s, mm^2/s, etc.)
148+
149+
Returns:
150+
--------
151+
>>> first_molar_mass(2, 1.408943, 0.709752)
152+
0.507524
153+
>>> first_molar_mass(-1, 2.016, 4.002)
154+
ValueError('Input Error: Molar mass and effusion rate values must greater than 0.')
155+
>>> first_molar_mass(1)
156+
Traceback (most recent call last):
157+
...
158+
TypeError: first_molar_mass() missing 2 required positional arguments: \
159+
'effusion_rate_1' and 'effusion_rate_2'
160+
>>> first_molar_mass(1, 2.016)
161+
Traceback (most recent call last):
162+
...
163+
TypeError: first_molar_mass() missing 1 required positional argument: \
164+
'effusion_rate_2'
165+
"""
166+
return (
167+
round(molar_mass / pow(effusion_rate_1 / effusion_rate_2, 2), 6)
168+
if validate(molar_mass, effusion_rate_1, effusion_rate_2)
169+
else ValueError(
170+
"Input Error: Molar mass and effusion rate values must greater than 0."
171+
)
172+
)
173+
174+
175+
def second_molar_mass(
176+
molar_mass: float, effusion_rate_1: float, effusion_rate_2: float
177+
) -> float | ValueError:
178+
"""
179+
Input Parameters:
180+
-----------------
181+
molar_mass: Molar mass of the first gas (g/mol, kg/kmol, etc.)
182+
effusion_rate_1: Effustion rate of first gas (m^2/s, mm^2/s, etc.)
183+
effusion_rate_2: Effustion rate of second gas (m^2/s, mm^2/s, etc.)
184+
185+
Returns:
186+
--------
187+
>>> second_molar_mass(2, 1.408943, 0.709752)
188+
1.970351
189+
>>> second_molar_mass(-2, 1.408943, 0.709752)
190+
ValueError('Input Error: Molar mass and effusion rate values must greater than 0.')
191+
>>> second_molar_mass(1)
192+
Traceback (most recent call last):
193+
...
194+
TypeError: second_molar_mass() missing 2 required positional arguments: \
195+
'effusion_rate_1' and 'effusion_rate_2'
196+
>>> second_molar_mass(1, 2.016)
197+
Traceback (most recent call last):
198+
...
199+
TypeError: second_molar_mass() missing 1 required positional argument: \
200+
'effusion_rate_2'
201+
"""
202+
return (
203+
round(pow(effusion_rate_1 / effusion_rate_2, 2) / molar_mass, 6)
204+
if validate(molar_mass, effusion_rate_1, effusion_rate_2)
205+
else ValueError(
206+
"Input Error: Molar mass and effusion rate values must greater than 0."
207+
)
208+
)

‎project_euler/problem_094/__init__.py

Whitespace-only changes.

‎project_euler/problem_094/sol1.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Project Euler Problem 94: https://projecteuler.net/problem=94
3+
4+
It is easily proved that no equilateral triangle exists with integral length sides and
5+
integral area. However, the almost equilateral triangle 5-5-6 has an area of 12 square
6+
units.
7+
8+
We shall define an almost equilateral triangle to be a triangle for which two sides are
9+
equal and the third differs by no more than one unit.
10+
11+
Find the sum of the perimeters of all almost equilateral triangles with integral side
12+
lengths and area and whose perimeters do not exceed one billion (1,000,000,000).
13+
"""
14+
15+
16+
def solution(max_perimeter: int = 10**9) -> int:
17+
"""
18+
Returns the sum of the perimeters of all almost equilateral triangles with integral
19+
side lengths and area and whose perimeters do not exceed max_perimeter
20+
21+
>>> solution(20)
22+
16
23+
"""
24+
25+
prev_value = 1
26+
value = 2
27+
28+
perimeters_sum = 0
29+
i = 0
30+
perimeter = 0
31+
while perimeter <= max_perimeter:
32+
perimeters_sum += perimeter
33+
34+
prev_value += 2 * value
35+
value += prev_value
36+
37+
perimeter = 2 * value + 2 if i % 2 == 0 else 2 * value - 2
38+
i += 1
39+
40+
return perimeters_sum
41+
42+
43+
if __name__ == "__main__":
44+
print(f"{solution() = }")

‎project_euler/problem_800/__init__.py

Whitespace-only changes.

‎project_euler/problem_800/sol1.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Project Euler Problem 800: https://projecteuler.net/problem=800
3+
4+
An integer of the form p^q q^p with prime numbers p != q is called a hybrid-integer.
5+
For example, 800 = 2^5 5^2 is a hybrid-integer.
6+
7+
We define C(n) to be the number of hybrid-integers less than or equal to n.
8+
You are given C(800) = 2 and C(800^800) = 10790
9+
10+
Find C(800800^800800)
11+
"""
12+
13+
from math import isqrt, log2
14+
15+
16+
def calculate_prime_numbers(max_number: int) -> list[int]:
17+
"""
18+
Returns prime numbers below max_number
19+
20+
>>> calculate_prime_numbers(10)
21+
[2, 3, 5, 7]
22+
"""
23+
24+
is_prime = [True] * max_number
25+
for i in range(2, isqrt(max_number - 1) + 1):
26+
if is_prime[i]:
27+
for j in range(i**2, max_number, i):
28+
is_prime[j] = False
29+
30+
return [i for i in range(2, max_number) if is_prime[i]]
31+
32+
33+
def solution(base: int = 800800, degree: int = 800800) -> int:
34+
"""
35+
Returns the number of hybrid-integers less than or equal to base^degree
36+
37+
>>> solution(800, 1)
38+
2
39+
40+
>>> solution(800, 800)
41+
10790
42+
"""
43+
44+
upper_bound = degree * log2(base)
45+
max_prime = int(upper_bound)
46+
prime_numbers = calculate_prime_numbers(max_prime)
47+
48+
hybrid_integers_count = 0
49+
left = 0
50+
right = len(prime_numbers) - 1
51+
while left < right:
52+
while (
53+
prime_numbers[right] * log2(prime_numbers[left])
54+
+ prime_numbers[left] * log2(prime_numbers[right])
55+
> upper_bound
56+
):
57+
right -= 1
58+
hybrid_integers_count += right - left
59+
left += 1
60+
61+
return hybrid_integers_count
62+
63+
64+
if __name__ == "__main__":
65+
print(f"{solution() = }")

0 commit comments

Comments
 (0)
Please sign in to comment.