defmodule Higher do @moduledoc """ Rather than writing a specific recursive function to do a task write a recursive function that takes another function to do the task. That way do not have to write the recursion all the time. Even better, use a library function to replace the recursion. That is exactly what is being done in this module. Created: Aug 2022 gtowell modified: Oct 2022 gtowell """ @doc """ A basic set of recursive functions to sum the items in a list With only one param, a list, turn it into two params with zero. First param is the list, second is the starting point -- usually zero. NOT tail-recursive """ def rsum(lst) do rsum(lst) end def rsum([]) do # nothing left, just return total 0 end def rsum([h|r]) do # h + rsum(r) end @doc """ Tail recursive version of rsum. Identical to rsum otherwise EXCEPT that in this case the second parameter holds the running sum which is what allows for tail recursion """ def rsum_tc(lst) do # with only one param, add a second -- zero -- as the starting point rsum_tc(lst, 0) end def rsum_tc([], tot) do # nothing left in the list so return the total tot end def rsum_tc([h|r], tot) do # the tail recursive call rsum_tc(r, h+tot) end @doc """ Tail recursive and passing a function to do the work. That way, unlike rsum_tc which can only ever just compute the sum of the items in the list, this one can do other things (the sum of the squares), etc. Here requiring the starting point to be passed in.. Still tail recursive! """ def rsum_tc_f([], tot, _f) do # nothing left to do but return the total tot end def rsum_tc_f([h|r], tot, f) do # do the recursion. Note the use to the function to update # the accumulator. rsum_tc_f(r, f.(tot,h), f) end @doc """ Rather than returning a single number for a list like rsum, here we return a new list with the same number of items as the original but with some transformation applied to them. In this case, the numbers are squared. One problem with this as written, the list is reversed! """ def t_map_tc(lst) do # one arg, just the list; call the two arg version with an empty list as second; the empty is where stuff will be accumulated. t_map_tc(lst, []) end def t_map_tc([], acc) do # nothing left in list, so just return acc end def t_map_tc([h|r], acc) do # do the recursion t_map_tc(r, [h*h | acc]) end @doc """ As with t_map_tc, this does the same sort of thing, but because it receives a function to do the transformation, it can do things other than just square the items in the list. """ def t_map_tc_f(lst, func) do # Two args, add a third to enable tail-recursion t_map_tc_f(lst, func, []) end def t_map_tc_f([], _func, acc) do # base case for recursion acc end def t_map_tc_f([h|r], func, acc) do # do the recursion, using the passed in function to transform t_map_tc_f(r, func, [func.(h) | acc]) end @doc """ look in a list for items matching a particular condition. return a new list with only those items """ def filter_tc(lst) do # the standard tail call accumulator trick filter_tc(lst, []) end def filter_tc([], acc) do # base case for recursion acc end def filter_tc([h|r], acc) do # do the filtering if rem(h, 2)==1 do # filter mapped so recur and add item to accumulation filter_tc(r, [h|acc]) else filter_tc(r, acc) end end @doc """ Filtering passing a function. Identical to filter_tc except that the decision is made using the passed in function rather than hard coded. """ def filter_tc_f(lst, func) do filter_tc_f(lst, [], func) end def filter_tc_f([], acc, _func) do acc end def filter_tc_f([h|r], acc, func) do if func.(h) do filter_tc_f(r, [h|acc], func) else filter_tc_f(r, acc, func) end end @doc """ Reverse a list. As always using tail recursion """ def reverse_tc(lst) do reverse_tc(lst, []) end def reverse_tc([], acc) do acc end def reverse_tc([h|r], acc) do reverse_tc(r, [h|acc]) end def main do lst = [1,2,3,4,5,6,7,8,9] IO.inspect lst IO.puts("Sum: #{rsum(lst)}") IO.puts("sum TR #{rsum_tc(lst)}") IO.puts("sum TR f #{rsum_tc_f(lst, 0, fn (acc, nw) -> acc+nw end)}") IO.puts("sum squares #{rsum_tc_f(lst, 0, fn (acc, nw) -> acc+nw*nw end)}") IO.puts("sum cubes #{rsum_tc_f(lst, 0, fn (acc, nw) -> acc+nw*nw*nw end)}") IO.inspect(t_map_tc(lst)) IO.inspect(reverse_tc(t_map_tc_f(lst, fn (itm) -> itm*itm end))) IO.inspect(filter_tc(lst)) IO.inspect(filter_tc_f(lst, fn (itm) -> rem(itm, 2)==1 end)) average = fn (lst) -> rsum_tc(lst) / Enum.count(lst) end IO.inspect(filter_tc_f(lst, &(&1 > average.(lst)))) end end Higher.main