Search code examples
c++algorithmdictionarymultimap

Find the largest / smallest key in a multimap of pairs <Class object, enum>, using multimap algorithms (std::minmax_element)?


I have this program, where I put pairs of Class Object + enums in a multimap. The class object has a member type int filesize. I want to find the largest and smallest key in my multimap.

I have done this by making 3 iterators, comparing each object with the next one and if it's smaller(or larger, depending in what I search for) it gets assigned to the 3rd iterator. After that, I just print out the 3rd iterator. Is there any other elegant way to do this? I know this works, but I'm sure there is another way of doing this - I just can't seem to find it.

Here is my function for max file:

void getMaxFile() {
        multimap<CFile, Filetype>::iterator p = m_DirectoryMap.begin();
        multimap<CFile, Filetype>::iterator t = m_DirectoryMap.begin();
        multimap<CFile, Filetype>::iterator x = m_DirectoryMap.begin();
        t++;
        while  (p != m_DirectoryMap.end()) {
                if (p->first.getFileSize() > t->first.getFileSize())
                    x = p;
                ++p, ++t;
        }
        cout << "The largest file is: " << endl << x->first.getFileName()
             << '\t' << x->first.getFileSize() << '\t' << x->second << endl;
    }

The constructor from the second class, where I make the multimap and fill it with pairs of another class objects + enums (read from a file):

 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;
              }
    }   

And the first class (which objects are the key in my multimap):

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;
    }
};

Solution

  • std::minmax_element allows you to pass a comparator to use when looking at the objects, so you can do something like this:

    auto p = std::minmax_element(m_directoryMap.begin(), m_directoryMap.end(), 
        [](CFile const &a, CFile const &b) { return a.getFileSize() < b.getFileSize(); });
    

    p will then be a pair of iterators into your collection, so you can (for example) print them out with:

    std::cout << "Smallest: " << p.first->getFileSize() << " bytes\n";
    std::cout << "Largest:  " << p.second->getFileSize() << " bytes\n";
    

    Looking more carefully, however, it appears that you're using the size member as the ordering for the files anyway. That being the case, you can use the fact that the map is already ordered based on the data you care about, so you can just use:

    std::cout << "Smallest: " << m_directoryMap.begin()->getFileSize() << " bytes\n";
    std::cout << "Largest:  " << m_directoryMap.rbegin()->getFileSize() << " bytes\n";
    

    Looking more at your code, however, you have a few other problems that are likely to affect being able to do what you're trying to do here. Here's a slightly simplified (and rewritten) version of your code, along with some code to find the minimum and maximum (and print them out):

    #include <string>
    #include <iostream>
    #include <map>
    #include <algorithm>
    
    using std::string;
    using std::istream;
    using std::ostream;
    
    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 const& obj) const {
            return (m_size < obj.m_size);
        }
        bool operator== (const CFile& obj) const {
            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) {
            return (obj1.m_size > obj2.m_size);
        }
    };
    
    struct cmp {
        bool operator()(CFile const &a, CFile const &b) {
            return a.getFileName() < b.getFileName();
        }
    };
    
    int main() {
        std::multimap<CFile, int, cmp> files {
            { CFile { "abc", 123 }, 1 },
            { CFile { "cde", 234 }, 2 },
            { CFile { "def", 345 }, 3 }
        };
    
        auto p = std::minmax_element(files.begin(), files.end(),
            [](auto const &a, auto const &b) { return a.first.getFileSize() < b.first.getFileSize(); });
    
        std::cout << p.first->first.getFileSize() << "\n";
        std::cout << p.second->first.getFileSize() << "\n";
    }