defmodule Primacy do
@moduledoc """
Prime number handling of various sorts
"""
@doc """
Prime check.
## Examples
iex> Primacy.is_prime?(61)
true
iex> Primacy.is_prime?(62)
false
"""
def is_prime?(n) when n < 2, do: false
def is_prime?(2), do: true
def is_prime?(n) when rem(n, 2) == 0, do: false
# Trial division should be good enough for our purposes
def is_prime?(n), do: is_prime?(n, 3)
defp is_prime?(n, k) when n < k * k, do: true
defp is_prime?(n, k) when rem(n, k) == 0, do: false
defp is_prime?(n, k), do: is_prime?(n, k + 2)
@doc """
Generate a list of primes near a given number
Options:
- `count`: how long a list to produce? (default: 1)
- `dir `:below`, `:above`, `:around` (default: `:around`)
## Examples
iex> Primacy.primes_near(600)
[601]
iex> Primacy.primes_near(600, count: 10)
[571, 577, 587, 593, 599, 601, 607, 613, 617, 619]
iex> Primacy.primes_near(600, count: 10, dir: :below)
[541, 547, 557, 563, 569, 571, 577, 587, 593, 599]
"""
def primes_near(n, opts \\ []) do
{c, d} = parse_options(opts)
{a, i} =
case is_prime?(n) do
true -> {[n], c - 1}
false -> {[], c}
end
down = fn c -> gather_primes(n - 1, c, -1, []) end
up = fn c -> gather_primes(n + 1, c, 1, []) end
case d do
:below ->
down.(i) ++ a
:above ->
a ++ up.(i)
:around ->
half = div(i, 2)
down.(half) ++ a ++ up.(half + rem(i, 2))
end
end
defp gather_primes(_, 0, 1, acc), do: Enum.reverse(acc)
defp gather_primes(_, 0, -1, acc), do: acc
defp gather_primes(n, c, i, acc) do
case is_prime?(n) do
false -> gather_primes(n + i, c, i, acc)
true -> gather_primes(n + i, c - 1, i, [n | acc])
end
end
defp parse_options(o) do
{Keyword.get(o, :count, 1), Keyword.get(o, :dir, :around)}
end
end