Have you ever thought about how essential prime numbers are in our digital world? They’re the backbone of encryption, securing everything from passwords to GPG keys. If someone ever cracked the pattern behind prime numbers, modern security would collapse!
While I was watching a series called Prime Target, which highlights the unpredictability of primes, I had a thought: How would I generate prime numbers efficiently in Elixir? So let’s dive into the fascinating world of prime generation!
Generating Prime Numbers in Elixir
Elixir gives us multiple ways to generate prime numbers. From a basic approach to more efficient algorithms, we’ll explore different techniques:
- Naïve Primality Check – Simple but slow.
- Sieve of Eratosthenes – Fast for generating many primes.
- Streams & Concurrency – Optimized for performance.
Let’s break it down!
1. The Naïve Primality Test
The simplest way to check if a number is prime is by dividing it by numbers up to its square root.
defmodule PrimeChecker do
def is_prime?(n) when n < 2, do: false
def is_prime?(2), do: true
def is_prime?(n) do
2..:math.sqrt(n) |> Enum.all?(fn i -> rem(n, i) != 0 end)
end
end
IO.inspect PrimeChecker.is_prime?(11) # true
✅ Pros: Easy to implement. ❌ Cons: Slow for large numbers.
2. The Sieve of Eratosthenes
This method efficiently finds all prime numbers up to a limit by eliminating multiples of each prime found.
defmodule PrimeGenerator do
def sieve(limit) when limit < 2, do: []
def sieve(limit) do
2..limit |> Enum.to_list() |> remove_multiples()
end
defp remove_multiples([]), do: []
defp remove_multiples([head | tail]) do
[head | remove_multiples(Enum.filter(tail, &rem(&1, head) != 0))]
end
end
IO.inspect PrimeGenerator.sieve(50)
✅ Pros: Much faster than the naïve method. ❌ Cons: Uses more memory.
3. Generating Infinite Primes with Streams
Elixir’s Stream module lets us lazily compute primes, generating as many as needed without holding them all in memory.
defmodule PrimeStream do
def stream_primes do
Stream.iterate(2, &next_prime/1)
end
defp next_prime(n) do
next = n + 1
if PrimeChecker.is_prime?(next), do: next, else: next_prime(next)
end
end
IO.inspect PrimeStream.stream_primes() |> Enum.take(10)
✅ Pros: Saves memory, generates infinite primes. ❌ Cons: Still sequential, needs optimization.
4. Parallel Prime Computation with Task.async_stream
We can speed things up using parallel tasks to check primes concurrently.
defmodule ParallelPrimes do
def generate(n) do
Task.async_stream(2..100_000, &PrimeChecker.is_prime?/1, max_concurrency: 4)
|> Stream.filter(fn {:ok, true} -> true; _ -> false end)
|> Enum.take(n)
end
end
IO.inspect ParallelPrimes.generate(10)
✅ Pros: Uses multiple CPU cores.
❌ Cons: Inefficient for small n
.
5. Ultimate Optimization: ETS + Flow
By combining ETS (Erlang Term Storage) for caching and Flow for parallel execution, we get a high-performance solution.
defmodule PrimeFlowETS do
require Flow
@table :prime_cache
def start_link do
:ets.new(@table, [:set, :public, :named_table])
:ets.insert(@table, {2, true})
{:ok, self()}
end
def get_primes(n) do
primes = :ets.tab2list(@table) |> Enum.map(fn {p, _} -> p end)
if length(primes) >= n, do: Enum.take(primes, n), else: find_more_primes(n)
end
defp find_more_primes(n) do
2..100_000
|> Flow.from_enumerable()
|> Flow.partition(stages: System.schedulers_online())
|> Flow.filter(&is_prime?/1)
|> Flow.each(fn prime -> :ets.insert(@table, {prime, true}) end)
|> Enum.take(n)
end
defp is_prime?(n) do
primes = :ets.tab2list(@table) |> Enum.map(fn {p, _} -> p end)
Enum.all?(primes, fn p -> rem(n, p) != 0 end)
end
end
PrimeFlowETS.start_link()
IO.inspect PrimeFlowETS.get_primes(20)
✅ Pros: Super-fast, multi-core, avoids recomputation. ❌ Cons: More complex setup.
Conclusion
Method | Pros | Cons |
---|---|---|
Naïve Check | Simple to implement | Slow for large numbers |
Sieve of Eratosthenes | Fast for many primes | Uses more memory |
Streams | Generates infinite primes | Still sequential |
Parallel Tasks | Uses multiple CPU cores | Less efficient for small n |
ETS + Flow | Ultra-fast, avoids recalculations | More complex setup |
What’s the Best Approach?
- If you need a quick solution, use the Sieve of Eratosthenes.
- For infinite primes, go with Streams.
- For maximum performance, combine ETS + Flow.
🚀 What’s next? Try modifying the GenServer to store primes or explore distributed computing for even faster prime generation!
Do you have a preferred approach for prime number generation? Let’s discuss in the comments!
Share on
Twitter Facebook LinkedInHave comments or want to discuss this topic?
Send an email to ~bounga/public-inbox@lists.sr.ht