How to get the smallest value group by variable value?

I'm noob in cpp I want to get some help, I want to select the lowest value within a vector which contain these list of object. it kind of select aggregation

class Label{
private:
    std::string lbl;
    int n;

public:

    int getN() const { return this->n; }
    std::string getlbl() const { return this->lbl; }

};

int main() {
    std::vector<Label> my_vect = {
    {"labl07", 0}, {"labl07", 0}, {"labl07", 0}, 
    {"labl07", 0}, {"labl07", 0}, {"labl02", 232}, 
    {"labl02", 232}, {"labl02", 233}, {"labl02", 234}, 
    {"labl02", 230}, {"labl02", 233}, {"labl02", 234}, 
    {"labl02", 229}, {"labl03", 379}, {"labl03", 377}, 
    {"labl03", 379}, {"labl03", 381}, {"labl03", 380}, 
    {"labl03", 377}, {"labl03", 381}, {"labl03", 372}
    };

    for(auto & v: my_vect)
    {
        cout <<"dis : "<< v.getlbl() <<" value " <<  v.getN() << endl;

    }
    return 0;
}

I hope do this aggragation

dis : labl07 value 0
dis : labl02 value 229
dis : labl03 value 372

in some comments below they use map associative container I need to understand why instead of vectors.

5 answers

  • answered 2018-07-18 10:41 azdoud

    try to use associative container maps in this case, cause using vector is more complex.

    string labelN;
    
    string val;
    int number;
    
    map<string, int> values;  
    
    while (readingInput)  
    {
      // input next line
      fileInput >> labelN >>  " ">> val>> "value " >> number;
      if (number> values[val])
      {
         values[val] = number;
       }
    }
    

    after reading some advice below. I have written this code I think it does the job unless somebody writes a better one. So first, you have to create a constructor of the objects that ganna be added to your vector. Second, you have to add a function which will sort your vector in an aggregation way, then insert the result into the map. the last part of the code I pushed the results in vector you might use it.

    #include <iostream>
    #include <sstream>
    #include <string>
    #include <map>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    class Label{
    private:
        std::string lbl;
        int n;
    
    public:
        Label(std::string sp, int np): lbl(sp), n(np) {}
        int getN() const { return this->n; }
        std::string getlbl() const { return this->lbl; }
        static bool sortByn( Label a, Label b )
        {
           if ( a.n < b.n ) return true;
           if ( a.n == b.n && a.lbl < b.lbl ) return true;
           return false;
        }
    
    };
    
    int main() {
        std::vector<Label> my_vect = {
        {"labl07", 0}, {"labl07", 0}, {"labl07", 0},
        {"labl07", 0}, {"labl07", 0}, {"labl02", 232},
        {"labl02", 232}, {"labl02", 233}, {"labl02", 234},
        {"labl02", 230}, {"labl02", 233}, {"labl02", 234},
        {"labl02", 229}, {"labl03", 379}, {"labl03", 377},
        {"labl03", 379}, {"labl03", 381}, {"labl03", 380},
        {"labl03", 377}, {"labl03", 381}, {"labl03", 372}
        };
    
        for(auto & v: my_vect)
        {
            cout <<"dis : "<< v.getlbl() <<" value " <<  v.getN() << endl;
        }
    
        map<string,int> smallest;
        string lbl;
        int n;
    
        for(auto & v: my_vect)
        {
            lbl = v.getlbl();
            n = v.getN();
            bool occurredBefore = smallest.count( lbl );
            if ( occurredBefore )
            {
             if ( n < smallest[lbl] ) smallest[lbl] = n;
            }
            else
            {
             smallest[lbl] = n;
            }
        }
    
       vector<Label> V;
       for ( auto e : smallest ) V.push_back( { e.first, e.second } );
       sort( V.begin(), V.end(), Label::sortByn );
       for ( Label L : V ) cout << L.getlbl() << '\t' << L.getN() << '\n';
    }
    

  • answered 2018-07-18 11:17 Andrew Kashpur

    you can use multimap to do this, consider following example (and comments)

    #include<iostream>
    #include<string>
    #include<map>
    #include<vector>
    #include<algorithm>
    
    struct x{
        std::string s_value;
        int i_value;
    };
    
    int main() {
        std::vector<x> v{
            {"01", 11},
            {"02", 9},
            {"03", 27},
            {"01", 3},
            {"02", 7},
            {"03", 34},
            {"01", 2},
            {"02", 6},
            {"03", 11},
        };
        // get unique keys
        std::vector<std::string> keys {};
        for(auto& x_value: v){
            // if key is not present in keys yet put it there
            if(std::find(keys.begin(),keys.end(), x_value.s_value) == keys.end()){
                keys.push_back(x_value.s_value);
            }
        }
        std::multimap<std::string, int> mmap;
        for(auto& x_value : v){
            //put values from vector into multimap
            mmap.insert( decltype(mmap)::value_type(x_value.s_value, x_value.i_value) );
        }
    
        for(auto& key : keys){
          // for each value we expect to be in multimap get range of values
          std::vector<int> values{};
          auto range = mmap.equal_range(key);
          // put vaules for range into vector
          for(auto i = range.first; i!= range.second; ++i){
              values.push_back(i->second);
          }
          // sort vector
          std::sort(values.begin(), values.end());
          // print the least value in range corresponding to key, if there was any
          if(!values.empty()){
            std::cout<<key<<" "<<values[0]<<std::endl;
          }
        }
    
        return 0;
    }
    

  • answered 2018-07-18 11:21 FrankS101

    As @Aconcagua has suggested, you can sort the vector using a custom comparator to sort the values of your vector:

    [](Label const& x, Label const& y) { 
                return ((x.getlbl() < y.getlbl()) || 
                       ((x.getlbl() == y.getlbl()) && (x.getN() < y.getN()))); };
    

    You also need a constructor to construct the objects that will be inserted in the vector:

    Label(std::string label, int value) : lbl(label), n(value){}
    

    and when you iterate over all the values just print the element whenever the label is a different one. Thus, the code can look like:

    #include <iostream>
    #include <vector>
    #include <string>
    #include <algorithm>
    
    class Label{
    private:
        std::string lbl;
        int n;
    
    public:
        Label(std::string label, int value) : lbl(label), n(value){}
        int getN() const { return this->n; }
        std::string getlbl() const { return this->lbl; }
    
    };
    
    int main() {
        std::vector<Label> my_vect = {
        {"labl07", 0}, {"labl07", 0}, {"labl07", 0}, 
        {"labl07", 0}, {"labl07", 0}, {"labl02", 232}, 
        {"labl02", 232}, {"labl02", 233}, {"labl02", 234}, 
        {"labl02", 230}, {"labl02", 233}, {"labl02", 234}, 
        {"labl02", 229}, {"labl03", 379}, {"labl03", 377}, 
        {"labl03", 379}, {"labl03", 381}, {"labl03", 380}, 
        {"labl03", 377}, {"labl03", 381}, {"labl03", 372}
        };
    
        std::sort(my_vect.begin(), my_vect.end(), [](Label const& x, Label const& y) { 
            return ((x.getlbl() < y.getlbl()) || ((x.getlbl() == y.getlbl()) && (x.getN() < y.getN()))); });
    
        std::string labelToPrint;
    
        for(const auto& v: my_vect)
        {
            if (labelToPrint.compare(v.getlbl()) != 0)
            {
                std::cout <<"dis : "<< v.getlbl() <<" value " <<  v.getN() << std::endl;    
                labelToPrint = v.getlbl();  
            }
        }
        return 0;
    }
    

  • answered 2018-07-18 11:27 Daniel Langr

    You can do this easily with range-v3 library:

    auto groups = my_vect | ranges::view::group_by(
       [](const Label& l1, const Label& l2){ return l1.getlbl() == l2.getlbl(); });
    
    for (const auto & group : groups) {
       auto min = ranges::min(group,
          [](const Label& l1, const Label& l2){ return l1.getN() < l2.getN(); });
    
       std::cout << min.getlbl() << ": " << min.getN() << std::endl;
    }
    

    Output:

    labl07: 0
    labl02: 229
    labl03: 372
    

    Note that for higher performance getlbl() shoud return by const reference.

  • answered 2018-07-18 11:34 Aconcagua

    While asdoud's answer technically being correct (referring to edit, revision 3), it uses multiple map lookups which can be avoided by the following variant of:

    for(auto & v: my_vect)
    {
        int n = v.getN();
        // pre-C++11 variant:
        //auto entry = smallest.insert(std::make_pair(v.getlbl(), n));
        // since C++11:
        auto entry = smallest.emplace(v.getlbl(), n);
        if(!entry.second)
        {
            if(n < entry.first->second)
                entry.first->second = n;
        }
    }
    

    Further improvement: Strings are yet copied, which actually is not necessary, as the map does not live longer than the vector, which contains the strings. So if lbl is returned as const reference, we could use std::reference_wrapper<std::string> as map keys (or even char const* with appropriate custom comparator).