lib/sieve_of_eratosthenes.ex

defmodule SieveOfEratosthenes do
  @moduledoc """
  Documentation for `SieveOfEratosthenes`.
    Implementation of sieve of eratosthenes algorithm to calculate all the prime numbers 
    until number given used as limit, using tail recursive optimization and async functions
  """

  @doc """
    Calculate all the primes until given `input` used as limit
  """
  def calculate_primes(input) do
    chunk_size = get_chunk_size(input)

    chunked_list = get_chunked_list(input, chunk_size)

    primes = recursive_primes(hd(chunked_list) , [])

    another_primes = get_non_multiples(tl(chunked_list), primes)

    primes ++ another_primes
  end

  @doc """
    Generate a list between two and `input` number
    And chunk that list by the `chunk_size`

  ## Examples

      iex> SieveOfEratosthenes.get_chunked_list(10, 2)
      [[2, 3], [4, 5], [6, 7], [8, 9], [10]]
  """
  def get_chunked_list(input, chunk_size) do
    2..input
    |> Enum.to_list
    |> Enum.chunk_every(chunk_size)
  end

  @doc """
    Get the size of the chunk using the square root from `input`
    this number are used to limit the prime calculation using the sieve of eratosthenes algorithm

  ## Examples

      iex> SieveOfEratosthenes.get_chunk_size(1_000)
      32
  """
  def get_chunk_size(input) do
    :math.sqrt(input) 
    |> Float.ceil(0) 
    |> trunc
  end

  @doc """
    filter all non-multiple `numbers` of the given `primes`

  ## Examples

      iex> SieveOfEratosthenes.get_non_multiples([2..100], [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]
  """
  def get_non_multiples(numbers, primes) do
    for l <- numbers do
      Task.async(fn -> remove_multiples(primes, l) end)
    end
    |> Task.yield_many(100_000)
    |> Enum.map(fn {t, res} -> elem(res, 1) || Task.shutdown(t, :brutal_kill) end)
    |> Enum.concat
  end

  @doc """
    Calculate all primes of list given using the sieve of eratosthenes algorithm

  ## Examples

      iex> SieveOfEratosthenes.recursive_primes([2,3,4,5,6,7,8,9,10], [])
      [2, 3, 5, 7]
  """
  def recursive_primes([head | tail], primes) do
    recursive_primes(Enum.filter(tail, fn x -> rem(x, head) != 0 end), primes ++ [head])
  end

  def recursive_primes([], list_primes), do: list_primes

  @doc """
    remove all the multiples numbers from given number list using list of prime numbers

  ## Examples

      iex> l = 10..100 |> Enum.to_list
      iex> SieveOfEratosthenes.remove_multiples([2,3,5,7,11], l)
      [13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
  """
  def remove_multiples([head | tail], number_list) do
    remove_multiples(tail, Enum.filter(number_list, fn x -> rem(x, head) != 0 end))
  end

  def remove_multiples([], number_list), do: number_list

end