Алгоритмы поиска натуральных чисел в заданном диапазоне на Python
Основные подходы к поиску натуральных чисел в заданном диапазоне
Натуральные числа - это целые положительные числа (1, 2, 3, ...). Задача поиска всех таких чисел в заданном диапазоне [a, b] обычно включает дополнительное условие: нужно отобрать только те числа, которые обладают определённым свойством (простота, совершенство, палиндромность и т.д.). Ниже представлено наиболее эффективное решение для типовой задачи - поиск простых чисел - и несколько альтернативных вариантов.
Наиболее эффективное решение: решето Эратосфена с оптимизацией
Для поиска всех простых чисел в диапазоне от 1 до b (где b не слишком велико) лучше всего подходит решето Эратосфена. Оно работает за O(n log log n) и использует O(n) памяти. Ниже приведена реализация, которая сразу отсеивает чётные числа и использует массив булевых значений.
def sieve_eratosthenes(limit):
"""Возвращает список простых чисел от 2 до limit."""
if limit < 2:
return []
sieve = [True] * (limit + 1)
sieve[0] = sieve[1] = False
# проходим только до sqrt(limit)
import math
for i in range(2, int(math.isqrt(limit)) + 1):
if sieve[i]:
step = i if i == 2 else 2 * i # для нечётных шаг 2*i
start = i * i
sieve[start:limit+1:step] = [False] * ((limit - start)//step + 1)
return [i for i, is_prime in enumerate(sieve) if is_prime]
# Пример использования: найти простые числа в диапазоне от 1 до 100
primes = sieve_eratosthenes(100)
print(primes)найти натуральные числа python (поиск натуральных чисел в заданном диапазоне в python)
[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]
Пояснение:
- Создаётся список
sieveдлиной limit+1, все элементы True. - 0 и 1 помечаются как составные (False).
- Для каждого i от 2 до sqrt(limit) проверяется, является ли i простым. Если да, то все кратные i, начиная с i², помечаются False.
- Для чётных чисел (кроме 2) шаг увеличен до 2*i, что уменьшает количество итераций.
- В конце формируется список чисел, для которых sieve остался True.
Типичные ошибки и их решение:
- Проблема: Включение 0 или 1 в результат. Решение: начать цикл просеивания с 2 и явно обнулить sieve[0] и sieve[1].
- Проблема: Неправильная граница - если limit меньше 2, функция вернёт пустой список, что может быть неочевидно. Решение: добавить проверку в начале.
- Проблема: Для больших limit (например, 10⁷) память может быть проблемой. Решение: использовать сегментированное решето или библиотеку array из модуля array.
Как найти все натуральные числа, удовлетворяющие произвольному условию, с помощью list comprehension?
def is_condition(x):
# пример: число, сумма цифр которого равна 10
return sum(int(d) for d in str(x)) == 10
start, end = 1, 200
result = [num for num in range(start, end+1) if is_condition(num)]
print(result[:20]) # первые 20[19, 28, 37, 46, 55, 64, 73, 82, 91, 109, 118, 127, 136, 145, 154, 163, 172, 181, 190, 199]
Цель:
Простота и читаемость. Подходит для небольших диапазонов (до миллионов) и простых условий.
Ошибка:
Если условие требует сложных вычислений, list comprehension без предварительной фильтрации может быть медленным. Решение: разбить на этапы (сначала отсеять очевидные неподходящие числа, затем применить условие).
Как организовать поиск с помощью функции filter и lambda?
condition = lambda x: x % 2 == 0 # чётные числа
filtered = list(filter(condition, range(1, 101)))
print(filtered[:10])[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Цель:
Более функциональный стиль, подходит, когда условие уже определено как функция.
Проблема:
filter возвращает итератор, который можно преобразовать в список, но при больших объёмах может потребляться много памяти. Решение: обрабатывать итератор лениво, например, через цикл for.
Как эффективно перебирать числа с помощью генератора, чтобы не хранить все результаты?
def find_numbers_gen(start, end, condition):
for num in range(start, end+1):
if condition(num):
yield num
# Условие: число является квадратом другого целого числа
import math
cond = lambda x: math.isqrt(x)**2 == x
gen = find_numbers_gen(1, 100, cond)
for n in gen:
print(n, end=' ')1 4 9 16 25 36 49 64 81 100
Цель:
Экономия памяти при работе с очень большими диапазонами, когда нужна последующая обработка каждого подходящего числа по одному.
Ошибка:
Забыть, что генератор можно использовать только один раз. Решение: преобразовать в список, если требуется многократный доступ, или создать новый генератор.
Как ускорить перебор с помощью NumPy для диапазонов с миллионами чисел?
import numpy as np
def find_numbers_numpy(start, end, condition_func):
arr = np.arange(start, end+1)
# condition_func должна работать с массивами (векторизована)
mask = condition_func(arr)
return arr[mask]
# Условие: число делится на 7 и 3 одновременно
vectorized_cond = np.vectorize(lambda x: (x % 7 == 0) & (x % 3 == 0))
result = find_numbers_numpy(1, 1000, vectorized_cond)
print(result[:10])[ 21 42 63 84 105 126 147 168 189 210]
Цель:
Максимальная скорость за счёт векторизации. Подходит для численных расчётов с большими объёмами данных.
Проблема:
np.vectorize часто не даёт реального ускорения, так как это просто цикл на Python. Лучше использовать готовые векторизованные функции (например, (arr%7==0)&(arr%3==0)).
Как выполнить параллельный перебор с помощью multiprocessing для многоядерного процессора?
import multiprocessing as mp
def worker(chunk):
start, end, condition = chunk
return [i for i in range(start, end+1) if condition(i)]
if __name__ == '__main__':
cond = lambda x: x == sum(int(d)**len(str(x)) for d in str(x)) # числа Армстронга
interval = (1, 10000)
num_workers = 4
step = (interval[1] - interval[0]) // num_workers
chunks = [(interval[0] + i*step, interval[0] + (i+1)*step - 1, cond) for i in range(num_workers)]
with mp.Pool(processes=num_workers) as pool:
results = pool.map(worker, chunks)
all_armstrong = [num for sublist in results for num in sublist]
print(sorted(all_armstrong))[1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474]
Цель:
Ускорение поиска на многоядерных системах. Полезно для очень больших диапазонов и сложных условий.
Ошибка:
Передача больших объектов (например, списков) между процессами может замедлить работу. Решение: использовать генераторы или предварительно разбить данные на куски.
Расширенные примеры поиска натуральных чисел с необычными условиями
В этом разделе приведены более сложные и редко встречающиеся задачи, которые могут быть решены с помощью Python. Каждый пример сопровождается полным кодом и выводом.
Пример 1: Поиск совершенных чисел (числа, равные сумме своих собственных делителей)
def perfect_numbers(limit):
perfects = []
for n in range(2, limit+1):
# сумма делителей, исключая само число
divisors_sum = 0
for i in range(1, int(n**0.5) + 1):
if n % i == 0:
divisors_sum += i
if i != 1 and i != n // i:
divisors_sum += n // i
if divisors_sum == n:
perfects.append(n)
return perfects
print(perfect_numbers(10000))[6, 28, 496, 8128]
Пояснение:
Для каждого числа n ищем делители до sqrt(n). Если делитель i найден, добавляем i и, если он не равен квадратному корню, добавляем n//i. Сравниваем сумму с n. Важно начинать с 2, так как 1 не считается совершенным (сумма собственных делителей 1 равна 0).
Пример 2: Поиск дружественных чисел (два числа, сумма делителей каждого равна другому)
def divisor_sum(n):
s = 0
for i in range(1, int(n**0.5)+1):
if n % i == 0:
s += i
if i != 1 and i != n // i:
s += n // i
return s
def amicable_pairs(limit):
pairs = []
for a in range(2, limit+1):
b = divisor_sum(a)
if b > a and b <= limit and divisor_sum(b) == a:
pairs.append((a, b))
return pairs
print(amicable_pairs(10000))[(220, 284), (1184, 1210), (2620, 2924), (5020, 5564), (6232, 6368)]
Пояснение:
Функция divisor_sum вычисляет сумму собственных делителей. Для каждого a вычисляем b = divisor_sum(a). Если b > a (чтобы избежать дублирования), b не превышает limit и сумма делителей b равна a, то пара дружественная.
Пример 3: Поиск чисел Фибоначчи в заданном диапазоне с помощью генератора
def fibonacci_in_range(start, end):
a, b = 1, 1
while a <= end:
if a >= start:
yield a
a, b = b, a + b
result = list(fibonacci_in_range(1, 1000))
print(result)[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
Пояснение:
Классическая итерация Фибоначчи с условием остановки. Генератор экономит память, так как числа не хранятся все сразу. Обратите внимание: первое число 1 встречается дважды (по определению последовательности).
Пример 4: Поиск чисел, являющихся палиндромами сразу в десятичной и двоичной системах
def is_palindrome(num, base):
s = ''
temp = num
while temp > 0:
s = str(temp % base) + s
temp //= base
return s == s[::-1]
def double_palindromes(limit):
res = []
for n in range(1, limit+1):
if is_palindrome(n, 10) and is_palindrome(n, 2):
res.append(n)
return res
print(double_palindromes(500))[1, 3, 5, 7, 9, 33, 99, 313, 585]
Пояснение:
Функция is_palindrome преобразует число в строку в указанной системе счисления и проверяет, является ли строка палиндромом. Затем перебираем числа и проверяем оба условия.
Пример 5: Поиск чисел Харшад (делятся на сумму своих цифр) с кэшированием суммы цифр
from functools import lru_cache
@lru_cache(maxsize=None)
def digit_sum(x):
return sum(int(d) for d in str(x))
def harshad_numbers(limit):
return [n for n in range(1, limit+1) if n % digit_sum(n) == 0]
print(harshad_numbers(200))[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 18, 20, 21, 24, 27, 30, 36, 40, 42, 45, 48, 50, 54, 60, 63, 70, 72, 80, 81, 84, 90, 100, 102, 108, 110, 111, 112, 114, 117, 120, 126, 132, 133, 135, 140, 144, 150, 152, 153, 156, 162, 171, 180, 190, 192, 195, 198, 200]Пояснение:
Декоратор
@lru_cacheзапоминает результаты функцииdigit_sum, что ускоряет повторные вызовы (полезно, если одинаковые числа встречаются часто, но в данном примере каждое число проверяется один раз). В более сложных задачах кэш может дать выигрыш.Пример 6: Использование рекурсии для поиска чисел по условию (например, числа, сумма цифр которых равна заданному числу K)
Примерdef find_by_digit_sum(current, remaining_digits, target_sum, results): if remaining_digits == 0: if current > 0 and sum(int(d) for d in str(current)) == target_sum: results.append(current) return # последняя цифра может быть от 1 до 9, остальные от 0 start = 1 if current == 0 else 0 for d in range(start, 10): find_by_digit_sum(current*10 + d, remaining_digits-1, target_sum, results) # найти все трёхзначные числа, сумма цифр которых равна 15 res = [] find_by_digit_sum(0, 3, 15, res) res.sort() print(res)[159, 168, 177, 186, 195, 249, 258, 267, 276, 285, 294, 339, 348, 357, 366, 375, 384, 393, 429, 438, 447, 456, 465, 474, 483, 492, 519, 528, 537, 546, 555, 564, 573, 582, 591, 609, 618, 627, 636, 645, 654, 663, 672, 681, 690, 708, 717, 726, 735, 744, 753, 762, 771, 780, 807, 816, 825, 834, 843, 852, 861, 870, 906, 915, 924, 933, 942, 951, 960]Пояснение:
Рекурсивно строим числа, добавляя цифры. Параметр
currentхранит частично сформированное число,remaining_digits- сколько цифр ещё добавить. Для первой цифры диапазон от 1 до 9, для остальных от 0 до 9. Когда число построено (remaining_digits == 0), проверяем сумму цифр. Такой подход позволяет избежать перебора всех чисел от 100 до 999, а сразу генерирует подходящие по разрядности.