Алгоритмы поиска натуральных чисел в заданном диапазоне на 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, а сразу генерирует подходящие по разрядности.

Поиск натуральных чисел в заданном диапазоне в Python - comments

En
найти натуральные числа python (python)