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

Учитывая вектор строк, как лучше всего записать их в набор данных 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. Мне не нужен временный контейнер

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

РЕДАКТИРОВАТЬ: Чтобы сузить проблему, я относительно знаком с игрой с данными на стороне C ++, именно на стороне HDF5 мне больше всего нужна помощь.

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


person Richard Corden    schedule 24.02.2009    source источник


Ответы (8)


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

Чтобы записать строку переменной длины в 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);
}      
person Richard Corden    schedule 24.02.2009
comment
Знаешь что? HDF5 - одна из тех вещей, которые я всегда хотел читать и писать. Но прокрастинация, будучи моим вторым именем, не материализовалась. Благодаря вам я решил на этот раз сделать снимок более целенаправленно. Мне было бы очень, очень интересно узнать, где вы это используете, если возможно. - person dirkgently; 24.02.2009
comment
Мы хотим изменить то, как наш инструмент статического анализа хранит данные, которые он собирает в результате своего анализа. Данные будут содержать древовидные структуры (области, типы и т. Д.) И списки диагностики. На этом этапе я просто оцениваю, насколько хорошо HDF5 обрабатывает различные типы данных. - person Richard Corden; 24.02.2009
comment
Этот вопрос (который я задал) описывает те функции, которые мы оцениваем: stackoverflow.com/questions/547195/ - person Richard Corden; 24.02.2009

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

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

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

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

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());


    }
}
person Leo Goodstadt    schedule 05.03.2013
comment
Ницца! Но как это прочитать из файла обратно в std::vector<std::string>? - person Walter; 02.07.2015
comment
На какой ОС это работает? Потому что похоже, что это вызовет сбои на многих машинах или просто повредит ваши данные. У меня есть подозрения, потому что я сделал нечто подобное, которое работало в Linux, но не удалось в OSX. - person Shep; 20.07.2015

Если вы ищете более чистый код: я предлагаю вам создать функтор, который будет принимать строку и сохранять ее в контейнере 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() ?
    }
};

Это поможет начать работу?

person dirkgently    schedule 24.02.2009
comment
Что ж - да, я надеюсь, что смогу достичь такого стиля - однако я не был уверен, было ли это (а) возможно и (б) эффективно. Спасибо за ответ. - person Richard Corden; 24.02.2009
comment
Я действительно новичок в HDF5, поэтому я понятия не имею, что нужно написать там, где у вас есть // append. - person Richard Corden; 24.02.2009
comment
Я так много слышал о HDF5. Я имел в виду, добавляя все, что вы делаете, под комментарием // Записывать временный контейнер в набор данных. - person dirkgently; 24.02.2009
comment
И в этом суть проблемы. Метод H5Dwrite принимает аргумент void * и пишет, что это немного похоже на memcpy или memmove, где вы задаете ему размер и блок данных. По крайней мере, я так думаю сейчас! :) - person Richard Corden; 24.02.2009
comment
Так что используйте your_data_string.c_str () и your_data_string.size (). void * - это действительно способ пропустить любые данные. Интересно, зачем вам нужна структура TempContainer? - person dirkgently; 24.02.2009
comment
В порядке. Ключевой вопрос: каковы параметры H5Dwrite, чтобы он добавлял только одну строку в следующий слот набора данных? Затем, как вы говорите, я могу использовать метод .c_str. Но в настоящее время мне нужно сначала создать контейнер в стиле C с моими строками, которые сбрасываются за один раз. - person Richard Corden; 24.02.2009
comment
Я предлагаю вместо создания одной огромной строки и одного вызова HDFwrite () не можете ли вы сделать обратное, т.е. написать много строк, используя несколько вызовов HDFwrite ()? - person dirkgently; 24.02.2009
comment
Итак, мы задаем тот же вопрос сейчас! ;) Все примеры для H5Dwrite, которые я нашел, похоже, записывают весь набор данных за один раз, а не запись за записью. - person Richard Corden; 24.02.2009
comment
Из того, что я вижу здесь - hdfgroup.org/HDF5/ doc / RM / RM_H5D.html # Dataset-Write - этот подход должен работать. - person dirkgently; 24.02.2009
comment
'HDFwrite () записывает частичный набор данных за раз.' - person dirkgently; 24.02.2009
comment
Спасибо, что помогли с этим. Одна из проблем заключается в том, что я еще не знаю, как написать частичный набор данных, и мне не удалось найти примеры, которые используют что-либо, кроме H5S_ALL для набора данных. Re: HDFwrite, это похоже на Matlab? По крайней мере, в моей библиотеке HDF5 его нет. - person Richard Corden; 24.02.2009
comment
Нет его и в библиотеке C. Вы это уже видели: hdfgroup.org/HDF5/doc/Intro/? - person dirkgently; 24.02.2009
comment
Я такого не видел - попробую. Спасибо за это. - person Richard Corden; 24.02.2009
comment
Также это: чтение и запись части набора данных где-то почти на полпути к этой странице hdfgroup.org/HDF5/doc/H5.intro.html#Intro-WhatIs. - person dirkgently; 24.02.2009

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

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

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

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

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

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

Вот краткий пример того, как это сделать, используя 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;
}
person Shep    schedule 19.07.2015

Я опаздываю на вечеринку, но я изменил ответ Лео Гудштадта на основе комментариев относительно ошибок сегментации. Я использую linux, но у меня таких проблем нет. Я написал 2 функции, одну для записи вектора std :: string в набор данных с заданным именем в открытом H5File, а другую для считывания результирующих наборов данных в вектор 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;
  }
person rveale    schedule 08.02.2016

Вместо 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());
}
person Benoît    schedule 24.02.2009

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

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

Обратите внимание, что HDF5 API возвращает список char*pointers при вызове 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;
  }
person Mark Lakata    schedule 30.04.2015

Не знаю насчет 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());
person Joao da Silva    schedule 24.02.2009
comment
Конечно. В идеале временный контейнер мне вообще не нужен. Этот код добавляет небольшой недостаток, заключающийся в том, что память нужно освобождать явно. - person Richard Corden; 24.02.2009