【问题标题】:How to emit or write a map of maps in yaml file? c++如何在 yaml 文件中发出或编写地图地图? C++
【发布时间】:2020-05-09 22:42:13
【问题描述】:

所以我有几个虚拟护目镜......每个都有不同的校准参数。我决定将这些参数保存到一个 yaml 文件中(作为配置文件).. 每个护目镜都有自己的序列号/标识号......并根据这个数字,我选择使用哪个。 如果护目镜没有预先保存的信息。我校准它并将这些参数添加到文件中

所以现在我正在尝试写入一个看起来像这样的 yaml 文件:

Headset:
  IdentificationNumber: b630cc42-9a03-42da-a039-0e023cf5b090
  GyroOffset:
    GyroX:
      Value: -0.013776619
    GyroY:
      Value: -0.016475508
    GyroZ:
      Value: -0.0114268782

这就是我实际得到的:

Headset2:
  IdentificationNumber: b630cc42-9a03-42da-a039-0e023cf5b090
? GyroOffset:
    GyroX:
      Value: -0.013776619
  ? GyroY:
      Value: -0.016475508
  : GyroZ:
      Value: -0.0114268782

我不知道我做错了什么! .. 这是我写入 yaml 文件的函数:

void ParseInputDeviceYaml::addCalibrationToConfigFile(const char* identificationNumber, const float* in)
{
    try {
        std::ofstream updatedFile;
        updatedFile.open(m_filename.toStdString(), std::ios::app);

        std::map<std::string, std::string>                  IDNumber;
        std::map<std::string, std::map<std::string, float>> gyroXOffset;
        std::map<std::string, std::map<std::string, float>> gyroYOffset;
        std::map<std::string, std::map<std::string, float>> gyroZOffset;

        IDNumber["IdentificationNumber"] = identificationNumber;

        gyroXOffset["GyroX"]["Value"] = *in;
        gyroYOffset["GyroY"]["Value"] = *(in + 1);
        gyroZOffset["GyroZ"]["Value"] = *(in + 2);

        YAML::Emitter newNode;


        newNode << YAML::BeginMap;
        newNode << YAML::Key << "Headset2";
        newNode << YAML::Value << YAML::BeginMap << YAML::Key << "IdentificationNumber" << YAML::Value << identificationNumber << YAML::EndMap;
        newNode << YAML::BeginMap << YAML::Key << "GyroOffset" << YAML::Value << gyroXOffset << gyroYOffset << gyroZOffset << YAML::EndMap;
        newNode << YAML::EndMap;

        updatedFile << newNode.c_str() << "\n";

        updatedFile.close();
    } catch (std::exception& e) {
        LOG4CPLUS_FATAL(m_logger, e.what());
        throw std::runtime_error(QObject::tr("Writing gyroscope offsets ").toStdString());
    }
}

【问题讨论】:

  • 为什么 YAML 中的所有内容都在序列中?您是否希望一个耳机有多个IdentificationNumbers 或多个GyroOffset 列表?如果您删除所有序列,阅读它会更简单,我看不出其中任何一个原因。
  • 另外,VersionHeadset1 处于同一级别并不好(我认为这里会有更多耳机),因为Version 不是耳机。这对实现来说是一个不必要的负担,如果在 Version 之后会有 Heasets: 这是一个包含耳机的地图,那就更简单了。
  • 我相信我现在把问题改得更具体了。当然,使用地图读取数据更简单。但我还有另一个问题。现在在问题中说明了这一点。
  • 好的,更清楚的是,您使用显式 ? 键显示的 YAML 是实际输出;我写了我的答案,假设这是您精心设计的输入。问题是您将映射写入键,如果您使用我建议的辅助类的方法,这仍然是可以避免的。

标签: c++ parsing maps yaml yaml-cpp


【解决方案1】:

主要问题似乎是您正在建立大量错误信息。我会尝试清除一些东西:

  • 是否使用序列与能否修改现有值或向文件添加新值完全无关。问题是您使用std::ios::app 附加到文件,这将始终创建一个新条目。相反,您应该将文件加载到 YAML 节点中,修改该节点的内容,然后写回整个节点。
  • 没有您提供的序列的 YAML 文件肯定不会像您认为的那样做,因为您将 ? GyroOffset 放置在与 Headset2: 相同的深度,使其成为 Headset2 的兄弟。另请注意,在同一映射中将隐式 (foo:) 与显式 (? foo) 键混合是一种极端情况,可能会混淆某些实现。 YAML 文件可能如下所示:
Headset2:
  IdentificationNumber: b630cc42-9a03-42da-a039-0e023cf5b090
  GyroOffset:
    GyroX:
      Value: -0.012388126
    GyroY:
      Value: -0.0155748781
    GyroZ:
      Value: -0.0115196211

为了使您的代码更具可读性,我建议使用帮助类来访问您的值。假设上面的代码是整个 YAML 文件,它可能看起来像这样:

