First, a simple way to do this with one argument:
defmodule Double do
def double([]), do: []
def double([head | tail]) when rem(head, 2) == 0, do: [head * 2 | double(tail)]
def double([head | tail]), do: [head | double(tail)]
end
This uses pattern matching of the argument to assign the first element of the list to the head
variable.
when rem(head, 2) == 0
is a guard that means this function clause will only get executed when it is true (the first item of the list is even, in this case).
We then return a new list consisting of the possibly doubled value, and use recursion to compute the rest of the list.
The above method builds up the result in the call stack. I suspect the reason you are asked to use two arguments is to take advantage of tail-call optimisation, which means even though a recursive call it made, no extra stack frames are used. Because we don't have a call stack in which to build the result, we add an extra output
argument and build it there:
defmodule Double do
def double([], output), do: Enum.reverse(output)
def double([head | tail], output) when rem(head, 2) == 0, do: double(tail, [head * 2 | output])
def double([head | tail], output), do: double(tail, [head | output])
end
Here we write a function that takes an input
and an output
list.
The function calls itself until the input
has been exhausted (is the empty list []
), and builds up the answer in the output
list, which it eventually returns. In each call, we prepend the current item to the output list.
iex> Double.double([1,2,3,4], [])
[1, 4, 3, 8]