Not directly related to your question, but a general tip for new Elixir devs: calling functions like length
and Enum.at
inside a loop should make you slightly worried about performance.
The reason is that both of those functions take time that’s proportional to the size of the input (bills
here usually), unlike other languages where arrays can be accessed in a constant amount of time. This means that calculating something like length(bills)
inside a recursion over bills
will immediately be accidentally quadratic.
IMO a good general principle is to avoid using indexes as much as possible. For instance, in your code above, every call to Enum.at
uses the same index so the whole thing can be rewritten as an Enum.map
:
calcTip = fn bill ->
if bill >= 50 and bill <= 300, do: bill * 0.15, else: bill * 0.2
end
bills = [ 22, 295, 176, 440, 37, 105, 10, 1100, 86, 52 ]
tips_and_totals_as_pairs =
Enum.map(bills, fn bill ->
tip = calcTip.(bill)
{tip, tip + bill}
end)
tips_and_totals = Enum.unzip(tips_and_totals_as_pairs)
or an alternate version with explicit recursion, if that’s a requirement:
calcTip = fn bill ->
if bill >= 50 and bill <= 300, do: bill * 0.15, else: bill * 0.2
end
bills = [ 22, 295, 176, 440, 37, 105, 10, 1100, 86, 52 ]
calc_tips_and_totals = fn
[bill | rest], tips, totals, recursive_fn ->
tip = calcTip.(bill)
calc_tips_and_totals(rest, [tip | tips], [tip + bill | totals], recursive_fn)
[], tips, totals, _ ->
[Enum.reverse(tips), Enum.reverse(totals)]
end
tips_and_totals = calc_tups_and_totals(bills, [], [], calc_tips_and_totals)
Some general notes from the above:
- to know when to stop, instead of checking
length
(which is expensive) this pattern-matches on [bill | rest]
vs []
(which is super-cheap)
- instead of appending to the end of lists with
totals ++ [new_value]
, this adds to the beginning of the list (super-cheap again) and then reverses at the end. See the BEAM Efficiency Guide for some additional discussion on this.
- both of the versions above produce separate lists for
tips
and totals
, but you may want to consider keeping those things together either as a tuple (omit the Enum.unzip
) or even a map/struct. That way the values for a particular bill are always in one place, instead of spread across multiple lists.