6

(This is a follow-on to Sum vector with range-v3)

If I have two (or more) vectors, I can zip them together with range-v3 like this:

std::vector< int > v1{1,1,1};
std::vector< int > v2{2,2,2};

auto v = ranges::views::zip( v1, v2 )
  | ranges::views::transform( ... );

This works well, but in practice, I don't have explicit vectors, but I do have a vector of vectors. I'd like to do the following, but it doesn't give the same result. (In fact, I'm not sure what the result is, and I don't know how to determine what the result is!)


std::vector< std::vector< int > > V{{1,1,1},{2,2,2}};

auto vV = ranges::views::zip( V )
  | ranges::views::transform( ... );

What can I do to zip a vector< vector > like I did to zip a few explicit vectors? I've tried using join along with stride, chunk, etc. but haven't found the magic combination.

jlconlin
  • 14,206
  • 22
  • 72
  • 105
  • btw, if you want to zip the fields of external `vector` it probably should not be vector but an `array` or `tuple` – bartop May 15 '20 at 12:54
  • The problem is I don't know how many sub-vectors there will be at compile time. I'm building up my vector (perhaps inefficiently) using `push_back` every time a new sub-vector is generated. – jlconlin May 15 '20 at 12:59
  • 2
    then there is no simple answer to your problem since zip has to know the external `vector` size in compile – bartop May 15 '20 at 13:13

4 Answers4

3

ranges::views::zip( V ) zips only one vector, not its content. (similarly to std::make_tuple(v) which does std::tuple<vector<int>>).

Issue is that vector has runtime size, so construct a tuple like from its contents requires some help:

template <std::size_t ...Is, typename T>
auto zip_vector(std::index_sequence<Is...>, std::vector<std::vector<T>>& v)
{
    assert(N <= v.size());
    return ranges::views::zip(v[Is]...);
}

template <std::size_t N, typename T>
auto zip_vector(std::vector<std::vector<T>>& v)
{
    return zip_vector(std::make_index_sequence<N>(), v);
}

And then:

std::vector< std::vector< int > > V{{1,1,1},{2,2,2}};

auto vV = zip_vector<2>( V )
  | ranges::views::transform( ... );
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

I suppose if you don't know the size of external vector in compile time the only reasonable solution that remains is work around it. Instead of trying to zip it I would suggest going with accumulate on top of that as it seems more versatile in this situation.

std::vector< std::vector< int > > V{{1,1,1},{2,2,2}};

auto vV = ranges::accumulate(
    V,
    std::vector<ResultType>(V[0].size()),
    [](const auto& acc, const auto& next) {
        auto range = ranges::views::zip(acc, next) | ranges::views::transform(...);
        return std::vector<int>(range.begin(), range.end());
    }
)

EDIT: I forgot the range has to be copied.

bartop
  • 9,971
  • 1
  • 23
  • 54
  • Thanks for that suggestion. I like the idea, but I'm struggling to [implement it](https://wandbox.org/permlink/H7P18q03TV8Pz4Wt). I'm not as familiar with `accumulate` so I'm not sure what the issue is. – jlconlin May 15 '20 at 13:55
  • 1
    @jlconlin BTW also take a look at `views::transpose`, it might suit you even more – bartop May 15 '20 at 14:14
  • Oh, good point. I hadn't thought of that. I love the range-v3 library, but there is just so much there that I simply don't even know what to ask. Did transpose make it into the C++20 standard? – jlconlin May 15 '20 at 14:33
  • @jlconlin don't know it honestly, you have to just check latest draft – bartop May 15 '20 at 14:38
  • @bartop I don't think there is a `views::transpose` in range-v3. Could you point me to it? – cigien May 15 '20 at 16:43
1

Based on the question you linked to, I think this particular question is an XY problem, i.e. there is no reason to involve zip to solve this problem, other than building upon the previous solution. While zip might have been a reasonable approach when the number of ranges was known at compile time, it just gets in the way when the number is only known at run-time.

So given that you have a vector<vector<int>>, where similar to the previous question, all the internal vectors are the same size, here's how I would write it in range-v3:

namespace rv = ranges::views;  

std::vector<std::vector<int>> v{{1,2,3},{4,5,6}};

int n = v.size();
int k = v[0].size();

auto vs = v | rv::join;

auto s = rv::iota(0, n + 1) 
       | rv::transform([=](int i){
           return ranges::accumulate(
                    vs | rv::drop(i) | rv::stride(k), 0);
         });

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
1

Zipping vector<vector<T>> is something you would want to do because it allows you to transpose these ranges.

The other answers sufficiently explain why you cant zip vector<vector<T>>.

However, you could write an explicit transpose adaptor as Eric Niebler did for his talk demonstrating range-v3

However, this is an expensive adaptor and expensive adaptors get ignored https://github.com/ericniebler/range-v3/issues/776

Tom Huntington
  • 2,260
  • 10
  • 20