1

I have seen this discussion (Concatenating two std::vectors) but it concerns combining (as in moving) two std::vector arrays.


I have three std::vectors and I am using C++17:

  1. m_mapHist[m_eHistAssign][strName]
  2. m_mapScheduleHist[m_eHistAssign][strName]
  3. m_mapScheduleFutureHist[m_eHistAssign][strName]

Each vector is of type std::vector<COleDateTime>. I don't want to change these vectors. Instead, I want to combine them (copy I guess) into a temporary single vector, so I can pass just the one vector to another class for processing.

At the moment I am doing this manually through iteration:

std::vector<COleDateTime> vecAssignmentDate;

// Past items from the history database
if (m_mapHist[m_eHistAssign].find(strName) != m_mapHist[m_eHistAssign].end())
{
    for (const auto& historyItemDate : m_mapHist[m_eHistAssign][strName])
    {
        vecAssignmentDate.push_back(historyItemDate);
    }
}

// Past items on the active schedule
if (m_mapScheduleHist[m_eHistAssign].find(strName) != m_mapScheduleHist[m_eHistAssign].end())
{
    for (const auto& historyItemDate : m_mapScheduleHist[m_eHistAssign][strName])
    {
        vecAssignmentDate.push_back(historyItemDate);
    }
}

// Future items (both on the active schedule and in the history database)
if (m_mapScheduleFutureHist[m_eHistAssign].find(strName) != m_mapScheduleFutureHist[m_eHistAssign].end())
{
    for(const auto &historyItemDate : m_mapScheduleFutureHist[m_eHistAssign][strName])
    {
        vecAssignmentDate.push_back(historyItemDate);
    }
}

Is there an easier way to create this temporary vector?

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 3
    The link you provided already has decent answers. `std::copy` and `std::back_inserter` seems appropriate with prior `std::vector::reserve` if you will. Example: `std::copy(a.begin(),a.end(),std::back_inserter(combined);` – Staz Nov 07 '21 at 12:34
  • You say "moving", but will a move actually work for you? Or do you need to preserve the original 3 separate vectors? If you don't care about preserving the 3 originals, then a move will be by far the most efficient way of doing this. However, it would likely be even faster to do nothing. This would mean adjusting the algorithm you're trying to call so that it can accept 3 different iterator pairs, rather than a single pair for a single vector. – Cody Gray - on strike Nov 07 '21 at 12:36
  • @CodyGray I don't want to move. I state I want to leave the original 3 intact. I am trying `std::copy`. – Andrew Truckle Nov 07 '21 at 12:37
  • @Staz I confirm that using `std::copy` works OK. Thanks. Now checking out some of the answer answers. – Andrew Truckle Nov 07 '21 at 12:42
  • 2
    How much data is in there? Note that `std::vector` is very slow in debug mode, and very fast in release mode. I wonder if the slow debug speed is the motivation for this question. – Barmak Shemirani Nov 07 '21 at 12:54
  • @BarmakShemirani Not a huge amount of data. Future list of dates will relistically be low. And the past data is limited to a couple of years. So it shouldn't be huge. My movitivation for the question was simplification of my code visually. – Andrew Truckle Nov 07 '21 at 12:57

3 Answers3

2

You may use boost::join. Example:

std::vector<COleDateTime> v1;
std::vector<COleDateTime> v2;
std::vector<COleDateTime> v3;
std::vector<COleDateTime> result;

result = boost::join(boost::join(v1, v2), v3);

Since c++17 the standard also have a std::merge util function:

std::vector<COleDateTime> v1;
std::vector<COleDateTime> v2;
std::vector<COleDateTime> v3;
std::vector<COleDateTime> result;

std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
std::merge(v3.begin(), v3.end(), result.begin(), result.end(), std::back_inserter(result));

Differences between std::merge and std::copy

from cplusplus.com/reference/algorithm/merge/ std::merge:

Combines the elements in the sorted ranges [first1,last1) and [first2,last2), into a new range beginning at result with all its elements sorted.

from cplusplus.com/reference/algorithm/copy/ std::copy:

Copies the elements in the range [first,last) into the range beginning at result..

So it depends on what you want to achieve.

loic.lopez
  • 2,013
  • 2
  • 21
  • 42
  • Thanks, I do have boost so I could try this. But the cavaet is that the `std::vector` might not be available (as a result of the `find`). – Andrew Truckle Nov 07 '21 at 12:34
  • 1
    I think you can call the joins separately in your conditions – loic.lopez Nov 07 '21 at 12:37
  • 1
    I have also added the c++ standard version, `std::merge` is an option but not only you can use `std::copy` – loic.lopez Nov 07 '21 at 12:44
  • I have used `std::copy`. I guess the question now is - what is the pros / cons of using `std::copy` v `std::merge`? – Andrew Truckle Nov 07 '21 at 12:45
  • 1
    from [cplusplus.com/reference/algorithm/merge/](https://www.cplusplus.com/reference/algorithm/merge/) `std::merge` : *Combines the elements in the sorted ranges [first1,last1) and [first2,last2), into a new range beginning at result with all its elements sorted.* and `std::copy`: *Copies the elements in the range [first,last) into the range beginning at result.*. So it depends on what you want to achieve. – loic.lopez Nov 07 '21 at 12:49
  • Technically I don't need to do this as they are already in date ascending order and we are piggy backing them. – Andrew Truckle Nov 07 '21 at 12:49
1

Using a standard library, you can do this. BTW, I have not tested the code. It could contain errors.

#include <algorithm>
template<typename T, typename U, typename ...Args>
std::vector<COleDateTime> combine(T key1, U key2, Args ...arg) {
    std::vector<COleDateTime> ret;
    for (const auto& v : {arg...}) {
        if (v[key1].find(key2) != v[key1].cend()) {
            std::copy(v[key1][key2].begin(), v[key1][key2].end(), std::back_inserter(ret));
        }
    }
    return ret;
}
Murat Hepeyiler
  • 430
  • 3
  • 12
1

What's wrong with a simple copy assignment?

const std::vector<COleDateTime>& v1;
const std::vector<COleDateTime>& v2;
const std::vector<COleDateTime>& v3;
std::vector<COleDateTime> result;

result.reserve(v1.size() + v2.size() + v3.size());
for(const std::vector<COleDateTime>* vec: {&v1, &v2, &v3})
    result.insert(result.end(), vec->begin(), vec->end());

Since your input vectors may not exist, here is a version that accounts for this:

using vector_type = std::vector<COleDateTime>;
using map_type = std::map<std::string, vector_type>;
auto find_or_null = [](const map_type& map, const std::string& key) noexcept
      -> const vector_type* {
    map_type::const_iterator found = map.find(key);
    return found == map.end() ? nullptr : &found->second;
};
const auto vecs = { find_or_null(n_mapHist, strName),
                    find_or_null(m_mapScheduleHist, strName),
                    find_or_null(m_mapScheduleFutureHist, strName)
};
std::size_t size = 0;
for(const vector_type* vec: vecs)
    if(vec)
        size += vec->size();
vector_type result;
result.reserve(size);
for(const vector_type* vec: vecs)
    if(vec)
        result.insert(result.end(), vec->begin(), vec->end());
Homer512
  • 9,144
  • 2
  • 8
  • 25
  • Thanks for the suggestion. But this does not take into account that one or more of teh vectors might not exist. Notice that I have to do an `if` test first. – Andrew Truckle Nov 07 '21 at 12:43