I study Elixir as my first functional style language. As the first simple project to familiarize myself with the environment and syntax, I decided to create a simple program that calculates the main factors for the number provided on the command line. This is my first solution:
defmodule Prime do defp is_factor?(number, divisor) do cond do rem(number, divisor) == 0 -> divisor true -> nil end end defp not_nil?(thing) do !is_nil(thing) end def factors(number) when number == 1 do [] end def factors(number) do 1..div(number, 2) |> Enum.map(&(is_factor?(number, &1))) |> Enum.filter(¬_nil?/1) end def is_prime?(number) when number == 1 do true end def is_prime?(number) do factors(number) == [1] end def prime_factors(number) do factors(number) |> Enum.filter(&is_prime?/1) end end input = hd(System.argv) number = String.strip(input) |> String.to_integer IO.puts "Prime factors of #{number} are #{inspect Prime.prime_factors(number)}"
It works, but it works quite slowly. On my laptop, the startup time is about 11 seconds to calculate the main odds of 50,000,000.
As I read more, it seems like this original solution is not very similar to Elixir. So I redid the code:
defmodule PrimeFactors do def of(n) do _factors(n, div(n, 2)) end defp _factors(_n, 1) do [1] end defp _factors(n, divisor) when rem(n, divisor) == 0 do cond do is_prime?(divisor) -> _factors(n, divisor - 1) ++ [divisor] true -> _factors(n, divisor - 1) end end defp _factors(n, divisor) do _factors(n, divisor - 1) end defp is_prime?(1) do true end defp is_prime?(n) do of(n) == [1] end end input = hd(System.argv) number = String.strip(input) |> String.to_integer IO.puts "Prime factors of #{number} are #{inspect PrimeFactors.of(number)}"
The typical runtime of this code to calculate the basic coefficients of 50,000,000 is much worse: more than 17 seconds.
I built equivalent programs in Swift and Ruby. Optimized Swift runs in just 0.5 seconds, and Ruby (2.2 and is never known for its speed) runs a little over 6 seconds.
My main question is: how should Elixir's code be structured more idiomatic and avoid the performance issues that I see?
I also left some problems associated with such a simple problem, you can write Elixir code, which is very different from the efficiency. Perhaps this is mainly my inexperience in functional styles?