简体   繁体   中英

splitting data of a std::map into multiple std::vectors

I have two classes: InitTable and InitEntry. InitTable contains a std::map table which stores the ID of an entry (car), and on object ( InitEntry ) which represents that car. Every InitEntry has 3 member variables:

  1. std::string ID
  2. double speed
  3. std::string heading

My goal is to first store all the cars in the std::map table , and then iterate through that data structure, and try to organize the cars into clusters ( std::vector<InitEntry> ) based on common properties: speed & heading .

Example:

Lets assume we have 6 cars (ids 0 to 8)

  • car0: id: "0", speed: 22, heading "N"
  • car1: id: "1", speed: 26, heading "N"
  • car2: id: "2", speed: 28, heading "W"
  • car3: id: "3", speed: 12, heading "E"
  • car4: id: "4", speed: 10, heading "E"
  • car5: id: "5", speed: 45, heading "S"

To keep it simple, at this stage for me it would be enough to group the cars only based on heading. And the result would be:

std::vector clus1 = {car0, car1}
std::vector clus2 = {car2}
std::vector clus3 = {car3, car4}
std::vector clus4 = {car5}

Unfortunately, I do not have enough knowledge of C++ STL to be able to understand how to achieve this in C++.


InitTable.h:

    #include <InitEntry.h>


    class InitTable {
        public:
            InitTable();
            virtual ~InitTable();

            void clearTable();
            void addEntry(std::string ID, double speed, std::string heading);
            void deleteEntry(std::string ID);
            InitEntry* getEntry(std::string ID);


        protected:
            std::map<std::string, InitEntry*> table;
    };

InitTable.cc:

#include"InitTable.h"

InitTable::InitTable(){}


InitTable::~InitTable()
{
    clearTable();
}


void InitTable::clearTable()
{
    this->table.clear();
}

void InitTable::addEntry(std::string ID, double speed, std::string heading)
{
    InitEntry* newEntry = new InitEntry(ID, speed, heading);


    std::cout<< "server::InitTable: vehicle registered to the init table" << newEntry << endl;

    table.insert(std::make_pair(ID, newEntry));

}



void InitTable::deleteEntry(std::string ID)
{
    InitEntry* ie = getEntry(ID);
    if (ie != NULL)
    {
        table.erase(ID);
        delete ie;
    }
}

InitEntry* InitTable::getEntry(std::string ID)
{
    std::map<std::string, InitEntry*>::iterator it = table.find(ID);

    if (it != table.end())
    {
        return it->second;
    }
    else
    {
        std::cout << "such entry does not exist" << endl;
        return NULL;
    }
}

InitEntry.h:

class InitEntry {
    public:
        virtual ~InitEntry();
        InitEntry(std::string ID, double speed, std::string heading);
        std::string getID();


    protected:
        std::string sumoID;
        double speed;
        std::string heading;

};

InitEntry.cc:

#include "InitEntry.h"


InitEntry::InitEntry(std::string ID, double speed, std::string heading): ID(ID), speed(speed), heading(heading){}


InitEntry::~InitEntry(){}

std::string InitEntry::getID()
{
    return this->ID;
}

EDIT 1: adding extra description (by request of @TomaszLewowski).

Yes, my goal would be to organize the vehicles in clusters, by the heading, and then based on the speed. So initially there would be one big cluster of vehicles going on a certain direction, which later would need to be split into more clusters, based on speed. Lets say: vehicles heading "north", with speed: 0 - 20... with speed speed: 40 - 50...etc

Consider changing your std::map<std::string, InitEntry*> table; to:

std::vector<InitEntry> table;

You would then need to change your *Entry methods to:

void InitTable::addEntry(std::string ID, double speed, std::string heading)
{
    table.push_back(InitEntry(ID, speed, heading));
    std::cout<< "server::InitTable: vehicle registered to the init table" << table.back() << endl;
}

void InitTable::deleteEntry(std::string ID)
{
    auto it = remove_if(table.begin(), table.end(), [&](const auto& i){return i.getID() == ID;});
    table.resize(distance(table.begin(), it));
}

