C ++:如何从格式化的文本文件中读取大量数据到程序中?

时间:2019-06-28 18:50:23

标签: c++ parsing config configuration-files mesh

我正在为特定的流体问题编写CFD解算器。到目前为止,每次运行模拟时都会生成网格,并且当更改几何形状和流体属性时,需要重新编译程序。

对于单元数较少的小型问题,它的工作原理很好。但是对于细胞数超过一百万的情况,需要经常更改流体性质,效率很低。

显然,我们需要将模拟设置数据存储在配置文件中,并将几何信息存储在格式化的网格文件中。

  1. Simulation.config文件
% Dimension: 2D or 3D
N_Dimension= 2
% Number of fluid phases
N_Phases=  1
% Fluid density (kg/m3)
Density_Phase1= 1000.0
Density_Phase2= 1.0
% Kinematic viscosity (m^2/s)
Viscosity_Phase1=  1e-6
Viscosity_Phase2=  1.48e-05
...
  1. Geometry.mesh文件
% Dimension: 2D or 3D
N_Dimension= 2
% Points (index: x, y, z)
N_Points: 100
x0 y0
x1 y1
...
x99 y99
% Faces (Lines in 2D: P1->p2)
N_Faces: 55
0 2
3 4
...
% Cells (polygons in 2D: Cell-Type and Points clock-wise). 6: triangle; 9: quad
N_Cells: 20
9 0 1 6 20
9 1 3 4 7
...
% Boundary Faces (index)
Left_Faces: 4
0
1
2
3
Bottom_Faces: 6
7
8
9
10
11
12
...

很容易将配置和网格信息写入格式化的文本文件。问题是,如何将这些数据有效地读入程序?我想知道是否有任何易于使用的c ++库来完成这项工作。

5 个答案:

答案 0 :(得分:5)

好吧 您可以基于有限元素集合,字典,一些Regex来实现自己的API,并且毕竟可以根据一些国际标准进行投注实践。

或者您可以看一下:

GMSH_IO

OpenMesh:

我刚刚在C ++ OpenGL项目的上一个实现中使用了OpenMesh。

答案 1 :(得分:5)

作为仅需获得可容忍的东西的第一个迭代解决方案,请使用@JosmarBarbosa的suggestion并为您的数据使用已建立的格式-它可能还具有免费的开放源代码库供您使用。一个例子是亚琛工业大学开发的OpenMesh。它支持:

  
      
  • 任意多边形(一般情况)和纯三角形网格的表示(提供更有效的专用算法)
  •   
  • 顶点,半边,边缘和面的明确表示。
  •   
  • 快速访问社区,尤其是单环社区(请参见下文)。
  •   
  • [自定义]
  •   

但是如果您真的需要加快网格数据的读取速度,请考虑执行以下操作:

  1. 将大小有限的元数据与更大的大小不受限制的网格数据分开;
  2. 将有限大小的元数据放在一个单独的文件中,然后按照您喜欢的方式进行读取,没关系。
  3. 将网格数据安排为固定大小的元素或固定大小的结构(例如单元,面,点等)的多个数组。
  4. 将每个固定宽度的网格数据数组存储在自己的文件中-无需在任何地方使用流式传输单个值:直接按原样读取或写入数组。 Here's an example of how a read would look。通过查看文件大小或元数据,您将知道读取的适当大小。

最后,您可以避免完全显式读取,而对每个数据文件使用内存映射。见

fastest technique to read a file into memory?

说明/腔室

  • 如果您在具有某些值的不同内存布局的系统上读写二进制数据(例如little-endian与big-endian),则需要对内存中的字节进行混洗。另请参阅this SO question有关字节顺序的信息。
  • 尽可能不优化读取速度可能并不值得。您应该考虑Amdahl's law,并且仅将其优化到不再占整个执行时间的大部分的程度。最好减少执行时间的几个百分点,但是要获得可以与支持已建立格式的其他工具一起使用的人类可读数据文件。

答案 2 :(得分:4)

在以下假设中,我假设:

  1. 如果一行的第一个字符是 while ( (output = inputStream.readLine()) != null) { println("Debug: " + output) } ,则它将作为注释忽略。
  2. 其他所有行的结构也完全相同,如下所示:%

我提供的代码将按照上述假设正确解析配置文件。这是代码(我希望所有需要的解释都在注释中):

identifier= value