struct Value {
  YAML::Node data;
  // access existing node
  explicit Value(YAML::Node data): data(data) {
    assert(data.IsMapping());
  }
  // create new node
  explicit Value(float value) {
    data["Value"] = value;
  }

  float get() { return data["Value"].as<float>(); }
  void set(float value) { data["Value"] = value; }
};

struct GyroOffset {
  YAML::Node data;
  explicit GyroOffset(YAML::Node data): data(data) {
    assert(data.IsMapping());
  }
  GyroOffset(float x, float y, float z) {
    data["GyroX"] = Value(x).data;
    data["GyroY"] = Value(y).data;
    data["GyroZ"] = Value(z).data;
  }
  Value gyroX() { return Value(data["GyroX"]); }
  Value gyroX() { return Value(data["GyroY"]); }
  Value gyroZ() { return Value(data["GyroZ"]); }
};

struct Headset {
  YAML::Node data;
  Headset(YAML::Node data): data(data) {
    assert(data.IsMapping());
  }
  Headset(const char *id) {
    data["IdentificationNumber"] = id;
    // initialize with zero values
    data["GyroOffset"] = GyroOffset(0, 0, 0).data;
  }

  std::string id() { return data["IdentificationNumber"].as<std::string>(); }
  void setId(const char *value) { data["IdentificationNumber"] = value; }

  GyroOffset gyroOffset() { return GyroOffset(data["GyroOffset"]); }
}

现在,找到给定标识号的 GyroOffset 看起来像这样(我展示了一个简单的函数,因为我不知道你的类的字段,因为你没有展示它们):

// write found values to output of found
bool findHedasetGyroOffset(Yaml::Node &input /* the file as shown above */, const char *id, GyroOffset &output) {
  for (auto it = input.begin(); it != input.end(); ++it) {
    Headset hs(it->second);
    if (hs.id() == id) {
      output = hs.gyroOffset();
      return true;
    }
  }
  return false;
}

由于YAML::Node 基本上是一个引用,所以当您更改返回的GyroOffset 内的值时,原始数据会发生变化。然后,您可以将根节点写回文件(附加它)并更新文件。

添加新耳机如下所示:

void addCalibrationToConfigFile(Yaml::Node &file, const char* identificationNumber, const float* in) {
  Headset newHs(identificationNumber);
  auto go = newHs.gyroOffset();
  go.gyroX().set(*in);
  go.gyroY().set(*(in + 1));
  go.gyroZ().set(*(in + 2));
  // note that this will overwrite an existing Headset2
  file["Headset2"] = newHs.data;
}

虽然我尝试遵循您显示的结构,但我感觉映射中的实际键不应该是 Headset2,而是 IdentificationNumber:

b630cc42-9a03-42da-a039-0e023cf5b090:
  Name: Headset2
  GyroOffset:
    GyroX:
      Value: -0.012388126
    GyroY:
      Value: -0.0155748781
    GyroZ:
      Value: -0.0115196211

由于您根据 ID 进行查找,这将更有意义。此外,创建一个新配置实际上会起作用(目前,由于硬编码的 "Headset2" 值,它总是会覆盖该耳机(如果存在)。

注意,我写的是演示代码,没有测试;可能有错误。

【讨论】:

  • 感谢您的回答。我现在将研究它并着手处理它:) 当然,“Headset2”现在只是硬编码的。一旦我开始阅读和写作没有问题。我会考虑引入某种计数器来计算识别出的护目镜数量。这就是为什么我实际上想到附加。所以最后文件会像这样 Headset1: .. Headset2:.. Headst3: 直到 Headsetn 如果有其他问题我会回来;) ...祝你有美好的一天!
  • 如果您的耳机没有有意义的名称,当然可以是序列而不是地图! :)
  • 好的,现在我阅读了您的代码,并且有几个问题我想尽可能多地理解)).. 1. 正如您所说,您正在构建帮助访问值的结构.. 什么显式实际上是做什么的?我用谷歌搜索,但我无法通过转换和复制构造函数真正得到它! (有必要吗?还是您的编码风格更多?).. 2.我没有耳机的有意义的名称,所以它将是一个序列。我想我必须将耳机结构断言为 isSequence 而不是映射? ..
  • 3.这里只能覆盖节点,这不是我的目标。使用计数器之后,我是否必须稍后使用发射器..或者我将如何将新耳机附加到节点?非常感谢 !如果问题太多,对不起:)
  • 1. explicit 避免从 YAML::Node 到包装类的隐式转换。如果您在使用诸如 clang-tidy 之类的工具的单参数构造函数上错过了它,您将收到警告。 2. 关于断言,这些基本上是真正错误处理的占位符,但是是的,IsSequence 将是要检查的东西。对于 3. 我不确定您缺少什么,我展示了用于访问可以更改的现有对象和附加新对象的代码。任何修改后,你总是通过发射器写入根节点。
猜你喜欢
  • 2013-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-25
  • 2014-05-28
  • 2011-03-25
  • 1970-01-01
相关资源
最近更新 更多