Как лучше всего записать std::vector < std::строка > контейнер для набора данных HDF5?

StackOverflow https://stackoverflow.com/questions/581209

  •  06-09-2019
  •  | 
  •  

Вопрос

Учитывая вектор строк, каков наилучший способ записать их в набор данных HDF5?На данный момент я делаю что-то вроде следующего:

  const unsigned int MaxStrLength = 512;

  struct TempContainer {
    char string[MaxStrLength];
  };

  void writeVector (hid_t group, std::vector<std::string> const & v)
  {
    //
    // Firstly copy the contents of the vector into a temporary container
    std::vector<TempContainer> tc;
    for (std::vector<std::string>::const_iterator i = v.begin ()
                                              , end = v.end ()
      ; i != end
      ; ++i)
    {
      TempContainer t;
      strncpy (t.string, i->c_str (), MaxStrLength);
      tc.push_back (t);
    }


    //
    // Write the temporary container to a dataset
    hsize_t     dims[] = { tc.size () } ;
    hid_t dataspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                               , dims
                               , NULL);

    hid_t strtype = H5Tcopy (H5T_C_S1);
    H5Tset_size (strtype, MaxStrLength);

    hid_t datatype = H5Tcreate (H5T_COMPOUND, sizeof (TempConainer));
    H5Tinsert (datatype
      , "string"
      , HOFFSET(TempContainer, string)
      , strtype);

    hid_t dataset = H5Dcreate1 (group
                          , "files"
                          , datatype
                          , dataspace
                          , H5P_DEFAULT);

    H5Dwrite (dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tc[0] );

    H5Dclose (dataset);
    H5Sclose (dataspace);
    H5Tclose (strtype);
    H5Tclose (datatype);
}

Как минимум, я бы действительно хотел изменить вышесказанное так, чтобы:

  1. Он использует строки переменной длины
  2. Мне не нужен временный контейнер

У меня нет ограничений на то, как я храню данные, так что, например, это не обязательно должен быть СОЕДИНЕНИЕ тип данных, если есть лучший способ сделать это.

Редактировать: Просто чтобы сузить проблему, я относительно знаком с работой с данными на стороне C ++, именно на стороне HDF5 мне нужна большая часть помощи.

Спасибо за вашу помощь.

Это было полезно?

Решение

[Большое спасибо дерзко за его помощь в ответе на этот вопрос.]

Чтобы записать строку переменной длины в HDF5, используйте следующее:

// Create the datatype as follows
hid_t datatype = H5Tcopy (H5T_C_S1);
H5Tset_size (datatype, H5T_VARIABLE);

// 
// Pass the string to be written to H5Dwrite
// using the address of the pointer!
const char * s = v.c_str ();
H5Dwrite (dataset
  , datatype
  , H5S_ALL
  , H5S_ALL
  , H5P_DEFAULT
  , &s );

Одним из решений для написания контейнера является написание каждого элемента по отдельности.Это может быть достигнуто с помощью гиперслэбы.

Например:

class WriteString
{
public:
  WriteString (hid_t dataset, hid_t datatype
      , hid_t dataspace, hid_t memspace)
    : m_dataset (dataset), m_datatype (datatype)
    , m_dataspace (dataspace), m_memspace (memspace)
    , m_pos () {}

private:
  hid_t m_dataset;
  hid_t m_datatype;
  hid_t m_dataspace;
  hid_t m_memspace;
  int m_pos;

//...

public:
  void operator ()(std::vector<std::string>::value_type const & v)
  {
    // Select the file position, 1 record at position 'pos'
    hsize_t count[] = { 1 } ;
    hsize_t offset[] = { m_pos++ } ;
    H5Sselect_hyperslab( m_dataspace
      , H5S_SELECT_SET
      , offset
      , NULL
      , count
      , NULL );

    const char * s = v.c_str ();
    H5Dwrite (m_dataset
      , m_datatype
      , m_memspace
      , m_dataspace
      , H5P_DEFAULT
      , &s );
    }    
};

// ...

