Search code examples
c++dictionarymultimap

Using map.find() and count() on a key, which is a class object type


Is there any way that you could possible use map.find() / map.count() algorithms for a key, which is of type object of a class?

My multimap is made of pairs - map.<myClass, enum> and myClass has some member, like Filename for example. I would like to search for duplicate Filenames in my map and I have read that find() and count() functions do that for keys, but is possible to implement them to search for a member of the key?

Here is some code:

CDirectory (string n) {
              fp.open (n, ios::in);
              string dirName, fileName,  fType;
              int fileSize;
              fp >> dirName;
              m_strDirectory = dirName;
              while (fp >> fileName >> fileSize >> fType) {
                      CFile obj (fileName, fileSize);
                       if (fType == "Archive")
                  filetype = Filetype::Archive;
              else if (fType == "Hidden")
                  filetype = Filetype::Hidden;
              else if (fType == "ReadOnly")
                  filetype = Filetype::ReadOnly;
              else if (fType == "System")
                  filetype = Filetype::System;
              else
                  filetype = Filetype::FileNotSupported;
                      m_DirectoryMap.insert(pair<CFile, Filetype>(CFile(obj.getFileName(), obj.getFileSize()), Filetype(filetype)));
              }
              multimap<CFile, Filetype>::iterator p = m_DirectoryMap.begin();
              while ( p != m_DirectoryMap.end()) {
                cout << endl << p->first.getFileName() << '\t' << p->first.getFileSize() << '\t' << p->second << endl;
                ++p;
              }
    }

This is the constructor of the second class, which has a multimap of pairs (Objects of another class, enum>).

And here is the first class:

class CFile {
    string m_strFile;
    unsigned int m_size;
public:
    CFile () { m_strFile = ""; m_size = 0; }
    CFile (string name, int size ) { m_strFile = name; m_size = size; }
    string getFileName () const { return m_strFile; }
    int getFileSize () const { return m_size; }
    void setFileSize ( int size ) { m_size = size; }
    bool operator< (CFile& obj) {
        return ( m_size < obj.m_size );
    }
    bool operator== (const CFile& obj) {
        return ( m_size == obj.m_size );
    }
    friend ostream& operator<< ( ostream& ost, const CFile& obj ) {
        return ost << obj.m_strFile << obj.m_size;
    }
    friend istream& operator>> ( istream& ist, CFile& obj ) {
        return ist >> obj.m_strFile >> obj.m_size;
    }
    static bool Greater(const CFile& obj1, const CFile& obj2) {
        if ( obj1.m_size > obj2.m_size )
            return true;
        else
            return false;
    }
};

I want to find duplicates of string m_strFile;


Solution

  • A std::multimap compares keys through a Predicate (a function object whose call operator takes a reference to two objects of type Key).

    The default predicate for a std::multimap is std::less<>, which is why maps are normally ordered by ascending key.

    In order to make your keys comparable, you either need to specify a custom predicate in the map's template argument list (in the third position), or give your class a < operator.

    Then you would iterate through the map in groups of pairs, such as:

    struct MyKey
    {
        MyKey(std::string fname) : _filename { std::move(fname) } {}
        const std::string& filename() const { return _filename; }
    
        private:
          std::string _filename;
    };
    
    // define a predicate to order the map
    struct order_by_filename {
      bool operator()(const MyKey& l, const MyKey& r) const {
        return l.filename() < r.filename();
      }
    };
    
    struct DataObject {};
    
    std::multimap<MyKey, DataObject, order_by_filename> my_map;
    
    void strip_duplicates()
    {
        for(auto current = my_map.begin() ; current != my_map.end() ; )
        {
            auto range = my_map.equal_range(current->first);
            // range is a std::pair containing the first and last iterator
            // of the range with equal keys
            auto num_items = range.second - range.first;
            if (num_items > 1) {
                // strip duplicates
                my_map.erase(std::next(range.first), range.second);
            }
            // move to next range of keys
            current = range.second;
        }
    }
    

    for completeness, here's another way eliminating duplicates without using equal_range:

    void erase_all_but_one(std::multimap<Key, Value>& mmap, const Key& to_erase)
    {
      auto ifind = mmap.find(to_erase);
      if (ifind == mmap.end()) return;
      for(ifind = std::next(ifind) ; ifind != mmap.end() && ifind->first == to_erase ; )
      {
        // NOTE: take a copy of the advanced iterator BEFORE erasing
        // the iterator.
        auto inext = std::next(ifind);
        mmap.erase(ifind);
        ifind = inext;
      }
    }