3

This is meant to allow user to input name, contact and address he/she wishes to search for. What I wanted to do is to display all objects that applying pred to it is true but I can't seems to get it working.

static string searchName, searchContact, searchAddress;

bool search_User(User &u)
{ 
   return (u.getName() == searchName && u.getContact() == searchContact && u.getAddress() == searchAddress);

}
void searchUser(vector<User> &u)
{
    cout << "Name of user: ";
    getline(cin, searchName);
    cout << "Contact of tutor: ";
    getline(cin, searchContact);
    cout << "Adress of user: ";
    getline(cin, searchAddress);
    vector<User>::iterator i;
    i = find_if(u.begin(), u.end(), search_User);
    cout << i->getName() << i->getContact() << i->getAddress() << endl;
}
delphi316
  • 201
  • 2
  • 4
  • 10
  • Seems your problem is maintaining a state in the predicate and for that you can use Function Objects. – Alok Save Jan 27 '12 at 14:06
  • `find_if` seems like the wrong algorithm. You probably want `for_each` with a function that does `if (predicate(x)) { action(x); }`. – CB Bailey Jan 27 '12 at 14:17
  • @CharlesBailey why would you say find_if wrong , the op is doing it wrong, isn't he? – Mr.Anubis Jan 27 '12 at 18:07
  • 1
    @Mr.Anubis: `find_if` is _supposed_ to stop at the first thing that it finds where as 'op' wants to do something (test and action) for _every_ element in a range for which `for_each` is more suitable. – CB Bailey Jan 27 '12 at 18:37
  • You should avoid those `static` variables and pass `u` by `const` reference to `search_User()` and `searchUser()`. – Walter Jul 24 '17 at 23:08

7 Answers7

11

The usual solution is to use std::copy_if:

std::vector<User> matches;
std::copy_if(v.begin(), v.end(), std::back_inserter(matches),
    [Name, Contact, Address](User const& u)
    { return u.getName() == Name && u.getContact() == Contact && u.getAddress() == Address;});

or just write a classic loop