void writeVector (hid_t group, std::vector<std::string> const & v)
{
  hsize_t     dims[] = { m_files.size ()  } ;
  hid_t dataspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                                    , dims, NULL);

  dims[0] = 1;
  hid_t memspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                                    , dims, NULL);

  hid_t datatype = H5Tcopy (H5T_C_S1);
  H5Tset_size (datatype, H5T_VARIABLE);

  hid_t dataset = H5Dcreate1 (group, "files", datatype
                             , dataspace, H5P_DEFAULT);

  // 
  // Select the "memory" to be written out - just 1 record.
  hsize_t offset[] = { 0 } ;
  hsize_t count[] = { 1 } ;
  H5Sselect_hyperslab( memspace, H5S_SELECT_SET, offset
                     , NULL, count, NULL );

  std::for_each (v.begin ()
      , v.end ()
      , WriteStrings (dataset, datatype, dataspace, memspace));

  H5Dclose (dataset);
  H5Sclose (dataspace);
  H5Sclose (memspace);
  H5Tclose (datatype);
}      

Другие советы

Вот некоторый рабочий код для написания вектора строк переменной длины с использованием HDF5 c ++ API.

Я включаю некоторые предложения в другие посты:

  1. используйте H5T_C_S1 и H5T_VARIABLE
  2. использование string::c_str() чтобы получить указатели на строки
  3. поместите указатели в vector из char* и перейдите к API HDF5

Это так не обязательно для создания дорогостоящих копий строки (напримерс strdup()). c_str() возвращает указатель на завершающиеся нулем данные базовой строки.Это именно то, для чего предназначена функция.Конечно, строки со встроенными nulls с этим работать не будут...

std::vector гарантированно иметь непрерывное базовое хранилище, поэтому использование vector и vector::data() это то же самое, что использовать необработанные массивы, но, конечно, намного аккуратнее и безопаснее, чем неуклюжий, старомодный способ работы на языке си.

#include "H5Cpp.h"
void write_hdf5(H5::H5File file, const std::string& data_set_name,
                const std::vector<std::string>& strings )
{
    H5::Exception::dontPrint();

    try
    {
        // HDF5 only understands vector of char* :-(
        std::vector<const char*> arr_c_str;
        for (unsigned ii = 0; ii < strings.size(); ++ii) 
            arr_c_str.push_back(strings[ii].c_str());

        //
        //  one dimension
        // 
        hsize_t     str_dimsf[1] {arr_c_str.size()};
        H5::DataSpace   dataspace(1, str_dimsf);

        // Variable length string
        H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
        H5::DataSet str_dataset = file.createDataSet(data_set_name, datatype, dataspace);

        str_dataset.write(arr_c_str.data(), datatype);
    }
    catch (H5::Exception& err)
    {
        throw std::runtime_error(string("HDF5 Error in " ) 
                                    + err.getFuncName()
                                    + ": "
                                    + err.getDetailMsg());


    }
}

Если вы смотрите на более чистый код:Я предлагаю вам создать функтор, который будет принимать строку и сохранять ее в контейнере HDF5 (в желаемом режиме).Ричард, я использовал неправильный алгоритм, пожалуйста, перепроверьте!

std::for_each(v.begin(), v.end(), write_hdf5);

struct hdf5 : public std::unary_function<std::string, void> {
    hdf5() : _dataset(...) {} // initialize the HDF5 db
    ~hdf5() : _dataset(...) {} // close the the HDF5 db
    void operator(std::string& s) {
            // append 
            // use s.c_str() ?
    }
};

Помогает ли это начать работу?

У меня была аналогичная проблема, с оговоркой, что я хотел, чтобы вектор строк хранился как атрибут.Сложность с атрибутами заключается в том, что мы не можем использовать навороченные функции пространства данных, такие как hyperslabs (по крайней мере, с C ++ API).

Но в любом случае может оказаться полезным ввести вектор строк в одну запись в наборе данных (если, например, вы всегда рассчитываете читать их вместе).В этом случае вся магия приходит с Тип, а не с самим пространством данных.

