std::unique and removing duplicates from a container of objects

15,816

Solution 1

Two options to be able to use std::unique:

  1. Define an operator== method for Packet and change the vector<Packet*> to vector<Packet>.

    bool Packet::operator==(const Packet& rhs) const
    {
        if (getFilingTime() != rhs.getFilingTime())
            return false;
        if (getSpid() != rhs.getSpid())
            return false;
        return true;
    }
    
    //etc.
    
    int main()
    {
        vector<Packet> pkts;
        pkts.push_back(Packet("10:20", "1004"));
        pkts.push_back(Packet("10:20", "1004")); // not unique (duplicate of the line above)
        pkts.push_back(Packet("10:20", "251"));
        pkts.push_back(Packet("10:20", "1006"));
    
        // remove packet from vector if time and ID are the same
    
         pkts.erase(unique(pkts.begin(), pkts.end()), pkts.end());                   
    
        return 0;
    }
    
  2. Keep the vector as vector<Packet*> and define a method to compare the elements.

    bool comparePacketPtrs(Packet* lhs, Packet* rhs)
    {
        if (lhs->getFilingTime() != rhs->getFilingTime())
            return false;
        if (lhs->getSpid() != rhs->getSpid())
            return false;
        return true;
    }
    
    //etc.
    
    int main()
    {
        vector<Packet*> pkts;
        pkts.push_back(new Packet("10:20", "1004"));
        pkts.push_back(new Packet("10:20", "1004")); // not unique (duplicate of the line above)
        pkts.push_back(new Packet("10:20", "251"));
        pkts.push_back(new Packet("10:20", "1006"));
    
        // remove packet from vector if time and ID are the same
    
         pkts.erase(unique(pkts.begin(), pkts.end(), comparePacketPtrs), pkts.end());                   
    
        return 0;
    }
    

Solution 2

As an alternative to unique, you could simply insert the elements into a set (or unordered_set in C++11).

Whichever way you decide to go, you'll need to define comparison operators for Packet. For unique, you'll need operator==; for set you'll need operator<. For completeness, you should define both, and their counterparts:

class Packet {
    …
    bool operator==(const Packet& p) const {
        return fillingTime == p.fillingTime && recordID == p.recordID;
    }
    bool operator<(const Packet& p) const {
        return fillingTime < p.fillingTime ||
               (fillingTime == p.fillingTime && recordID < p.recordID);
    }
    bool operator!=(const Packet& p) const { return !(*this == p); }
    bool operator> (const Packet& p) const { return p < *this; }
    bool operator>=(const Packet& p) const { return !(*this < p); }
    bool operator<=(const Packet& p) const { return !(p < *this); }
    …
};

If you use C++11's unordered_set, you'll need to go one step further and define a hash function.

EDIT: I just noticed you're storing pointers to Packet. Why? Just store Packet directly.

Share:
15,816
tapatron
Author by

tapatron

Updated on June 04, 2022

Comments

  • tapatron
    tapatron almost 2 years

    I would like to know if there is an efficient way to remove objects from a container based on values of member fields of the objects. For example, I can do the following using stl::unique with a list of strings:

    #include<iostream>
    #include<list>
    #include<string>
    #include<algorithm>
    using namespace std;
    
    bool stringCompare(const string & l, const string & r)                                                                                   
    {                                                                                                                                    
       return (l==r);                                                                                                                         
    }
    
    int main()                                                                                                                               
    {                                                                                                                                        
    
      list<string> myStrings;                                                                                                                
      myStrings.push_back("1001");                                                                                                           
      myStrings.push_back("1001");                                                                                                           
      myStrings.push_back("81");                                                                                                             
      myStrings.push_back("1001");                                                                                                           
      myStrings.push_back("81");                                                                                                             
    
      myStrings.sort();                                                                                                                      
      myStrings.erase(unique(myStrings.begin(), myStrings.end(), stringCompare), myStrings.end());                                           
    
      list<string>::iterator it;                                                                                                             
      for(it = myStrings.begin(); it != myStrings.end(); ++it)                                                                               
      {                                                                                                                                      
        cout << *it << endl;                                                                                                                 
      }                                                                                                                                      
    
      return     0;                                                                                                                              
    }
    

    prints 1001, 81...

    Is there a way I can do something similar with the following code, or do I need to perform the comparisons "manually" using operators and iterating through the containers. I couldn't think of a more elegant solution and would like to know if this is possible without writing a lot of code. Any help will be much appreciated!

    class Packet
    {
    public:
    Packet(string fTime, string rID) : filingTime(fTime), recordID(rID)
    
      string getFilingTime() {return filingTime;}
      string getRecordId() {return recordID;}
    
    private:
      string filingTime;
      string recordID;
    
    };
    
    int main()
    {
    vector<Packet*> pkts;
    pkts.push_back(new Packet("10:20", "1004"));
    pkts.push_back(new Packet("10:20", "1004")); // not unique (duplicate of the line above)
    pkts.push_back(new Packet("10:20", "251"));
    pkts.push_back(new Packet("10:20", "1006"));
    
    // remove packet from vector if time and ID are the same
    
    return 0;
    }
    

    Thanks

  • Geoff Montee
    Geoff Montee over 11 years
    Yeah, no kidding. LOL. Thanks.
  • Craig Wright
    Craig Wright over 11 years
    If you absolutely must have a vector of pointers you can pass compare functions to unique and sort that will accept the pointers and operate on the underlying instances. Note the semantics of the compare functions are "==" and "<" respectively. Anyway, copying objects is not always desirable, and placing them in a vector will get you just that.
  • Alexei
    Alexei about 6 years
    @GeoffMontee Ran into the same problem, and came up with the same solution. However, in the second method, since we have a vector of dynamically created instances of Packet (using new), don't we need to deallocate memory? Does pkts.erase(_; _; _;) deallocate it for us when it removes elements from the vector?