Как лучше всего записать контейнер 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);
}
Как минимум, я бы очень хотел изменить выше, чтобы:
- Он использует строки переменной длины
- Мне не нужно иметь временный контейнер
У меня нет ограничений на то, как я храню данные, например, это не должно быть тип данных COMPOUND, если есть лучший способ сделать это.
РЕДАКТИРОВАТЬ: Чтобы сузить проблему, я довольно хорошо знаком с воспроизведением данных на стороне С++, это сторона HDF5, где мне нужна большая часть помощи.
Спасибо за вашу помощь.
Ответы
Ответ 1
[Большое спасибо dirkgently за помощь в ответе на это.]
Для записи строки переменной длины в 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);
}
Ответ 2
Вот некоторый рабочий код для записи вектора строк переменной длины с использованием API-интерфейса HDF5 С++.
Я использую некоторые предложения в других сообщениях:
- используйте H5T_C_S1 и H5T_VARIABLE
- используйте
string::c_str()
для получения указателей на строки
- поместите указатели в
vector
of 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());
}
}
Ответ 3
Если вы смотрите на более чистый код: я предлагаю вам создать функтор, который возьмет строку и сохранит ее в контейнере 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() ?
}
};
Помогло ли это начать?
Ответ 4
У меня была аналогичная проблема, с оговоркой, что я хотел, чтобы вектор строк был сохранен как атрибут. Сложная вещь с атрибутами заключается в том, что мы не можем использовать причудливые функции dataspace, такие как гиперсловы (по крайней мере, с С++ API).
Но в любом случае может оказаться полезным ввести вектор строк в одну запись в наборе данных (если, например, вы всегда ожидаете, что прочитаете их вместе). В этом случае вся магия поставляется с типом, а не с самим массивом данных.
Есть в основном 4 шага:
- Сделайте
vector<const char*>
, который указывает на строки.
- Создайте структуру
hvl_t
, которая указывает на вектор и содержит длину.
- Создайте тип данных. Это
H5::VarLenType
wrapping a (переменная длина) H5::StrType
.
- Введите тип
hvl_t
в набор данных.
Действительно хорошая часть этого метода заключается в том, что вы заполняете всю запись в то, что HDF5 считает скалярным значением. Это означает, что создание атрибута (а не набора данных) является тривиальным.
Если вы выберете это решение или одно с каждой строкой в своей собственной записи набора данных, вероятно, также зависит от желаемой производительности: если вы ищете случайный доступ к определенным строкам, возможно, лучше написать строки в набор данных, чтобы они могли быть проиндексированы. Если вы всегда будете читать их вместе, это решение может работать так же хорошо.
Вот краткий пример того, как это сделать, используя 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;
}
Ответ 5
Вместо 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());
}
Ответ 6
В интересах иметь возможность читать std::vector<std::string>
Я отправляю свое решение на основе подсказок от Лео здесь fooobar.com/questions/336719/....
У меня смешанные C и С++ API. Не стесняйтесь редактировать это и упростите его.
Обратите внимание, что API HDF5 возвращает список указателей char*
при вызове чтения. Эти указатели 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;
}
Ответ 7
Я опаздываю на вечеринку, но я изменил ответ Лев Гудстадт на основе комментариев относительно segfaults. Я на 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;
}
Ответ 8
Я не знаю о 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());