2009-02-24 10 views
15

Dado un vector de cadenas, ¿cuál es la mejor manera de escribirlas en un conjunto de datos HDF5? En el momento que estoy haciendo algo como lo siguiente:¿Cuál es la mejor forma de escribir un contenedor std :: vector <std :: string> en un conjunto de datos 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); 
} 

Como mínimo, me gustaría cambiar a lo anterior de forma que:

  1. Utiliza cadenas de longitud variable
  2. Pongo 't necesita tener un contenedor temporal

tengo ninguna restricción sobre cómo almacenar los datos así que por ejemplo, no tiene por qué ser un COMPUESTO tipo de datos si hay una mejor manera de hacerlo.

EDIT: Simplemente para reducir el problema, estoy relativamente familiarizado con jugar con los datos en el lado C++, es el lado HDF5 donde necesito la mayor parte de la ayuda.

Gracias por su ayuda.

Respuesta

10

[Muchas gracias a dirkgently por su ayuda en responder a esta.]

para escribir una cadena de longitud variable en HDF5 utilizar los siguientes:

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

Una solución para escribir un contenedor es escribir cada elemento individualmente. Esto se puede lograr usando hyperslabs.

Por ejemplo:

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); 
}  
+0

¿Sabes qué? HDF5 es una de esas cosas que siempre quise leer y escribir. Pero la procrastinación es mi segundo nombre que no se ha materializado. Gracias a ti, he decidido darle un tiro más dedicado esta vez. Estaría muy, muy interesado en saber dónde estás usando esto, si es posible. – dirkgently

+0

Estamos buscando cambiar cómo nuestra herramienta de análisis estático almacena los datos que recopila de su análisis. Los datos contendrán estructuras similares a árboles (ámbitos, tipos, etc.) y listas de diagnósticos. En esta etapa, estoy evaluando qué tan bien maneja HDF5 los diferentes tipos de datos. –

+0

Esta pregunta (que pregunté) describe el tipo de características que estamos evaluando: http://stackoverflow.com/questions/547195/evaluating-hdf5-what-limitations-features-does-hdf5-provide-for-modelling -data –

1

Si está buscando un código limpiador: le sugiero que cree un functor que tome una cadena y la guarde en el contenedor HDF5 (en el modo deseado). Richard, usé el algoritmo incorrecto, por favor vuelve a verificar!

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

¿Eso ayuda a empezar?

+0

Bueno - sí estoy esperando para poder llegar a este tipo de estilo - sin embargo, no estaba seguro de si era (a) posible y (b) eficiente. Gracias por la respuesta. –

+0

Soy realmente muy nuevo en HDF5, por lo que no tengo idea de lo que debe escribirse donde tiene "// append". –

+0

Solo he oído hablar mucho de HDF5. Quise decir agregando lo que sea que estés haciendo bajo el comentario // Escribe el contenedor temporal en un conjunto de datos. – dirkgently

-1

No sé sobre HDF5, pero se puede usar

struct TempContainer { 
    char* string; 
}; 

y luego copiar las cadenas de esta manera:

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

Esto asignará una cadena con el tamaño exacto, y también mejora mucho al insertar o leer desde el contenedor (en su ejemplo hay una matriz copiada, en este caso solo un puntero). También puede utilizar std :: vector:

