Як перевірити, чи число є простим у Python

У цьому посібнику ви дізнаєтеся, як створити програму на Python для визначення, чи є задане число простим.

Можливо, ви стикалися з математичними задачами на перевірку простоти чисел під час проходження тестів з програмування. Зараз ви навчитеся розробляти ефективний спосіб вирішення цієї проблеми.

У цьому навчальному матеріалі ви:

  • освіжите свої знання про прості числа,
  • напишете код на Python, який перевіряє, чи є число простим, і
  • оптимізуєте його для досягнення алгоритмічної складності O(√n).

Розпочнімо наше дослідження.

Що ж таке просте число?

Спершу розглянемо визначення простих чисел.

У теорії чисел, натуральне число n вважається простим, якщо воно має рівно два дільники: 1 та саме число (n). Згадаємо зі шкільної програми: число i називається дільником числа n, якщо n ділиться на i без залишку. ✅

У таблиці нижче наведено кілька чисел, їхні дільники та зазначено, чи є вони простими.

n Дільники Просте число?
1 1 Ні
2 1, 2 Так
3 1, 3 Так
4 1, 2, 4 Ні
7 1, 7 Так
15 1, 3, 5, 15 Ні

З таблиці вище ми можемо зробити висновки:

  • 2 – найменше просте число.
  • 1 є дільником будь-якого числа.
  • Кожне число n є дільником самого себе.

Таким чином, 1 та n є тривіальними дільниками будь-якого числа n. Просте число не повинно мати жодних інших дільників, крім цих двох.

Це означає, що при перевірці чисел від 2 до n – 1, не має бути жодного нетривіального дільника, який би ділив n без залишку.

O(n) Алгоритм для перевірки простоти числа в Python

У цьому розділі ми формалізуємо описаний вище підхід у вигляді функції Python.

Ми можемо перебрати всі числа від 2 до n – 1, використовуючи об’єкт `range()` у Python.

У Python функція `range(початок, кінець, крок)` повертає об’єкт діапазону. Далі, можна ітерувати по цьому об’єкту, щоб отримати послідовність чисел від початку до кінця-1 зі заданим кроком.

Оскільки нам потрібна послідовність цілих чисел від 2 до n-1, ми використаємо `range(2, n)` у циклі `for`.

Наш алгоритм буде таким:

  • Якщо ми знайдемо число, яке ділить n без залишку, то n не є простим числом.
  • Якщо ми перебрали всі числа від 2 до n – 1 і не знайшли жодного дільника, то число n є простим.

Функція Python для перевірки простоти числа

Використовуючи вищезазначене, ми можемо визначити функцію `is_prime()`:

    def is_prime(n):
      for i in range(2,n):
        if (n%i) == 0:
          return False
      return True
  

Тепер розглянемо визначення цієї функції.

  • Функція `is_prime()` приймає натуральне число n як аргумент.
  • Якщо в заданому діапазоні (2, n-1) знайдено дільник, то функція повертає `False`, оскільки число не є простим.
  • Якщо ж цикл пройде повністю без знаходження дільника, функція поверне `True`.

Викличемо функцію з різними аргументами та перевіримо результати.

    is_prime(2)
    # True

    is_prime(8)
    # False

    is_prime(9)
    # False

    is_prime(11)
    # True
  

Як бачимо, 2 та 11 є простими числами (функція повертає `True`), а 8 і 9 – ні (функція повертає `False`).

Оптимізація функції `is_prime()` на Python

Можлива невелика оптимізація. Зверніть увагу на перелік дільників у таблиці.

Число Дільники
6 1, 2, 3, 6
10 1, 2, 5, 10
18 1, 2, 3, 6, 9, 18

Єдиний дільник n, який є більшим за n/2 – це саме n.

Отже, немає потреби перебирати числа до n – 1. Достатньо перевірити лише числа до n/2.

Якщо до n/2 не знайдено нетривіального дільника, то його не буде і після n/2.

Змінимо функцію `is_prime()`, щоб перевіряти дільники в діапазоні (2, n/2):

    def is_prime(n):
      for i in range(2,int(n/2)):
        if (n%i) == 0:
          return False
      return True
  

Перевіримо результат, викликавши функцію:

    is_prime(9)
    # False

    is_prime(11)
    # True
  

Хоча здається, що ми провели оптимізацію, цей алгоритм не є ефективнішим з точки зору складності виконання. Обидва алгоритми мають O(n) часову складність: вона пропорційна до значення n, тобто лінійна.

Чи можна досягнути кращого результату? Так, це можливо!

