ssukienn
ssukienn

Reputation: 588

Searching through lists of records with repeating elements

I need to make an address book in Erlang. I have done almost everything except for one function that's giving me problems.

My record is:

-record(contact, {fname, lname, phone=[], mail=[], city=[], street=[]}).

I have to write a function that will search through instances of contact and find all with a particular city name, and for those instances return {fname,lname} tuples. Different contacts can of course have the same cities.

When I needed similar functions for the mail and phone fields I did it this way:

findByPhone(_,[]) -> {error,"not found"};
findByPhone(Phone,[H|T]) ->
  case findPhoneForUser(Phone, H#contact.phone) of
    true -> {H#contact.fname, H#contact.lname};
    false -> findByPhone(Phone, T)
  end.
findPhoneForUser(_,[]) -> false;
findPhoneForUser(Phone, [Phone|_]) -> true;
findPhoneForUser(Phone, [_|T]) -> findPhoneForUser(Phone, T).

But both mail and phone are unique values, so whenever one is found the function is finished. For city, a search can yield multiple return values, so it must collect all matches.

How to handle this problem? I thought about list comprehensions something like:

{X,Y} || X<-H#contact.fname, Y<-H#contact.lname, City=:=H#contact.city

but it will return tuples from single ASCII codes :/

Upvotes: 2

Views: 137

Answers (1)

Steve Vinoski
Steve Vinoski

Reputation: 20014

You can use a list comprehension. Assuming your address book is stored in a variable named AddressBook, and the city you're matching is stored in a variable named City, the following will work:

[{C#contact.fname, C#contact.lname} || C <- AddressBook, C#contact.city == City].

Also note that you can simplify your findByPhone function using the lists:keyfind/3 function:

findByPhone(Phone, AddressBook) ->
    case lists:keyfind(Phone, #contact.phone, AddressBook) of
        #contact{fname=Fname, lname=Lname} -> {Fname, Lname};
        false -> {error, not_found}
    end.

This works because records are tuples under the covers. The construct #contact.phone used as the second argument to lists:keyfind/3 provides the element number of the phone field of the underlying tuple. In fact, you could write a single non-exported find function supporting any unique field in your record with this approach, and then write exported functions for each such searchable field:

find(Value, Field, AddressBook) ->
    case lists:keyfind(Value, Field, AddressBook) of
        #contact{fname=Fname, lname=Lname} -> {Fname, Lname};
        false -> {error, not_found}
    end.

findByPhone(Phone, AddressBook) -> find(Phone, #contact.phone, AddressBook).
findByMail(Mail, AddressBook) -> find(Mail, #contact.mail, AddressBook).

Upvotes: 3

Related Questions