16

I have vector<vector<int> > Y. I want to merge the sub-vectors (call it y) inside Y into one vector<int>. But I don't want to sort them,i.e, merge them in the order of their occurence. How would I do this efficiently, perhaps by using STL algorithms? The std::merge method merges by sorting which I don't want.

Edits: What I want is: given {{1,6,5},{5,3-1,77},{0},...} return {1,6,5,5,3,-1,77,0,...}

Simple Lime
  • 10,790
  • 2
  • 17
  • 32
Utkrist Adhikari
  • 1,052
  • 3
  • 15
  • 42
  • `std::merge` is a sorting algorithm, so what would you expect. I really don't understand your question; you have a bunch of vectors and you want to merge them all? Is that it? – nullpotent Jun 25 '13 at 10:18
  • What does *in the order of their occurence* mean? Is it the order of the occurence of the vectors or of the elements in each vector? As an exemple, if you have `{1,2,3,4}` and `{2,9, 4, 8}`, do you want `{1,2,3,4,9,8}` or do you want `{1,2,3,9,4,8}` ? – rectummelancolique Jun 25 '13 at 10:50
  • What I want is: given {{1,6,5},{5,3-1,77},{0},...} return {1,6,5,5,3,-1,77,0,...} – Utkrist Adhikari Jun 25 '13 at 11:01
  • that's not merging, that's collapsing or flattening as Joe said above. follow his link. – rectummelancolique Jun 25 '13 at 11:06
  • just this ``` for(auto& sub : all) accum.insert(end(accum), begin(sub), end(sub));``` – imbr Aug 29 '20 at 14:04

2 Answers2

20

The word for this is concatenation or flatten:

std::vector<int> a { 1,2,3 },
                 b {9,0,-7};

std::vector<int> c(begin(a), end(a));
c.insert(end(c), begin(b), end(b));

Or, indeed simpler:

auto c = a;
c.insert(end(c), begin(b), end(b));

c now contains 1,2,3,9,0,-7

You can generalize this to handle your nested container case:

template <template<typename...> class R=std::vector, 
          typename Top, 
          typename Sub = typename Top::value_type> 
   R<typename Sub::value_type> flatten(Top const& all)
{
    using std::begin;
    using std::end;

    R<typename Sub::value_type> accum;

    for(auto& sub : all)
        accum.insert(end(accum), begin(sub), end(sub));

    return accum;
}

If you wanted to move elements from the first container to the last (in case the elements are moveable only, or expensive to copy) use std::move with std::back_inserter or apply std::make_move_operator to each source iterator.

See it live on Coliru

Update: Variadic concatenation

Initially, I had expected you to be after a variadic solution: let me demonstrate how you can make this a lot more general, so you can say:

 auto x = to_vector(std::vector<int> { 1,2,3 }, std::list<int> { 9,8,11 }, std::set<int> { 42 });

In fact, I made it so general, that you concatenate heterogeneous collections into "arbitrary" containers:

// fun with maps:
auto y = concatenate<std::map<long, std::string> >(
        std::map<int,      const char*> { { 1, "one" }, { 2, "two" } },
        std::map<unsigned, std::string> { { 1, "one" }, { 3, "three" } }            
    );

You'll (correctly) expect that to_vector is just a convenience short-hand for concatenate<std::vector<...>>. Here's the full monty, see it live on Coliru and ideone:

#include <vector>
#include <utility>
#include <iterator>

namespace detail
{
    template <typename R>
    void do_concatenation(R& accum) {}

    template <typename R, typename First, typename... More> 
    void do_concatenation(R& accum, First const& first, More const&... more)
    {
        using std::begin;
        using std::end;
        std::copy(begin(first), end(first), std::inserter(accum, end(accum)));

        do_concatenation(accum, more...);
    }
}

template <typename Result, typename... Containers> 
   Result concatenate(Containers const&... containers)
{
    Result accum;
    detail::do_concatenation(accum, containers...);
    return accum;
}

template <typename First, typename... More> 
   std::vector<typename First::value_type> to_vector(First const& first, More const&... containers)
{
    return concatenate<std::vector<typename First::value_type>>(first, containers...);
}

/// demo
#include <set>
#include <list>
#include <iostream>

#include <map>
#include <string>

int main()
{
    auto x = to_vector(std::vector<int> { 1,2,3 }, std::list<int> { 9,8,11 }, std::set<int> { 42 });

    for (auto i : x)
       std::cout << i << " ";

    std::cout << std::endl;

    // fun with maps:
    auto y = concatenate<std::map<long, std::string> >(
            std::map<int,      const char*> { { 1, "one" }, { 2, "two" } },
            std::map<unsigned, std::string> { { 1, "one" }, { 3, "three" } }            
        );

    for (auto kvp : y)
       std::cout << "(" << kvp.first  << ", " << kvp.second << ")";

}

Output:

1 2 3 9 8 11 42 
(1, one)(2, two)(3, three)
sehe
  • 374,641
  • 47
  • 450
  • 633
  • That’s not quite the same. You’ve solved the case for N=2 but OP is asking for a general algorithm. – Konrad Rudolph Jun 25 '13 at 10:40
  • @Konrad Rudolph is right. – Utkrist Adhikari Jun 25 '13 at 11:03
  • Okay, successfully trolled me into supplying the overkill answer too: see it live on **[Coliru](http://coliru.stacked-crooked.com/view?id=27a1b38f8110bb384a8187513af05d17-542192d2d8aca3c820c7acc656fa0c68)** and **[ideone](http://ideone.com/8dEy87)** – sehe Jun 25 '13 at 13:01
  • And in case you **really** wanted a `flatten(...)` of `set>` and similar: http://coliru.stacked-crooked.com/view?id=515be1c72dfa8647cb8fd559b599321b-542192d2d8aca3c820c7acc656fa0c68 (updated the question with it too) – sehe Jun 25 '13 at 13:17
  • This adds the sub-elements one by one, which is not ideal. – Sebastian Redl Jun 25 '13 at 14:03
  • 1
    I really learnt a lot from your code. I really liked the generality that encompasses arbitrary containers. – Utkrist Adhikari Jun 25 '13 at 14:14
  • @SebastianRedl granted. I fixed the low-hanging fruit. The variadic version will require a trait to do efficient insertion for varying container types. I might do so later :) – sehe Jun 25 '13 at 15:24
  • The OP was asking about a vector of vectors... not concatenating two seperate vectors. In vanilla C, a 2D array was contiguous, so if you have a pointer to the first element, it is already flat. In C++ with a vector of vector, I think you have to iterate through the 2D structure and make a copy. I am hoping there is a better way though... – CraigDavid May 01 '20 at 19:52
16
template <typename T>
std::vector<T> flatten(const std::vector<std::vector<T>>& v) {
    std::size_t total_size = 0;
    for (const auto& sub : v)
        total_size += sub.size(); // I wish there was a transform_accumulate
    std::vector<T> result;
    result.reserve(total_size);
    for (const auto& sub : v)
        result.insert(result.end(), sub.begin(), sub.end());
    return result;
}

Compared to @not-sehe's solution, this has a few performance advantages:

  • It calculates the final size of the result and preallocates storage. This means there is no need for repeated reallocation.
  • It uses range-insert instead of single-element insert as the other version does. This allows the container to apply some optimizations, especially if the elements are trivially copyable (i.e. a single memcpy per inner container).

On the downside, it only works with vector, not deque or list.

Community
  • 1
  • 1
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • 3
    To address the comment in the code: `transform_reduce` is [coming in C++17](http://en.cppreference.com/w/cpp/algorithm/transform_reduce) – wrhall Feb 23 '17 at 20:21