Hayden
Hayden

Reputation: 41

Iterator through std::set using find() won't work. What's going wrong?

I am trying to create a program that has a list of contacts where the user can search for a contact's name based on a phone number. Sorry for including so much code but it is necessary for understanding my problem:

#include <iostream>
#include <string>
#include <set>
using namespace std;

struct ContactItem
{
    string name;
    string phoneNumber;
    string displayAs;

    ContactItem(const string inName, const string inNumber) : name(inName), phoneNumber(inNumber)
    {
        displayAs = name + ": " + phoneNumber;
    }

    bool operator== (const ContactItem& searchParameter) const
    {
        return (this->phoneNumber == searchParameter.phoneNumber);
    }

    bool operator< (const ContactItem& compareResult) const
    {
        return (this->name < compareResult.name);
    }

    operator const char*() const
    {
        return displayAs.c_str();
    }
};

int main()
{
    //Initialize a set and populate it with contacts of type ContactItem
    set<ContactItem> contactBook;
    contactBook.insert(ContactItem("Sally", "123654864"));
    contactBook.insert(ContactItem("Joe", "8435102654"));
    contactBook.insert(ContactItem("Steve", "8135691234"));
    contactBook.insert(ContactItem("Alice", "8432489425"));

    //Search for a contact's name by only being given their number
    cout << "Please give the number of one contact whose name you would like to know: " << endl;
    string userNumber;
    getline(cin, userNumber);

    auto findNumber = contactBook.find(ContactItem("", userNumber));

    if (findNumber != contactBook.end())
        cout << "The name of the contact whose number matches the phone number given is: " << (*findNumber).name << endl;
    else
        cout << "Contact not found" << endl;

    return 0;
}

My problem always seems to be with the line auto findNumber = contactBook.find(userNumber);. Every single time I run this code, the message "Contact not found" is displayed. I can't figure out what I'm doing wrong. Is it my redefinition of operator==?

The above code was inspired by Rao, Siddhartha. Sams Teach Yourself C++ in One Hour a Day. 8th ed., Indianapolis, IN, Sams, 2017.

Upvotes: 1

Views: 827

Answers (3)

Adrian McCarthy
Adrian McCarthy

Reputation: 48038

You don't want to use std::set::find to do this. std::set::find is to find an exact match, but you're looking for a partial match. std::set::find is going to look at only a subset of the contacts because it knows they are sorted. But you need to check all of the contacts, because any one of them could match the phone number.

What you need is std::find_if from <algorithm>. std::find_if takes a predicate, which is a function or function-like object that can tell if you if this is the right one.

First, include <algorithm>

#include <algorithm>

We can use a lambda for the predicate:

auto findNumber =
  std::find_if(contactBook.begin(), contactBook.end(),
               [&userNumber](const ContactItem &contact) {
                 return contact.phoneNumber == userNumber;
               });

If you haven't worked with lambdas before, this can look pretty weird. A lambda is like a nameless function with state.

The square brackets [] tell the compiler that this is a lambda. The &userNumber says that, in the body of the lambda, we're going to need a reference to the userNumber variable that's in the current scope. (This is called "capture by reference.")

The parentheses enclose a function-like parameter list. std::find_if is going to invoke this lambda on each contact, as though it were a regular function, by passing in a reference to the contact.

The body of the lambda (in braces {}) is a function body that returns a bool to tell use whether the contact passed in meets our criteria for a match. The body can reference the arguments passed in as well as anything "captured" from the defining scope. In this case, we just care whether the contact's phone number matches the desired phone number.

Upvotes: 2

NathanOliver
NathanOliver

Reputation: 181068

The issue here is that std::set does not use your operator == to find the element. Instead it uses the same thing it uses to compare objects which is the operator <. You're either going to have to change your operator < to compare by phoneNumber or provide a named contact.

You could also consider a boost multi-index container that would allow you to index my name and number.

Upvotes: 0

Slava
Slava

Reputation: 44288

As stated in std::set documentation by default it uses std::less which in this case uses ContactItem::operator< you povided. As you compare structs by name field - this field is used in comparing elements, so basically you are trying to find contact with empty name. You either need to specify different comparator for this std::set or change ContactItem::operator < accordingly.

Note: unlike std::unordered_set std::set does not use operator== in any form, as you can also see in documentatioon:

std::set is an associative container that contains a sorted set of unique objects of type Key. Sorting is done using the key comparison function Compare.

and std::less documentation says:

Function object for performing comparisons. Unless specialized, invokes operator< on type T.

Upvotes: 0

Related Questions