c++range-v3

How can I insert a linebreak every 20 strings using ranges v3?


I have a vector of OpenCV points and want to print 20 of them per line.

The following code works, but I don't like that it needs to create intermediate vectors.

const std::vector<cv::Point> points { .... };

fmt::print("20 points per line:\n{}",
           points | ranges::views::transform([](const auto& point) {
                      return fmt::format("({}, {})", point.x, point.y);
                    }) 
                  | ranges::views::chunk(20)
                  | ranges::views::transform([](const auto& chunk) {
                      // really ugly
                      return chunk | ranges::to<std::vector<std::string>>;
                    })
                  // but how can I do this otherwise?
                  | ranges::views::intersperse(std::vector<std::string>{"\n"})
                  | ranges::views::join 
                  | ranges::views::join 
                  | ranges::to<std::string>);

Can I somehow intersperse a linebreak every 20 strings by directly specifying that this is supposed to be a range of the same type, or otherwise get around the intermediate to<vector<string>>?

What doesn't work, for reasons I don't understand, is just using

| ranges::views::intersperse(ranges::view::single(std::string{"\n"}))

directly after the chunk.

Run on CompilerExplorer


Solution

  • You can use concat view instead.

    https://godbolt.org/z/WYoqr1z9G

        std::string result = points 
            | ranges::views::transform([](auto&& point) {
                return fmt::format("({}, {})"sv, point.x, point.y);
            })
            | ranges::views::chunk(5)
            | ranges::views::transform([](auto&& chunk) {
                return ranges::views::concat(chunk | ranges::views::join, "\n"sv);
            })
            | ranges::views::join
            | ranges::to<std::string>;
    

    EDIT

    As in what I was mentioning in comment, is about C++23 implementation of interparse -- join_with; godbolt

        auto result = points 
            | ranges::views::transform([](auto&& point) {
                return fmt::format("({}, {})"sv, point.x, point.y);
            })
            | ranges::views::chunk(5)
            | ranges::views::transform([](auto&& chunk) {
                return chunk | ranges::views::join | ranges::to<std::string>();
            })
            | ranges::views::join_with("\n"sv)
            | ranges::to<std::string>();
    

    If someone knows how to flatten a chunk_view to string_view compatible without allocating additional string copies that is much appreciated.