for (User& u : users) {
  if (search_User(u) {
    std::cout << u; // Assumes you've implemented operator<<(ostream&, User)
  }
} 
Walter
  • 44,150
  • 20
  • 113
  • 196
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Makes me wish there would be a `std::filter` algorithm. – Frerich Raabe Jan 27 '12 at 14:17
  • @FrerichRaabe: What would the semantics be? `copy_if` is already a filter (so is `remove_if`). – MSalters Jan 27 '12 at 14:20
  • `std::filter` would yield a new object containing all elements in the given range for which the given predicate yields `true`. This `Create new object, use copy_if to put matching elements into it` pattern is quite common. – Frerich Raabe Jan 27 '12 at 14:23
  • 4
    That's not a "classic loop"; it's an ultra-modern C++ 11 loop. – CB Bailey Jan 27 '12 at 14:25
  • is copy_if under the stl algorithm of c++? Can't seem to find it in http://www.cplusplus.com/reference/algorithm/ – delphi316 Jan 27 '12 at 14:26
  • 5
    there's `boost::filter_iterator`. The `std::filter` algorithm you describe would, in order to fit with the design of the rest of the standard library, have to take an output iterator instead of creating and returning its own collection. This is exactly what `copy_if` does. – bames53 Jan 27 '12 at 14:30
  • @NewUserSeekingHelp: It's C++11, for older compilers you can use a workaround with `std::remove_copy_if`. – Christian Ammer Jan 27 '12 at 14:31
  • The question is not tagged `C++11` – Andriy Tylychko Jan 27 '12 at 14:31
  • 3
    @AndyT: It's not tagged c++98 either, so I'm using the current definition. – MSalters Jan 27 '12 at 14:36
  • 1
    @NewUserSeekingHelp `copy_if` is actually part of C++11. cplusplus.com hasn't been updated with C++11 stuff. You can use http://en.cppreference.com/w/cpp/algorithm instead. - `copy_if` was left out of the original standard library just as an oversight, however the same algorithm is available under a less intuitive name. If your implementation of C++ has `copy_if` just use it. Otherwise use `remove_copy_if` with `not1` to invert your predicate. – bames53 Jan 27 '12 at 14:38
  • Also, if `User` has an appropriate equality operator the `equal_to` predicate might be better than a lambda. `copy_if(begin(v),end(v),back_inserter(matches),bind(equal_to(),searchUser,_1))`. Though whether this is more readable or not may depend on how used the reader is to the standard predicates. – bames53 Jan 27 '12 at 14:50
  • 2
    The thing about the `copy_if()` solution is that it holds in memory all the predicates that hold true. An efficient solution would be either a `filter_iterator` or a `for_each_if()` algorithm. – wilhelmtell Jan 27 '12 at 18:53
  • 1
    You're missing the `return` keyword in the lambda – underscore_d Jul 24 '17 at 22:25
6

Pseudocode:

for(iterator i = v.begin(); 
    (i = find_if(i, v.end(), ...)) != v.end(); ++i )
{
    print *i;
}
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 1
    A concrete, complete, usable template solution is not much different from this pseudo-code. Might as well write the whole template algorithm. – wilhelmtell Jan 27 '12 at 18:57
4

One method would be to use std::copy_if (since C++11) or std::remove_copy_if by negating your predicate with not1 – this copy_if workaround I found in the answer to "Why there is no std::copy_if algorithm?".

std::vector<User> result;
std::remove_copy_if(u.begin(), u.end(),
                    std::back_inserter(result),
                    std::not1(std::ptr_fun(search_User)));

Another method would be to use the std::partition algorithm.

Reorders the elements in the range [first, last) in such a way that all elements for which the predicate p returns true precede the elements for which predicate p returns false.
(http://en.cppreference.com/w/cpp/algorithm/partition)

std::vector<User>::const_iterator newend =
    std::partition(u.begin(), u.end(), search_User);
Community
  • 1
  • 1
Christian Ammer
  • 7,464
  • 6
  • 51
  • 108
1

(Requires C++11) A slight variation on that proposed by MSalters would be to write to std::cout as matches are found:

#include <iostream> 
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>

...

std::ostream& operator<<(std::ostream& a_out, const User& a_user)
{
    std::cout << a_user.getName() << ", " << a_user.getContact() << ", " <<
        a_user.getAddress();
    return a_out;
}

...

std::copy_if(users.begin(),
             users.end(),
             std::ostream_iterator<const User>(std::cout, "\n"),
             [name, contact, address](const User& a_user)
             {
                return name    == a_user.getName()    &&
                       contact == a_user.getContact() &&
                       address == a_user.getAddress();
             });

EDIT:

To display a "User not found" message you could modify it as follows:

int count = 0;
std::copy_if(users.begin(),
             users.end(),
             std::ostream_iterator<const User>(std::cout, "\n"),
             [&count, name, contact, address](const User& a_user) -> bool
             {
                if (name    == a_user.getName()    &&
                    contact == a_user.getContact() &&
                    address == a_user.getAddress())
                {
                    count++;
                    return true;
                }
                return false;
             });

if (!count) std::cout << "User not found\n";
Community
  • 1
  • 1
hmjd
  • 120,187
  • 20
  • 207
  • 252
  • I've decided to use your method but there will be one issue. How will I be able to display "There is no user with such information" when nothing is found. – delphi316 Jan 28 '12 at 10:55
  • Is it not enough to display nothing? Prior to `std::copy_if()` you could print `std::cout << "Beginning search\n"`;` and after `std::copy_if()` print `std::cout << "Seach complete\n";`. The absence of any user details indicates no user was found. – hmjd Jan 28 '12 at 12:00
  • Tried it but it seem weird. So there is no other solution? – delphi316 Jan 28 '12 at 12:34
  • Updated my answer with another alternative. – hmjd Jan 28 '12 at 12:35
  • Having error with the alternative, "a lambda that has been specified to have a void return type cannot return a value" – delphi316 Jan 28 '12 at 12:45
0

For C++14, my preferred solution is to use std::copy_if and return a vector of all matches and pass in parameters required to maintain compartmentalization:

std::vector<User> findMatches( const std::vector<User> &v) {
    std::vector<User> matches;
    std::copy_if(v.begin(), v.end(), std::back_inserter(matches),
    [Name, Contact, Address](User& u)
    { 
         return(u.getName() == Name 
             && u.getContact() == Contact 
             && u.getAddress() == Address);
    });
    return matches;
}

The currently top posted solution by @MSalters has a bug in the lambda which is missing a return statement.

https://godbolt.org/g/CQOXzv

http://cpp.sh/2j7q7

$ g++-5 -std=c++14 user.cpp -o test.bin
$ ./test.bin

To compile in command line, save following as user.cpp, and run:

#include <string>
#include <algorithm>
#include <vector>
#include <iostream>

class User {
    public:
        std::string Name;
        std::string Contact;
        std::string Address;

        auto getName() {
            return Name;
        }
        auto getContact() {
            return Contact;
        }
      auto getAddress() {
          return Address;
      }
      User() = default;
};

std::ostream & operator<<(std::ostream &stream, const User &u)
{
    stream << u.Name << " " << u.Contact << " " << u.Address;
    return stream;
}

std::vector<User> findmatches( std::vector<User> &v, 
                           std::string Name, 
                           std::string Contact, 
                           std::string Address) {
    std::vector<User> matches;
    std::copy_if(v.begin(), v.end(), std::back_inserter(matches),
    [Name, Contact, Address](User& u)
    {
        return(u.getName() == Name 
        && u.getContact() == Contact 
        && u.getAddress() == Address);
    });
    return matches;
}


int main () {

std::vector<User> v {};
v.push_back({"Tom Cruise","917-032-2342","200 Top Gun Lane, Miramar, CA 93212"});
v.push_back({"Chuck Norris","911-032-1111","300 Santa Monica Blvd, Hollywood, CA 93212"});
v.push_back({"Santa Clause","315-4323-3111","1 North Pole Circle, North Pole, Elf Division, Antarctica 00000"});

auto matches = findmatches(v, "Chuck Norris", "911-032-1111", "300 Santa Monica Blvd, Hollywood, CA 93212");

for ( auto i : matches ) {
    std::cout << i << std::endl; 
  }
}
underscore_d
  • 6,309
  • 3
  • 38
  • 64
Chuck Norrris
  • 284
  • 5
  • 12
0

You can loop through all hits by incrementing the return value of find_if() and passing it to a subsequent call, e.g.:

// Switched to const_iterator, purely because the example code wasn't
// changing the elements found.
vector<User>::const_iterator i = find_if(u.begin(), u.end(), search_User);

while (i != u.end())
{
    cout << i->getName() << i->getContact() << i->getAddress() << endl;
    ++i;
    i = find_if(i, u.end(), search_User);
}
-1

To convert static vars to struct:

struct Search_conditions
{
    string name, contact, address;

    bool operator()(User& u) {...}
};

Search_conditions sc = {name, contact, address};
find_if(u.begin(), u.end(), sc);
Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112