В основном есть 4 шага:

  1. Сделайте vector<const char*> который указывает на строки.
  2. Создать hvl_t структура, которая указывает на вектор и содержит его длину.
  3. Создайте тип данных.Это такой H5::VarLenType упаковка a (переменной длины) H5::StrType.
  4. Напишите hvl_t введите в набор данных.

Действительно приятной частью этого метода является то, что вы заполняете всю запись тем, что HDF5 считает скалярным значением.Это означает, что сделать его атрибутом (а не набором данных) тривиально.

Выберете ли вы это решение или решение, в котором каждая строка находится в отдельной записи набора данных, вероятно, также зависит от желаемой производительности:если вы ищете произвольный доступ к определенным строкам, вероятно, лучше записать строки в dataset, чтобы их можно было проиндексировать.Если вы всегда собираетесь читать их все вместе, это решение может сработать так же хорошо.

Вот краткий пример того, как это сделать, используя C ++ API и простой скалярный набор данных:

#include <vector>
#include <string>
#include "H5Cpp.h"

int main(int argc, char* argv[]) {
  // Part 0: make up some data
  std::vector<std::string> strings;
  for (int iii = 0; iii < 10; iii++) {
    strings.push_back("this is " + std::to_string(iii));
  }

  // Part 1: grab pointers to the chars
  std::vector<const char*> chars;
  for (const auto& str: strings) {
    chars.push_back(str.data());
  }

  // Part 2: create the variable length type
  hvl_t hdf_buffer;
  hdf_buffer.p = chars.data();
  hdf_buffer.len = chars.size();

  // Part 3: create the type
  auto s_type = H5::StrType(H5::PredType::C_S1, H5T_VARIABLE);
  s_type.setCset(H5T_CSET_UTF8); // just for fun, you don't need this
  auto svec_type = H5::VarLenType(&s_type);

  // Part 4: write the output to a scalar dataset
  H5::H5File out_file("vtest.h5", H5F_ACC_EXCL);
  H5::DataSet dataset(
    out_file.createDataSet("the_ds", svec_type, H5S_SCALAR));
  dataset.write(&hdf_buffer, svec_type);

  return 0;
}

