HDF5をC++で使う：(その2）グルーピング

# グルーピングによるデータ階層化

HDF5のHは" **Hierarchical** （階層的な）"の意味ですが、階層構造を実現しているのがグルーピングという仕組みです。

例えば[(その1)](https://qiita.com/zhidao/items/22ca97f7d2e55ece6a3f)で作成した書き出しプログラムを実行し、作成されたtest.h5の中身をh5dumpで出力すると、次のような出力が得られました。
```
HDF5 "test.h5" {
GROUP "/" {
   DATASET "dataset" {
      DATATYPE  H5T_STD_I32LE
      DATASPACE  SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
      DATA {
      (0,0): 0, 1, 2, 3, 4, 5,
      (1,0): 6, 7, 8, 9, 10, 11,
      (2,0): 12, 13, 14, 15, 16, 17,
      (3,0): 18, 19, 20, 21, 22, 23
      }
   }
}
}
```
この最初の{}の中に`GROUP "/"`とあるのに気付いた方もいると思います。データセット作成時に特に指定しなかった場合、それは"/"という名前のグループに含まれることになっています。

グループはディレクトリに似ており、どんどん階層化することができます。グループの中にグループを持たせることができるという意味です。この仕組みによって、関連性の高いデータセットを整理したり、元データを保存したディレクトリ構造をそのままデータ構造に反映させたりできます。

# グループを用いたデータ書き出し

サンプルコードを載せます。
```C++
#include <iostream>
#include <H5Cpp.h>

#define GROUP1_NAME "/group1"
#define GROUP2_NAME "/group2"
#define GROUP1_SUB_NAME "/group1/subgroup"
#define DATASET1_NAME "/dataset1"
#define DATASET2_NAME "/dataset2"

bool create_dataset(H5::DataSet &dataset, H5::DataType &datatype, int offset)
{
  H5::DataSpace dataspace = dataset.getSpace();
  hsize_t dims[dataspace.getSimpleExtentNdims()];
  dataspace.getSimpleExtentDims( dims );

  int i, j, *dset_data = new int [dims[0]*dims[1]];
  if( !dset_data ) return false;
  for( i=0; i<static_cast<int>(dims[0]); i++ ){
     for( j=0; j<static_cast<int>(dims[1]); j++ )
        dset_data[i*dims[1]+j] = i * dims[1] + j + offset;
  }
  dataset.write( dset_data, datatype );
  delete dset_data;
  return true;
}

bool open_group_dataset(H5::H5File &file, const char *groupname, const char *datasetname, hsize_t dim1, hsize_t dim2, int offset)
{
  H5::Group group;
  try{
    group = file.createGroup( groupname );
  } catch( ... ){
    group = file.openGroup( groupname );
  }
  hsize_t dims[] = { dim1, dim2 };
  H5::DataSpace dataspace;
  dataspace.setExtentSimple( sizeof(dims) / sizeof(hsize_t), dims );

  std::string groupnamestr = groupname;
  std::string datasetnamestr = datasetname;
  H5::DataType datatype = H5::PredType::STD_I32LE;
  H5::DataSet dataset = group.createDataSet( groupnamestr + datasetnamestr, datatype, dataspace );
  create_dataset( dataset, datatype, offset );
  dataset.close();
  group.close();
  return true;
}

int main(int argc, char *argv[])
{
  if( argc < 2 ){
    std::cerr << "input file name." << std::endl;
    return 1;
  }
  H5::Exception::dontPrint(); // make this progam silent against exceptions.
  H5::H5File file( argv[1], H5F_ACC_TRUNC );
  open_group_dataset( file, GROUP1_NAME, DATASET1_NAME, 1, 6, 0 );
  open_group_dataset( file, GROUP1_NAME, DATASET2_NAME, 2, 4, 10 );
  open_group_dataset( file, GROUP1_SUB_NAME, DATASET1_NAME, 2, 6, -10 );
  open_group_dataset( file, GROUP2_NAME, DATASET1_NAME, 3, 3, 20 );
  open_group_dataset( file, GROUP2_NAME, DATASET2_NAME, 4, 6, 30 );
  file.close();
  return 0;
}
```
ちょっと長くなってしまいましたが、main()関数から順を追っていきましょう。
```C++
  if( argc < 2 ){
    std::cerr << "input file name." << std::endl;
    return 1;
  }
  H5::Exception::dontPrint(); // make this progam silent against exceptions.
  H5::H5File file( argv[1], H5F_ACC_TRUNC );
```
作成するHDF5ファイルの名前をコマンドライン第1引数で指定するようにしました。
`H5::Exception::dontPrint()`というメソッドが出てきましたが、これは無くても構いません。コメントに書いている通り、例外発生時のエラーメッセージを出力しないようにする効果を持つものです。

次にopen_group_dataset()関数に入ります。
```C++
  H5::Group group;
  try{
    group = file.createGroup( groupname );
  } catch( ... ){
    group = file.openGroup( groupname );
  }
```
`H5::Group`がグループを表すクラスです。新規にグループ作成する場合には`H5File`クラスのcreateGroup()メソッドを、既存グループを開く場合にはopenGroup()メソッドをそれぞれ用います。引数はどちらもグループ名（文字列）を与えます。

createGroup()メソッドは、グループが既に存在していた場合例外を発生します。これを利用して、与えられたグループ名が既にあるか無いかに応じて処理を分岐させています。

続く
```C++
  hsize_t dims[] = { dim1, dim2 };
  H5::DataSpace dataspace;
  dataspace.setExtentSimple( sizeof(dims) / sizeof(hsize_t), dims );
```
でデータスペースを定義しています。ここは前回と同様です。

この後の
```C++
  std::string groupnamestr = groupname;
  std::string datasetnamestr = datasetname;
```
は、グループ名とデータセット名を個別に与えるようにしている都合で用意しました。文字列の連結を+演算子でやりたかったからで、C言語風にsprintf()やstrcat()を使ってももちろん構いません。この事例から分かるように、文字列はcharポインタとstd::stringインスタンスどちらでも受け付けてくれます。

前回の例ではデータセットは`H5::H5File`のcreateDataSet()メソッドを使って作成しましたが、`H5::Group`インスタンスを陽に作る場合には、そちらのcreateDataSet()メソッドを用います。
```C++
  H5::DataType datatype = H5::PredType::STD_I32LE;
  H5::DataSet dataset = group.createDataSet( groupnamestr + datasetnamestr, datatype, dataspace );
```
`H5::DataType`インスタンスを作っているのは、この後に自作create_dataset()関数の引数に与えるためです。

そのcreate_dataset()関数に入りましょう。
```C++
  H5::DataSpace dataspace = dataset.getSpace();
  hsize_t dims[dataspace.getSimpleExtentNdims()];
  dataspace.getSimpleExtentDims( dims );
```
ここは前回作成した読み込みプログラムでデータスペース取得した方法と同じです。open_group_dataset()関数に与えた引数`dim1`, `dim2`をcreate_dataset()にも与えれば不要になるのですが、データセットが既にこれらの情報を持っているということで、冗長性を避けてこのようにしてみました。

続いて2重forループで適当にデータを作成してから、
```C++
  dataset.write( dset_data, datatype );
```
としてデータを書き出しています。これも前回の書き出しプログラムで説明しました。

あとは、open_group_dataset()関数に返って
```C++
  dataset.close();
  group.close();
```
としてデータセットとグループをともに閉じてから、main()関数に戻ります。最後に
```C++
  file.close();
```
としてファイルを閉じて、終了です。

# 階層化されたデータの例

上記プログラムのmain()関数では
```C++
  open_group_dataset( file, GROUP1_NAME, DATASET1_NAME, 1, 6, 0 );
  open_group_dataset( file, GROUP1_NAME, DATASET2_NAME, 2, 4, 10 );
  open_group_dataset( file, GROUP1_SUB_NAME, DATASET1_NAME, 2, 6, -10 );
  open_group_dataset( file, GROUP2_NAME, DATASET1_NAME, 3, 3, 20 );
  open_group_dataset( file, GROUP2_NAME, DATASET2_NAME, 4, 6, 30 );
```
としていますので、作成されるHDF5ファイルは
```
/
/group1/dataset1
/group1/dataset2
/group1/subgroup/dataset1
/group2/dataset1
/group2/dataset2
```
という階層構造を持っていることが期待されます。実際にプログラムを走らせてファイルtest.h5を作り、h5dumpで中身を出力すると次のようになり、予想通りであることが分かります。
```
HDF5 "test.h5" {
GROUP "/" {
   GROUP "group1" {
      DATASET "dataset1" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 1, 6 ) / ( 1, 6 ) }
         DATA {
         (0,0): 0, 1, 2, 3, 4, 5
         }
      }
      DATASET "dataset2" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 2, 4 ) / ( 2, 4 ) }
         DATA {
         (0,0): 10, 11, 12, 13,
         (1,0): 14, 15, 16, 17
         }
      }
      GROUP "subgroup" {
         DATASET "dataset1" {
            DATATYPE  H5T_STD_I32LE
            DATASPACE  SIMPLE { ( 2, 6 ) / ( 2, 6 ) }
            DATA {
            (0,0): -10, -9, -8, -7, -6, -5,
            (1,0): -4, -3, -2, -1, 0, 1
            }
         }
      }
   }
   GROUP "group2" {
      DATASET "dataset1" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 3, 3 ) / ( 3, 3 ) }
         DATA {
         (0,0): 20, 21, 22,
         (1,0): 23, 24, 25,
         (2,0): 26, 27, 28
         }
      }
      DATASET "dataset2" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
         DATA {
         (0,0): 30, 31, 32, 33, 34, 35,
         (1,0): 36, 37, 38, 39, 40, 41,
         (2,0): 42, 43, 44, 45, 46, 47,
         (3,0): 48, 49, 50, 51, 52, 53
         }
      }
   }
}
}
```