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 …
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
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)