Implementing a maximum function on a list

Hello I am trying to learn Elixir by reading the book Programming Elixir 1.6
There is a question there:

Write a max(list) that returns the element with the maximum value in the
list. (This is slightly trickier than it sounds.)

I want to implement this function with using the reduce method, which was written like this:

  def reduce([], value,_func), do: value
  def reduce([head | tail], value, func), do: reduce(tail, func.(head, value), func)

for simplicity sake I am assuming that the input is always a non empty list. This is my non functioning implementation of the function:

  def max([a]), do: a
  def max([head | tail]), do: reduce([head | [second | tail]], check_big(head,second), check_big)

  def check_big(a,b) when a > b, do: a
  def check_big(a,b) when a <= b, do: b

When I try to compile the file I get this error message:

  Mylist.ex:15: undefined function check_big/0

why can’t I just pass a reference to the function? why isn’t it working?

Thanks for the help.

now that I look at it from a distance, I see that there are actually other logical mistakes, which would make this implementation invalid. :confused:

I got alone.

def max([a]), do: a
def max([head | tail]), do: reduce(tail, head, &check_big/2)

def check_big(a,b) when a > b, do: a
def check_big(a,b) when a <= b, do: b

Best Forum ever to talk with yourself
Love it.

16 Likes
10 Likes

It is totally fine to assume that there will be at least one element in the list, as there is no maximum defined for the empty list (this is also true for minimum).

There are two ways in elixir to solve this. One is to just let it crash, though this way you take the opportunity to handle the problem from the caller.

The other is, to return an ok/error tuple.

Also you really do not need to special case the singleton list. as reduce/3 with an empty list will just return the accumulator.

Next is that I’d call your current check_big/2 max/2 instead, as this is the name it is known as and taught in match classes.

Last but not least I’d probably implement that max/2 as a single clause with an if/2, or at least drop the second guard.

2 Likes

Another solution

[max | rest] = [5, 8, 4, 6, 10, 5]
Enum.reduce(rest, max, fn i, max ->
if max < i do
  i
else
  max
end
end)
[5, 8, 4, 6, 10, 5] |> Enum.reduce(&max/2)
5 Likes

There’s also Enum.max that they can use directly.

4 Likes
    def max([], val, _func), do: val
    def max([head|tail],val, func), do: max(tail, func.(head,val), func)
    def max(list), do: max(list, 0, fn (head, old_val) when head >= old_val -> head; (_, old_val) -> old_val; end)

Although, this is not the best solution to find greatest value in a list. However, in the book Programming Elixir 1.6, the quiz was supposed to be solved by implementing a reduce function, called max, with the help of recursion, pipe operator, anonymous function, comparison operators, guard clauses and pattern matching only. Without using any other predefined module/library functions or control flow constructs, if, unless, case etc.

Here is another version I wrote using Relaxed Boolean Operators (II and &&) to find greater of two numbers.

    ########### Find Greatest Value within a List - V2.0 ##########
    def max([], val, _func), do: val
    def max([head | tail], val , func), do: max(tail, func.(head, val), func)
    def max(list), do: max(list, 0, fn a,b -> a >= b && a || b end)

When I read the problem it seemed to me that the intention was to write the logic into the function without using Enum methods, so I got this:

  @spec max(list) :: number
  def max([a]), do: a
  def max([h | tl]) do
    [x | y] = tl
    if h > x do 
      max([h | y])
    else
      max(tl)
    end
  end
1 Like

Always enjoy these exercises over coffee in the morning. Here’s my take:

  @spec max([number()]) :: number
  def max([head]), do: head
  def max([head | [next | tail]]) when head > next, do: max([head | tail])
  def max([_head | tail]), do: max(tail)
3 Likes
def max([x]), do: x
def max([x | rest]), do: max(rest) |> Kernel.max(x)

Serious question, would the Kernel call be required in Elixir >= 1.9? I thought it was automatically imported now…?

I think the point there was to prevent a name conflict with the local function max. Otherwise yes, it’s automatically imported.

1 Like

I know you arrived at a different solution, but for learning purposes, your error is caused by the fact that naming a function w/out parens still calls it as if empty params were provided– aka check_big().

The way to get a function reference of a named function is to use the capture operator, &. aka &check_big/0 to pass it around!

2 Likes