Search code examples
c++c++11stdvectorstdmappartition

Accessing a partitioned vector within a map


I have an input file which has the following structure

#Latitude   Longitude   Depth [m]   Bathy depth [m] CaCO3 [%] ...
-78 -177    0   693 1
-78 -173    0   573 2
.
.

I have created a map, which has a key based on a string (the name of an ocean basin) and a value which contains a vector of the data. Now I need to sort the vectors by bathyDepth. To be precise, I would like to partition the vectors so that I can have a partition between all data rows with a depth between 0 and 500m, 500m and 1500m, 1000m and 2000m...

I have stored the data into the map structure, but I'm unsure of how to store and access the partition so that I can then cout a data point at a particular depth.

My attempt:

//Define each basin spatially
//North Atlantic
double NAtlat1 = 0,  NAtlong1 = -70, NAtlat2 = 75, NAtlong2 = -15;
//South Atlantic and the rest...
double SPLIT = 0;

struct Point
{
   //structure Sample code/label--Lat--Long--SedimentDepth[m]--BathymetricDepth[m]--CaCO3[%]--CO3freefraction (SiO2 carb free)[%]--biogenic silica (bSiO2)[%]--Quartz[%]--CO3 ion[umol/l]--CO3critical[umol/l]--Delta CO3 ion[umol/kg]--Ref/source
   string dummy;
   double latitude, longitude, rockDepth, bathyDepth, CaCO3, fCaCO3, bSilica, Quartz, CO3i, CO3c, DCO3;
   string dummy2;
   //Use Overload>> operator
   friend istream& operator>>(istream& inputFile, Point& p);
};

//MAIN FUNCTION
std::map<std::string, std::vector<Point> > seamap;
seamap.insert( std::pair<std::string, std::vector<Point> > ("Nat", vector<Point>{}) );
seamap.insert( std::pair<std::string, std::vector<Point> > ("Sat", vector<Point>{}) );
//Repeat insert() for all other basins

Point p;
while (inputFile >> p && !inputFile.eof() )
{
    //Check if Southern Ocean
    if (p.latitude > Slat2)
    {
        //Check if Atlantic, Pacific, Indian...
        if (p.longitude >= NAtlong1 && p.longitude < SAtlong2 && p.latitude > SPLIT)
        {
            seamap["Nat"].push_back(p);
        } // Repeat for different basins
    }
    else
    {
        seamap["South"].push_back(p);
    }
}
//Partition basins by depth
for ( std::map<std::string, std::vector<Point> >::iterator it2 = seamap.begin(); it2 != seamap.end(); it2++ )
{
    for (int i = 500; i<=4500; i+=500 )
    {
        auto itp = std::partition( it2->second.begin(), it2->second.end(), [&i](const auto &a) {return a.bathyDepth < i;} );
    }
}

Note: a is of type Point. If I try to store itp into a structure such as a vector, I get the following error:

error: no matching function for call to ‘std::vector<Point>::push_back(__gnu_cxx::__normal_iterator<Point*, std::vector<Point> >&)’

I'm just unsure of how to store itp. The end goal is to calculate the distance between a data point and all the other data points within a particular depth window (e.g. 1500m to 2500m). Any help for this novice would be appreciated.


Solution

  • First, of let's make a simple case which will tell your problem:

    struct Point { int bathyDepth; }; // this is all, what you need to show    
    
    int main()
    {
        // some points
        Point a{ 1 }, b{ 100 }, c{ 1000 }, d{ 2000 }, e{ 3000 }, f{ 4000 }, g{ 4501 }, h{ 400 }, i{ 1600 }, j{ 2200 }, k{ 700 };
        // one map element
        std::map<std::string, std::vector<Point> > seamap
        { {"Nat", std::vector<Point>{a, b, c, d, e, f, g, h, i, j, k}} };
    
        //Partition basins by depth
        for (auto it2= seamap.begin(); it2!= seamap.end(); ++it2)
        {
            int i = 500; // some range
            auto itp = std::partition(it2->second.begin(), it2->second.end(), [&i](const auto &a) {return a.bathyDepth < i; });    
        }
    
        return 0;
    }
    

    I'm just unsure of how to store itp.

    To store, all you need to know is its type. which is equal to decltype(it2->second)::iterator, as std::partition returns iterator type of the container.

    Since, your map's key_type is std::vector<Point>, it is equal to std::vector<Point>::iterator

    You can test it programamically:

    if (std::is_same<decltype(it2->second)::iterator, decltype(itp)>::value)
       std::cout << "Same type";
    

    That means you can store itp in

    using itpType = std::vector<Point>::iterator;
    std::vector<itpType> itpVec;
    // or any other containers, with itpType
    

    The end-goal is to calculate the distance between a data point and all the other data points within a particular depth window (e.g. 1500 to 2500m).

    If so, you need to simply sort the map's values(std::vector<Point>) according to the bathyDepth and iterate through it to find the required range. When you use std::partition, inside this loop

    for (int i = 500; i<=4500; i+=500 )
    

    the end effect/ result is the same as sorting at once, but you do it step by step. Also, note that to get a proper result using std::partition, you need a sorted std::vector<Point>.

    For instance, see anexample code here, which will print the range as you mentioned.

    #include <iostream>
    #include <vector>
    #include <string>
    #include <map>
    #include <algorithm>
    
    struct Point
    {
        int bathyDepth;
        // provide a operator< for std::sort()
        bool operator<(const Point &rhs)const { return this->bathyDepth < rhs.bathyDepth; }
    };
    // overloaded  << operator for printing #bathyDepth
    std::ostream& operator<<(std::ostream &out, const Point &point) { return out << point.bathyDepth; }
    
    //function for printing/ acceing the range
    void printRange(const std::vector<Point>& vec, const int rangeStart, const int rangeEnd)
    {
        for (const Point& element : vec)
        {
            if (rangeStart <= element.bathyDepth && element.bathyDepth < rangeEnd) std::cout << element << " ";
            else if (element.bathyDepth > rangeEnd) break; // no need for further checking
        }
        std::cout << "\n";
    }
    
    int main()
    {
        Point a{ 1 }, b{ 100 }, c{ 1000 }, d{ 2000 }, e{ 3000 }, f{ 4000 }, g{ 4501 }, h{ 400 }, i{ 1600 }, j{ 2200 }, k{ 700 };
        std::map<std::string, std::vector<Point> > seamap
        { {"Nat", std::vector<Point>{a, b, c, d, e, f, g, h, i, j, k}} };
    
        for (auto it2 = seamap.begin(); it2 != seamap.end(); ++it2)
        {
            // sort it
            std::sort(it2->second.begin(), it2->second.end());
            //Partition basins by depth
            for (int i = 0; i < 4500; i += 500)  printRange(it2->second, i, i + 500);
        }
        return 0;
    }
    

    Output:

    1 100 400 
    700 
    1000 
    1600 
    2000 2200 
                          // no elements in this range
    3000 
                          // no elements in this range
    4000