How would you write the following algorithm in Elixir?

Copied form somewhere, but I don’t wanna show the source yet, because in that case the (imperative) solutions there might influence ElixirForums’ solutions.

Calculating something like the bacterial reproduction is very easy, e.g., “Under ideal conditions, the number of bacteria doubles every 30 minutes”, but my requirement is different.

An animal gives birth to her first offspring at the age of 7 years, after that it gives birth every year up to the age of 30. How many of its offspring will be alive after 100 years if (ideally) all of the offspring are females.

(Someone mentioned in the comments that I should mention the age at which an animal dies and suggested the age of 40. Let’s think every animal dies at the age of 40).

I tried to do it, but failed. Looks like some nested loop with if-else will be involved, but my brain isn’t working on it right now.

Maybe you can model each animal as a process just for fun which would send messages to themselves like :give_birth up until they “die”. And they would be supervised so that you can count how many of them animals are “alive” at any moment.

Or maybe you can model it as a differential equation …

1 Like

It looks like very interesting scenario. I thought about it for a while, but wasn’t able to create some algorithm mentally.

I wouldn’t start modeling that with processes if you don’t need it. I’ll start with plain datastructures and work my way up.

Plain datastructures version
defmodule AnimalHerd do
  defstruct animals: [], age: 0

  defmodule Animal do
    defstruct age: 0
  end

  alias AnimalHerd, as: Herd
  alias AnimalHerd.Animal

  def new(num_initial_animals \\ 1)
      when is_integer(num_initial_animals) and num_initial_animals >= 1 do
    animals = for _ <- 1..num_initial_animals, do: %Animal{}
    %Herd{animals: animals}
  end

  def advance_years(herd, years \\ 1) when is_integer(years) and years >= 1 do
    Enum.reduce(1..years, herd, fn _, herd ->
      advance_year(herd)
    end)
  end

  defp advance_year(%{animals: animals} = herd) do
    animals =
      animals
      |> Enum.flat_map(fn
        # Animal dies
        %{age: 40} ->
          []

        # Breed offspring
        %{age: age} = animal when age >= 7 ->
          [%{animal | age: animal.age + 1}, %Animal{}]

        # To young for offsprings
        animal ->
          [%{animal | age: animal.age + 1}]
      end)

    %{herd | age: herd.age + 1, animals: animals}
  end
end
6 Likes

Here’s another way to do it, I think. (I haven’t gone through to verify that this gives the correct answer.)

We’ll let the population be represented by a list of integers. The ith element of the list is the number of animals aged i+1. To start with, we’ll assume a single animal of age 1.

pop = [1]

In a given year, each animal aged 7-30 will produce an offspring, so to get the number of new births, we sum elements 6 through 29 in the list. For this, we slice the list starting at element 6 taking 24 terms.

defp fertile_pop(pop) do
  pop |> Enum.slice(6,24) |> Enum.sum()
end

To calculate the population the next year, the animals of age 1 will be equal to the new births, which is given by fertile_pop. For the rest, the animals of age n will just be the animals of age n-1 from the previous year. So the list

[fertile_pop(pop) | pop]

will give the next year’s population. We iterate this as many times as we want, and then to get the number of live animals (aged 1-40), we sum the first 40 elements of the list. Here’s a recursive function that should give the answer.

# If n=0, we are done. Return the number of live animals
def find_population(pop, 0) do
  pop |> Enum.take(40) |> Enum.sum()
end

# Otherwise, calculate the population the next year and recurse with n-1.
def find_population(pop, n) do
  find_population([fertile_animals(pop) | pop], n-1)
end

find_population([1], 100)