defmodule Numy.Enumy do @moduledoc """ Extend Enum for homogeneous enumerables. """ @doc """ Check if all elements of a list are integers. ## Examples iex(1)> import Numy.Enumy Numy.Enumy iex(2)> all_integers?([1, 2, 3]) true iex(3)> all_integers?([1.1, 2, 3]) false """ @spec all_integers?(Enumerable.t()) :: boolean def all_integers?(enumerable) do Enum.all?(enumerable, fn item -> is_integer(item) end) end @doc """ Check if all elements of a list are floats. ## Examples iex(10)> import Numy.Enumy Numy.Enumy iex(11)> all_floats?([1.1, 2.2, 3.3]) true iex(12)> all_floats?([1.1, 2.2, 3]) false """ @spec all_floats?(Enumerable.t()) :: boolean def all_floats?(enumerable) do Enum.all?(enumerable, fn item -> is_float(item) end) end @spec all_numbers?(Enumerable.t()) :: boolean def all_numbers?(enumerable) do Enum.all?(enumerable, fn item -> is_number(item) end) end @doc """ Convert all numerical elements of a list to `float` type. ## Examples iex(13)> all_to_float([1.1, 2.2, 3]) [1.1, 2.2, 3.0] """ @spec all_to_float(Enumerable.t()) :: [float] def all_to_float(enumerable) do Enum.map(enumerable, fn item -> cond do is_float(item) -> item is_integer(item) -> item / 1 # idiomatic way to convert integer to float true -> raise "non numerical item" end end) end @doc """ The dot product is the sum of the products of the corresponding entries of the two sequences of numbers. ## Examples iex> dot_product([1,2,3],[2,3,0]) 8 ## Benchmarks iex(1)> fn1 = fn(v1,v2) -> Numy.Enumy.dot_product(v1,v2) end iex(2)> fn2 = fn(v1,v2) -> Enum.zip(v1,v2) |> Enum.map(fn {a,b} -> a * b end) |> ...(2)> Enum.reduce(&Kernel.+/2) end iex(3)> fn3 = fn(v1,v2) -> Enum.zip(v1,v2) |> Flow.from_enumerable |> ...(3)> Flow.map(fn {a,b} -> a * b end) |> Enum.sum end iex(4)> vec = Enum.to_list(1..999_000) iex(20)> Benchee.run(%{"1" => fn -> fn1.(vec,vec) end, ...(20)> "2" => fn -> fn2.(vec,vec) end, "3" => fn -> fn3.(vec,vec) end}) Comparison: 1 22.58 3 0.58 - 38.84x slower +1.68 s 2 0.30 - 76.26x slower +3.33 s """ @spec dot_product([number], [number]) :: number def dot_product(vec1, _vec2) when vec1 == [], do: 0 def dot_product(vec1, vec2) when is_list(vec1) and is_list(vec2) do [h1|t1] = vec1 [h2|t2] = vec2 (h1*h2) + dot_product(t1, t2) end @doc """ Get mean (average) of a sequence of numbers. ## Examples iex(14)> mean([1,2,3,4,5,6,7,8,9]) 5.0 """ @spec mean(Enumerable.t()) :: float def mean(enumerable) do Enum.sum(enumerable) / Enum.count(enumerable) end end