std::vector<char *> tc; 
... 
tc.push_back(strdup(i->c_str()); 
+0

Claro. Idealmente, no necesitaría el contenedor temporal en absoluto. Este código agrega la ligera desventaja de que la memoria debe liberarse explícitamente. –

0

En lugar de un TempContainer, se puede utilizar un std :: vector sencilla (también se puede a plantillas para que coincida con T -> basic_string Algo como esto:

.
4

Aquí hay algunos códigos de trabajo para escribir un vector de cadenas de longitud variable utilizando la API HDF5 C++.

incorporo algunas de las sugerencias de los otros mensajes:

  1. uso H5T_C_S1 y H5T_VARIABLE
  2. uso string::c_str() obtener punteros a las cadenas
  3. lugar los punteros en un vector de char* y paso a la HDF5 API

Es No es necesario para crear copias costosas de la cadena (p. con strdup()). c_str() devuelve un puntero a los datos terminados nulos de la cadena subyacente. Esto es precisamente para lo que está destinada la función. Por supuesto, las cadenas con valores nulos implícitos no funcionarán con esta ...

std::vector está garantizado para tener un almacenamiento subyacente contigua, así que usar vector y vector::data() es lo mismo que usar matrices primas, pero es por supuesto mucho más limpio y más seguro que el forma de hacer las cosas torpe y anticuada.

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


    } 
} 
+0

¡agradable! ¿Pero cómo leer eso desde el archivo a un 'std :: vector '? – Walter

+0

:-(desafortunadamente, obtengo segfault con esto ... – Walter

+0

¿En qué sistema operativo funciona? Porque parece que esto provocará segfaults en muchas máquinas, o simplemente corromperá sus datos. Sospecho porque hice algo similar que funcionó en Linux pero falló en OSX. – Shep

0

Con el fin de tener la capacidad de leer std::vector<std::string> les dejo mi solución, basado en los consejos de Leo aquí https://stackoverflow.com/a/15220532/364818.

He mezclado API C y C++. Por favor, siéntase libre de editar esto y hacerlo más simple.

Tenga en cuenta que la API HDF5 devuelve una lista de punteros char* cuando llama a lectura. Estos punteros char* se deben liberar después de su uso; de lo contrario, se produce una pérdida de memoria.

Ejemplo de uso

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

Aquí está el código

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; 
    } 
1

tuve un problema similar, con la salvedad de que quería un vector de cadenas almacenadas como un atributo. Lo difícil con los atributos es que no podemos usar características de dataspace sofisticadas como hyperslabs (al menos con la API de C++).

Pero en cualquier caso, puede ser útil introducir un vector de cadenas en una sola entrada en un conjunto de datos (si, por ejemplo, siempre espera leerlos juntos). En este caso, toda la magia viene con el tipo , no con el propio espacio de datos.

Hay básicamente 4 pasos:

  1. Haz una vector<const char*> que apunta a las cuerdas.
  2. Crea una estructura hvl_t que apunta al vector y contiene su longitud.
  3. Crea el tipo de datos.Este es un H5::VarLenType envoltura a (longitud variable) H5::StrType.
  4. Escribe el tipo hvl_t en un conjunto de datos.

La parte realmente agradable de este método es que está rellenando toda la entrada en lo que HDF5 considera un valor escalar. Esto significa que convertirlo en un atributo (en lugar de un conjunto de datos) es trivial.

Si elige esta solución o la que tiene cada cadena en su propia entrada de conjunto de datos es probablemente también una cuestión del rendimiento deseado: si busca acceso aleatorio a cadenas específicas, es probable que sea mejor escribir las cadenas en un conjunto de datos para que puedan ser indexados. Si siempre los va a leer juntos, esta solución puede funcionar igual de bien.

He aquí un breve ejemplo de cómo hacer esto, utilizando la API C++ y un simple conjunto de datos escalares:

#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; 
} 
0

llego tarde a la fiesta, pero he modificado la respuesta de Leo Goodstadt basado en las observaciones relativas segfaults. Estoy en Linux, pero no tengo esos problemas. Escribí 2 funciones, una para escribir un vector de std :: string a un conjunto de datos de un nombre de pila en un H5File abierto, y otra para leer los conjuntos de datos resultantes en un vector de std :: string. Tenga en cuenta que puede haber copias innecesarias entre los tipos algunas veces que pueden ser más optimizadas. Aquí está el código de trabajo para la escritura y lectura:

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


     } 
    } 

Y para que diga:

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; 
    } 
Cuestiones relacionadas