阅读标识符后,您当然可以对它们进行任何处理。我仅以打印它们为例来说明如何获取它们。有关#include <fstream> //required for file IO #include <iostream> //required for console IO #include <unordered_map> //required for creating a hashtable to store the identifiers int main() { std::unordered_map<std::string, double> identifiers; std::string configPath; std::cout << "Enter config path: "; std::cin >> configPath; std::ifstream config(configPath); //open the specified file if (!config.is_open()) //error if failed to open file { std::cerr << "Cannot open config file!"; return -1; } std::string line; while (std::getline(config, line)) //read each line of the file { if (line[0] == '%') //line is a comment continue; std::size_t identifierLenght = 0; while (line[identifierLenght] != '=') ++identifierLenght; identifiers.emplace( line.substr(0, identifierLenght), std::stod(line.substr(identifierLenght + 2)) ); //add entry to identifiers } for (const auto& entry : identifiers) std::cout << entry.first << " = " << entry.second << '\n'; } 的更多信息,请查看here。有关使解析器有很多非常好的信息,请改用here

如果要使程序处理输入更快,请在std::unordered_map的开头插入以下行:main。这将使C ++ IO与C IO失去同步,从而使其变得更快。

答案 3 :(得分:4)

假设:

  • 您不想使用现有的网格格式
  • 您不想使用通用文本格式(json,yml等)
  • 您不想要二进制格式(即使您需要高效的格式)

简而言之,您确实需要自己的文本格式。

您可以使用任何parser generator入门。虽然您可能只使用正则表达式来解析配置文件,但从长远来看,它们可能确实受到限制。因此,我建议使用context-free grammar生成的Boost spirit::x3解析器。

AST

抽象语法树将保存解析器的最终结果。

#include <string>
#include <utility>
#include <vector>
#include <variant>

namespace AST {
    using Identifier = std::string; // Variable name.
    using Value = std::variant<int,double>; // Variable value.
    using Assignment = std::pair<Identifier,Value>; // Identifier = Value.
    using Root = std::vector<Assignment>; // Whole file: all assignments.
}

解析器

语法描述:

#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/home/x3.hpp>

namespace Parser {
    using namespace x3;

    // Line: Identifier = value
    const x3::rule<class assignment, AST::Assignment> assignment = "assignment";
    // Line: comment
    const x3::rule<class comment> comment = "comment";
    // Variable name
    const x3::rule<class identifier, AST::Identifier> identifier = "identifier";
    // File
    const x3::rule<class root, AST::Root> root = "root";
    // Any valid value in the config file
    const x3::rule<class value, AST::Value> value = "value";

    // Semantic action
    auto emplace_back = [](const auto& ctx) {
        x3::_val(ctx).emplace_back(x3::_attr(ctx));
    };

    // Grammar
    const auto assignment_def = skip(blank)[identifier >> '=' >> value];
    const auto comment_def = '%' >> omit[*(char_ - eol)];
    const auto identifier_def = lexeme[alpha >> +(alnum | char_('_'))];
    const auto root_def = *((comment | assignment[emplace_back]) >> eol) >> omit[*blank];
    const auto value_def = double_ | int_;

    BOOST_SPIRIT_DEFINE(root, assignment, comment, identifier, value);
}

用法

// Takes iterators on string/stream...
// Returns the AST of the input.
template<typename IteratorType>
AST::Root parse(IteratorType& begin, const IteratorType& end) {
    AST::Root result;
    bool parsed = x3::parse(begin, end, Parser::root, result);
    if (!parsed || begin != end) {
        throw std::domain_error("Parser received an invalid input.");
    }
    return result;
}

Live demo

进化

  • 要更改允许空格的位置,请在x3::skip(blank)表达式中添加/移动xxxx_def
  • 当前文件必须以换行符结尾。重写root_def表达式可以解决此问题。
  • 您肯定会想知道为什么无效输入导致解析失败。为此,请参见error handling tutorial
  • 您仅需几个规则即可解析更复杂的内容:

    //                                               100              X_n        Y_n
    const auto point_def = lit("N_Points") >> ':' >> int_ >> eol >> *(double_ >> double_ >> eol)
    

答案 4 :(得分:2)

如果您不需要特定的文本文件格式,但是有大量数据并且确实关心性能,建议您改用一些现有的数据序列化框架。

例如Google协议缓冲区允许使用很少的代码进行有效的序列化和反序列化。该文件是二进制文件,因此通常比文本文件小得多,并且二进制序列化比解析文本要快得多。它还支持结构化数据(数组,嵌套结构),数据版本控制和其他功能。

https://developers.google.com/protocol-buffers/