Вместо TempContainer вы можете использовать простой std::vector (вы также могли бы шаблонизировать его, чтобы он соответствовал T -> basic_string .Что- то вроде этого:

#include <algorithm>
#include <vector>
#include <string>
#include <functional>

class StringToVector
  : std::unary_function<std::vector<char>, std::string> {
public:
  std::vector<char> operator()(const std::string &s) const {
    // assumes you want a NUL-terminated string
    const char* str = s.c_str();
    std::size_t size = 1 + std::strlen(str);
    // s.size() != strlen(s.c_str())
    std::vector<char> buf(&str[0], &str[size]);
    return buf;
  }
};

void conv(const std::vector<std::string> &vi,
          std::vector<std::vector<char> > &vo)
{
  // assert vo.size() == vi.size()
  std::transform(vi.begin(), vi.end(),
                 vo.begin(),
                 StringToVector());
}

В интересах иметь возможность Читать std::vector<std::string> Я публикую свое решение, основанное на подсказках Лео здесь https://stackoverflow.com/a/15220532/364818.

Я смешал API C и C ++.Пожалуйста, не стесняйтесь отредактировать это и упростить.

Обратите внимание, что HDF5 API возвращает список char*указатели, когда вы вызываете read.Эти char* указатели должны быть освобождены после использования, в противном случае происходит утечка памяти.

Пример использования

H5::Attribute Foo = file.openAttribute("Foo");
std::vector<std::string> foos
Foo >> foos;

Вот код

  const H5::Attribute& operator>>(const H5::Attribute& attr0, std::vector<std::string>& array)
  {
      H5::Exception::dontPrint();

      try
      {
          hid_t attr = attr0.getId();

          hid_t atype = H5Aget_type(attr);
          hid_t aspace = H5Aget_space(attr);
          int rank = H5Sget_simple_extent_ndims(aspace);
          if (rank != 1) throw PBException("Attribute " + attr0.getName() + " is not a string array");

          hsize_t sdim[1];
          herr_t ret = H5Sget_simple_extent_dims(aspace, sdim, NULL);
          size_t size = H5Tget_size (atype);
          if (size != sizeof(void*))
          {
              throw PBException("Internal inconsistency. Expected pointer size element");
          }

          // HDF5 only understands vector of char* :-(
          std::vector<char*> arr_c_str(sdim[0]);

          H5::StrType stringType(H5::PredType::C_S1, H5T_VARIABLE);
          attr0.read(stringType, arr_c_str.data());
          array.resize(sdim[0]);
          for(int i=0;i<sdim[0];i++)
          {
              // std::cout << i << "=" << arr_c_str[i] << std::endl;
              array[i] = arr_c_str[i];
              free(arr_c_str[i]);
          }

      }
      catch (H5::Exception& err)
      {
          throw std::runtime_error(string("HDF5 Error in " )
                                    + err.getFuncName()
                                    + ": "
                                    + err.getDetailMsg());


      }

      return attr0;
  }

Я опаздываю на вечеринку, но я изменил ответ Лео Гудштадта, основываясь на комментариях относительно segfaults.Я использую Linux, но у меня нет таких проблем.Я написал 2 функции, одну для записи вектора std::string в набор данных с заданным именем в открытом файле H5, а другую для обратного считывания результирующих наборов данных в вектор std::string.Обратите внимание, что может потребоваться копирование между типами несколько раз, что может быть более оптимизировано.Вот рабочий код для записи и чтения:

void write_varnames( const std::string& dsetname, const std::vector<std::string>& strings, H5::H5File& f)
  {
    H5::Exception::dontPrint();

    try
      {
        // HDF5 only understands vector of char* :-(
        std::vector<const char*> arr_c_str;
        for (size_t ii = 0; ii < strings.size(); ++ii)
      {
        arr_c_str.push_back(strings[ii].c_str());
      }

        //
        //  one dimension
        // 
        hsize_t     str_dimsf[1] {arr_c_str.size()};
        H5::DataSpace   dataspace(1, str_dimsf);

        // Variable length string
        H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
        H5::DataSet str_dataset = f.createDataSet(dsetname, datatype, dataspace);

        str_dataset.write(arr_c_str.data(), datatype);
      }
    catch (H5::Exception& err)
      {
        throw std::runtime_error(std::string("HDF5 Error in ")  
                 + err.getFuncName()
                 + ": "
                 + err.getDetailMsg());


      }
  }

И читать:

std::vector<std::string> read_string_dset( const std::string& dsname, H5::H5File& f )
  {
    H5::DataSet cdataset = f.openDataSet( dsname );


    H5::DataSpace space = cdataset.getSpace();

    int rank = space.getSimpleExtentNdims();

    hsize_t dims_out[1];

    int ndims = space.getSimpleExtentDims( dims_out, NULL);

    size_t length = dims_out[0];

    std::vector<const char*> tmpvect( length, NULL );

    fprintf(stdout, "In read STRING dataset, got number of strings: [%ld]\n", length );

    std::vector<std::string> strs(length);
    H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
    cdataset.read( tmpvect.data(), datatype);

    for(size_t x=0; x<tmpvect.size(); ++x)
      {
        fprintf(stdout, "GOT STRING [%s]\n", tmpvect[x] );
        strs[x] = tmpvect[x];
      }

    return strs;
  }

Я не знаю насчет HDF5, но вы можете использовать

struct TempContainer {
    char* string;
};

а затем скопируйте строки таким образом:

TempContainer t;
t.string = strdup(i->c_str());
tc.push_back (t);

Это выделит строку с точным размером, а также значительно улучшит процесс вставки или чтения из контейнера (в вашем примере скопирован массив, в данном случае только указатель).Вы также можете использовать std::vector:

std::vector<char *> tc;
...
tc.push_back(strdup(i->c_str());
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top