Home > OS >  Function which outputs several std::collections to CSV / row first iteration
Function which outputs several std::collections to CSV / row first iteration

Time:11-15

I want to write a function with the following signature (or similar):

template <typename... Ts>
void collection_to_csv(const std::string filepath, const Ts& ... containers);

The function should write a csv file to a file located at filepath, where containers can be any number of iteratable containers from the STD and would define the columns of the outputted CSV file. E.g. I could use it like this:

std::vector<int> column1{1, 2, 3, 4};
std::list<float> column2{1.5, 2.5, 3.5, 4.5};
collections_to_csv(someFilePath, c1, c2);

Is this possible? So far I've been trying to use variadic templates like so:

    template <typename... Ts>
    void collection_to_csv(const std::string filepath, std::initializer_list<std::string> headers, const Ts& ... containers)
    {
        std::ofstream file;
        file.open(filepath, std::ofstream::out | std::ofstream::trunc);

        for (std::size_t irow=0; irow < size;   irow)
        {
            for (const auto& container : containers) //But how to iterate through the variadic arguments when they could be different types?
            {
                file << container[i] << ",";
            }
        }

        file.flush();
        file.close();
    }

Everything I've seen about variadic templates suggests they're 'iterated' using recursion but this would force a 'column first' iteration order. I need a 'row first' order, making me think variadic templates might be the wrong tool here. Is there a better approach?

CodePudding user response:

Using a helper function with std::index_sequence and unpacking on a lambda expression you can get it to work like this:

#include <cstddef>
#include <iostream>
#include <list>
#include <tuple>
#include <utility>
#include <vector>

template <typename... TIters, std::size_t... Indices>
void collections_to_csv_helper(std::tuple<TIters...> begins,
                               std::tuple<TIters...> ends,
                               std::index_sequence<Indices...>) {
    auto currents = begins;
    while (((std::get<Indices>(currents) != std::get<Indices>(ends)) && ...)) {
        (([&] {
             auto& current = std::get<Indices>(currents);
             std::cout << *current << '\t';
             current  ;
         }()),
         ...);

        std::cout << '\n';
    }
}

template <typename... Containers>
void collections_to_csv(const Containers&... containers) {
    collections_to_csv_helper(std::tuple{containers.begin()...},
                              std::tuple{containers.end()...},
                              std::index_sequence_for<Containers...>{});
}

int main() {
    std::vector<int> column1{1, 2, 3, 4};
    std::list<float> column2{1.5, 2.5, 3.5, 4.5, 5.5};
    collections_to_csv(column1, column2);
}

Output:

1   1.5 
2   2.5 
3   3.5 
4   4.5 

Note, that it goes up to the shortest list and ignores the rest of the other lists.

Alternatively there are also some non-standard zip methods that can zip multiple collections in a single iterator in e.g. boost and some other libraries.

CodePudding user response:

You can use std::tuple to store the iterator of each containers and then use std::apply to increment them one by one to do this.

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <list>

template <typename... Ts>
void collection_to_csv(const Ts&... containers) {
  const auto ncol = [](const auto& first, const auto&...) { 
    return first.size(); 
  }(containers...);

  auto iters = std::make_tuple(containers.begin()...);

  for (std::size_t icol = 0; icol < ncol;   icol) {
    std::apply(
      [&](auto&... iter) { 
        ((std::cout << *iter   << ","), ...); 
    }, iters);
    std::cout << "\n";
  }
}

int main() {
  std::vector column1{1, 2, 3, 4};
  std::list column2{1.5, 2.5, 3.5, 4.5};
  collection_to_csv(column1, column2);
}

Demo.

Thanks to the introduction of C 23 views::zip, collection_to_csv can be implemented more simply just using views::zip, that is, zip all containers and iterate their elements one by one.

#include <ranges>
#include <tuple>

template <typename... Ts>
void collection_to_csv(const Ts&... containers) {
  for (const auto& values : std::ranges::zip_view(containers...)) {
    std::apply(
      [](const auto&... value) { ((std::cout << value << ","), ...); }, values);
    std::cout << "\n";
  }
}

Demo.

  • Related