InitEntry* InitTable::getEntry(std::string ID)
{
    auto it = find_if(table.begin(), table.end(), [&](const auto& i){return i.getID() == ID;});

    if (it != table.end())
    {
        return it->second;
    }
    else
    {
        std::cout << "such entry does not exist" << endl;
        return NULL;
    }
}    

To sort table you would need to decide what to sort by.

  • To sort by ID: std::sort(table.begin(), table.end(), [](const auto& first, const auto& second){return first.getID() < second.getID();});
  • To sort by speed: std::sort(table.begin(), table.end(), [](const auto& first, const auto& second){return first.getSpeed() < second.getSpeed();});
  • To sort by heading: std::sort(table.begin(), table.end(), [](const auto& first, const auto& second){return first.getHeading() < second.getHeading();});

Obviously you'd need to add the getters to make these work.

If you don't want to iterate over all elements (and I guess you don't), then only option is to keep these groups somewhere, and only some indices to them in the main table. This would look roughly like that:

#include <string>
#include <vector>
#include <map>
#include <cassert>
#include <algorithm>

typedef std::string ID;
typedef std::string Heading;
typedef double Speed;

struct Entry
{
    ID id;
    Speed speed;
    Heading heading;
};

class EntryProxy
{
public:
    EntryProxy(Entry* target) : entry(target)
    {}

    ID getId()
    {
        return entry->id;
    }

    Speed getSpeed()
    {
        return entry->speed;
    }

    Heading getHeading()
    {
        return entry->heading;
    }

private:
    Entry* entry;
};

class InitTable
{
public:
    const std::vector<EntryProxy>& getSameHeading(std::string id)
    {
        return groupedByHeadings.at(entries.at(id).heading);
    }

    const std::vector<EntryProxy>& getSimilarSpeed(std::string id)
    {
        return groupedBySpeed.at(calculateSpeedGroupIndex(entries.at(id).speed));
    }

    void addEntry(ID id, Speed speed, Heading heading)
    {
        Entry e{ id, speed, heading };
        auto record = entries.insert(std::make_pair(id, e)); // pair<iterator, bool>

        groupedByHeadings[record.first->second.heading].push_back(EntryProxy(&record.first->second));
        groupedBySpeed[calculateSpeedGroupIndex(record.first->second.speed)].push_back(EntryProxy(&record.first->second));
    }

    void deleteEntry(ID id)
    {
        auto entry = entries.find(id);
        assert(entry != entries.end());

        auto currentEntryHeadings = groupedByHeadings[entry->second.heading];
        currentEntryHeadings.erase(std::find_if(currentEntryHeadings.begin(), currentEntryHeadings.end(), [&entry](EntryProxy p){return p.getId() == entry->second.id; }));

        auto currentEntrySpeeds = groupedBySpeed[calculateSpeedGroupIndex(entry->second.speed)];
        currentEntrySpeeds.erase(std::find_if(currentEntrySpeeds.begin(), currentEntrySpeeds.end(), [&entry](EntryProxy p){return p.getId() == entry->second.id; }));

        entries.erase(id);
    }

    EntryProxy getEntry(ID id)
    {
        return EntryProxy(&entries.at(id));
    }

private:
    typedef int SpeedGroupIndex;

    SpeedGroupIndex calculateSpeedGroupIndex(Speed s)
    {
        // you may prefer a different implementation here

        return s / 10;
    }

    std::map<ID, Entry> entries;
    std::map<Heading, std::vector<EntryProxy> > groupedByHeadings;
    std::map<SpeedGroupIndex, std::vector<EntryProxy> > groupedBySpeed;
};

Grouping by Heading is pretty obvious, as it requires only a simple map. However, when it comes to speed it's a little tougher - you need to describe requirements for grouping (by implementing function getSpeedGroupId ), as doubles shouldn't be used as map indices.

In case you prefer a more safe approach towards memory management you can substitute Entry* for std::shared_ptr<Entry> in EntryProxy and Entry for std::shared_ptr<Entry> in InitTable::entries . In that case you would also need to change creation from Entry e{id, speed, heading to auto e = std::make_shared<Entry>(id, speed, heading) .

If deleting is too complex for you, you may keep EntryProxy structures in a list, and a reference to specific iterators in entries map.

You may also not like the fact that groups are EntryProxy and single elements are Entry - in such case you may make getEntry also return EntryProxy .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM