erlang

How to check if a list of records contains a specific record in Erlang?


I'm new to Erlang and am trying to write a function that when passed a Point record (an x,y coordinate) and a list of Point records (a list of x,y coordinates), returns true if the Point record with the specified x and y coordinates is an element (i.e. already exists) in the list and false otherwise.

Here's the definition of the point record:

-record(point, {x, y})

This is the signature of the function:

contains_point(Point, List)

I tried looking up similar questions and in the Erlang docs, but haven't found what I'm looking for.

I also tried the following code:

contains_point(Point, List)->
    Found = fun(Point) -> lists:member(Point, List) end,
    case lists:any(Found, List) of
        true ->
            yes;
        false ->
            no
    end.

But got the warning messages, "variable 'Point' shadowed in 'fun'" and "variable 'Point' is unused."

Would appreciate any help with this, thank you!


Solution

  • Your function has multiple problems, hinted at by the unused and shadowed variable warnings.

    contains_point(Point, List) ->
        Found = fun(Point) -> lists:member(Point, List) end,
        case lists:any(Found, List) of
            true ->
                yes;
            false ->
                no
        end.
    

    The lists:any/2 function takes as its first argument a predicate function that's called with each element of the second argument, a list, until either the predicate returns true or the list is exhausted. The predicate here is the anonymous function Found, which returns the result of passing its list element argument to lists:member/2. But the lists:member/2 function also traverses its list argument, returning true if its first argument matches a list element or false otherwise, which means the Found predicate checks the entire list every time lists:any/2 calls it, which is quite inefficient.

    Your function also returns yes or no, but according to your description it's supposed to return a boolean.

    The easiest way to fix your function is to return the result of lists:member/2, since it traverses a list looking for a match:

    contains_point(Point, List) ->
        lists:member(Point, List).
    

    Alternatively, if you don't want to use the lists module, you can write the function to traverse the list itself and check for matches:

    contains_point(_, []) -> false;
    contains_point(Point, [Point|_]) -> true;
    contains_point(Point, [_|Tail]) ->
        contains_point(Point, Tail).
    
    1. The first clause matches the empty list and so returns false.
    2. The second clause provides the true case, since its first argument and the head of the list are both the same Point value and so they match.
    3. The third clause matches when Point doesn't match the head of the list; it calls contains_point/2 recursively with Point and the tail of list.

    This approach is essentially what lists:member/2 does.