▶️ Перейдіть до наступного розділу, щоб дізнатися, як покращити часову складність перевірки простих чисел.

O(√n) Алгоритм перевірки простоти числа на Python

Дільники числа завжди зустрічаються парами.

Якщо a є дільником числа n, то існує такий дільник b, що a * b = n.

Розглянемо приклад.

У таблиці нижче наведено дільники числа 18, які зустрічаються парами. Ви можете перевірити це для інших чисел.

Зауважте, що √18 ≈ 4.24.

У парах дільників (a, b), якщо a < 4.24, то b > 4.24 – наприклад, (2, 9) та (3, 6).

Дільники числа 18 у вигляді пар.

Якщо n є повним квадратом, то a = b = √n є одним із дільників.

▶️ Подивіться на дільники 36 у таблиці. Оскільки 36 є повним квадратом, a = b = 6 є одним із дільників. Для інших пар дільників (a, b) справедливо a < 6 та b > 6.

Дільники числа 36 у вигляді пар.

Підсумовуючи, маємо:

  • Будь-яке число n можна записати як n = a * b.
  • Якщо n є повним квадратом, то a = b = √n.
  • Якщо a < b, то a < √n та b > √n.
  • Інакше, якщо a > b, то a > √n та b < √n.

Отже, замість перебору чисел до n/2, можна обмежитися перевіркою до √n. Це значно ефективніше.

Як змінити функцію `is_prime()` на O(√n)?

Оптимізуємо функцію перевірки простоти числа в Python.

      import math
      def is_prime(n):
        for i in range(2,int(math.sqrt(n))+1):
          if (n%i) == 0:
            return False
        return True
    

Розглянемо визначення функції:

  • Імпортуємо вбудований математичний модуль Python і використовуємо функцію `math.sqrt()` для обчислення квадратного кореня.
  • Оскільки n може не бути ідеальним квадратом, перетворимо корінь на ціле число за допомогою `int(var)`.
  • Щоб переконатися, що ми справді перевіряємо √n, ми додаємо одиницю, оскільки функція `range()` виключає кінцеву точку.

Перевіримо, чи правильно працює наша функція `is_prime()`:

      is_prime(8)
      # False

      is_prime(15)
      # False

      is_prime(23)
      # True
    

У наступному розділі ми створимо графіки, щоб наочно порівняти O(n) та O(√n).

Візуальне порівняння O(n) та O(√n)

▶️ Запустіть наведений нижче код у середовищі Jupyter Notebook:

      import numpy as np
      import seaborn as sns
      import pandas as pd


      n = 20

      x = np.arange(n)
      y1 = np.sqrt(x)
      y2 = x
      df = pd.DataFrame({"O(√n)":y1,"O(n)":y2})
      sns.set_theme()
      sns.lineplot(data = df)
    

Код створює графіки для n та √n для заданого діапазону значень.

  • Функція NumPy `arange()` створює масив чисел.
  • Зібрані значення n та √n (до 20, не включаючи) в об’єкт pandas DataFrame.
  • Для побудови графіка використовується функція `seaborn.lineplot()`.

З графіка видно, що √n значно менше за n.

Збільшимо діапазон до 2000 і повторимо ці кроки.

      import numpy as np
      import seaborn as sns
      import pandas as pd


      n = 2000

      x = np.arange(n)
      y1 = np.sqrt(x)
      y2 = x
      df = pd.DataFrame({"O(√n)":y1,"O(n)":y2})
      sns.set_theme()
      sns.lineplot(data = df)
    

З графіка видно, що алгоритм O(√n) значно швидший для перевірки великих чисел на простоту.

Наприклад, 2377 є простим числом – перевірте це! 😀

Алгоритм O(n) потребує близько 2000 кроків, тоді як O(√n) лише 49. ✅

Висновок

⏳ Підсумуємо основні моменти:

  • Для перевірки, чи є число простим, наївний метод передбачає перебір всіх чисел в діапазоні (2, n-1). Якщо не знайдено дільника, то n є простим.
  • Оскільки єдиний дільник n, більший за n/2, це n, можна перевіряти лише до n/2.
  • Обидва підходи мають часову складність O(n).
  • Оскільки дільники зустрічаються парами, можна перебирати лише до √n. Цей алгоритм значно швидший, особливо при перевірці великих чисел.

Сподіваюсь, ви зрозуміли, як перевірити простоту числа в Python. Наступним кроком можете ознайомитись з нашим посібником про операції з рядками в Python, де ви навчитеся перевіряти рядки на паліндром, анаграми тощо.

До зустрічі в наступних посібниках! Бажаємо успішного кодування! 👩🏽‍💻