我正在使用C++在Gurobi上构建优化模型,我有一个关于如何为系数赋值的问题。目前,我在.cpp文件中作为进行了这些操作
const int A = 4;
double B[] = { 1, 2, 3 };
double C[][A] = {
{ 5, 1, 0, 3 },
{ 7, 0, 2, 4 },
{ 4, 6, 8, 9 }
};
这意味着B[1]=1,B[2]=2,B[3]=3,并且C[1][1]=5,C[1][2]=1,等等。但是,我希望对不同的系数集运行相同的模型,因此,如果我可以从多个.dat文件中读取,那么就更容易了,而不是更改.ccp文件中的值。我可以知道怎么做吗?
如果我将.dat文件保存为以下格式,可以吗?
[4]
[1, 2, 3]
[[5, 1, 0, 3],
[7, 0, 2, 4],
[4, 6, 8, 9]]
我不建议这样做。有些人会建议使用JSON或YAML,但如果你的系数总是那么简单,这里有一个建议:
原始文件
4
1 2 3
5 1 0 3
7 0 2 4
4 6 8 9
#include <iostream>
#include <sstream>
#include <vector>
struct Coefficients {
unsigned A;
std::vector<double> B;
std::vector< std::vector<double> > C;
};
std::vector<double> parseFloats( const std::string& s ) {
std::istringstream isf( s );
std::vector<double> res;
while ( isf.good() ) {
double value;
isf >> value;
res.push_back( value );
}
return res;
}
void readCoefficients( std::istream& fs, Coefficients& c ) {
fs >> c.A;
std::ws( fs );
std::string line;
std::getline( fs, line );
c.B = parseFloats( line );
while ( std::getline( fs, line ) ) {
c.C.push_back( parseFloats( line ) );
}
}
一个使用示例:
std::string data = R"(
4
1 2 3
5 1 0 3
7 0 2 4
4 6 8 9
)";
int main() {
Coefficients coef;
std::istringstream isf( data );
readCoefficients( isf, coef );
std::cout << "A:" << coef.A << std::endl;
std::cout << "B:" << std::endl << " ";
for ( double val : coef.B ) {
std::cout << val << " ";
}
std::cout << std::endl;
std::cout << "C:" << std::endl;
for ( const std::vector<double>& row : coef.C ) {
std::cout << " ";
for ( double val : row ) {
std::cout << val << " ";
}
std::cout << std::endl;
}
}
结果:
Program stdout
A:4
B:
1 2 3
C:
5 1 0 3
7 0 2 4
4 6 8 9
代码:https://godbolt.org/z/9s3zffahj
Gurobi。非常有趣!你选择了C++来连接它。很好。
我试着用非常简单的陈述给你一个非常简单的答案。
所有的代码都具有最低的复杂性,并且只需要使用少数语句。
不需要很多类似C的语句,因为C++是一种表达能力很强的语言。你可以做一个真正可以理解的或";易于阅读的";为你的问题抽象。如果你看看main,你只看到几行代码,然后你就会明白我的意思。
另外,我会给你详细的解释一切。所以,我不只是转储代码,而是逐行解释。此外,我添加了许多评论,并使用了合理的long和"说";变量名。
这段代码将使用C++,而不是C风格,正如您经常看到的那样。
然后让我们集中讨论一下如何在C++中进行操作。
如果我们看看你的第一个定义:
const int A = 4;
double B[] = { 1, 2, 3 };
double C[][A] = {
{ 5, 1, 0, 3 },
{ 7, 0, 2, 4 },
{ 4, 6, 8, 9 }
};
我们可以在这里看到C样式的数组。括号内有[]的。B数组的元素数量由初始化器元素的数量定义。因此,初始化器列表中的3个元素将在数组中使用3个元素-->数组大小为3。好的,理解
C元素是一个2维矩阵。列数由"const int A=4"定义。所以,我不确定,A是一个大小,还是一个系数。但最终,这并不重要。行数由源文本文件中的行数给定。Sor,我们有一个3行4列的矩阵。
第一个重要信息:在C++中,我们不使用C样式数组[]
。我们基本上有两匹通用的工作马:
- 如果在编译时已知数组的大小,则为
std::array
- C++主容器
std::vector
。可以根据需要动态增长的阵列。这是非常强大的,并且在C++中使用了很多。它还知道它包含多少元素,所以现在需要像A=4
这样的显式定义
std::vector
是用于此目的的容器。请阅读此处有关std::vector
的信息。因此,即使我们事先不知道行和列的数量,我们也可以使用std::vectorand it
来根据需要增长。
出于这个原因,我不确定;值为4"的const;,在您的系数信息中需要。无论如何,我会添加它。
接下来,C++是一种面向对象的语言。在该语言的最初,它甚至被称为"带对象的C"。C++中的对象是用类或结构来建模的。
面向对象方法的一个主要思想是,将数据和对这些数据进行操作的方法放在一个类(或结构)中。
因此,我们可以定义一个Coefficient类,并在这里存储所有系数数据,如a、B、C。然后,最重要的是,我们将向该类添加函数,这些函数将对这些数据进行操作。在我们的特殊情况下,我们将添加读取和写入功能。
正如您所知,C++使用了一个非常通用的IO流库。并具有所谓的"提取"运算符>>
和"插入器"运算符<<
。"流"被实现为分层类。这意味着,在哪个流(例如std::cout、文件流或字符串流)上使用<<
或>>
运算符并不重要,它基本上在任何地方都以相同的方式工作。
对于许多现有的和内置的数据类型,提取器>>
和插入器<<
运算符已经过载。因此,您可以输出许多不同的数据类型,例如std::cout
。
但是,这当然不适用于自定义类型,比如我们的类"系数"。但在这里,我们可以通过定义适当的插入器和提取器运算符来简单地添加功能。之后,我们可以以与其他内置数据类型相同的方式使用我们的新类型。
现在让我们来看第一个代码示例:
struct Coefficient {
// The data
int A{};
std::vector<double> B{};
std::vector<std::vector<double>> C{};
friend std::istream& operator >>(std::istream& is, Coefficient& coefficient);
friend std::ostream& operator << (std::ostream& os, const Coefficient& coefficient);
};
仅此而已。很简单,不是吗?现在该类具有所需的功能。
我们稍后将向运营商展示实施情况。
注意,这种机制也被称为(去)序列化,因为cour类的数据将以串行和人类可读的方式写入/读取。我们需要注意数据的输出和输入结构是相同的,这样我们就可以始终使用我们的2个运算符。
您现在应该已经理解了,我们稍后可以在main
或其他函数中对IO操作进行极其简单和低复杂度的处理。现在让我们看看主要内容:
// Some example data in a stream
std::istringstream exampleFile{ R"(4
1 2 3
5 1 0 3
7 0 2 4
4 6 8 9 )" };
// Test/Driver code
int main() {
// Here we have our coefficients
Coefficient coefficient{};
// Simply extract all data from the file and store it in our coefficients variable
// This is just a one-liner, intuitive and simple to understand.
exampleFile >> coefficient;
// One-liner debug output. Even mor simple
std::cout << coefficient;
}
这看起来非常直观,与其他内置数据类型的输入和输出类似。
现在让我们来谈谈实际的输入和输出函数。而且,由于我们希望保持简单,我们将以易于阅读的方式在".dat"文件中构建您的数据。
也就是说:用空格分隔的数据。那么:81 999 42
等等。为什么这么简单?因为在C++中,格式化的输入函数(具有提取器>>
的那些)将容易地读取这样的数据。示例:
int x,y,z;
std::cin >> x >> y >> z
如果您如上所示给出一个以空格分隔的输入,它将读取字符,将其转换为数字,并将其存储在变量中。
C++中有一个问题。也就是说,在大多数情况下,行尾字符"\n"也将被视为空白。因此,在循环中读取值不会在一行的末尾停止。
这个问题的标准解决方案是使用像std::getline
这样的非格式化输入函数,并首先将一整行读取到std::string
变量中。然后,我们将把这个字符串放入std::istringstream
中,它也是一个流,并从中提取值。
在".dat"文件中,有许多行包含数据。因此,我们需要反复进行上述操作。对于需要重复执行的事情,我们使用C++中的函数。我们需要一个函数,它接收流(任何流)读取值,将它们存储在std::vector
中并返回向量。
在向您展示这个函数之前,我将节省一些打字工作,并使用using
语句缩写矢量和二维矢量。
请参阅:
// Some abbreviations for easier typing and reading
using DVec = std::vector<double>;
using DDVec = std::vector<DVec>;
// ---------------------------------------------------------------------------
// A function to retrieve a number of double values from a stream for one line
DVec getDVec(std::istream& is) {
// Read one complete line
std::string line{}; std::getline(is, line);
// Put it in an istringstream for better extracting
std::istringstream iss(line);
// And use the istream_iterator to iterate over all doubles and put the data in the resulting vector
return { std::istream_iterator<double>(iss), {} };
}
你看,一个简单的3行函数。最后一行对于初学者来说可能很难理解。我稍后会解释。因此,我们的函数期望引用一个流作为输入参数,然后返回一个包含一行中所有双精度的std::vector<double>
。
因此,首先,我们将一整行读取到类型为std::string
的变量中。超简单,具有现有的std::getline
功能。
然后,我们将字符串放入一个std::istringstream
变量中。这将基本上将字符串转换为流,并允许我们在其中使用所有流函数。记住,我们为什么这么做:因为我们想读一整行,然后从中提取数据。现在是最后一行:
return { std::istream_iterator<double>(iss), {} };
那是什么?我们期望返回一个std::vector<double>
。编译器知道我们想要返回这样一个类型。因此,他将为我们创建一个该类型的临时变量,并使用其范围构造函数no5()(参见此处)来初始化我们的向量。用什么?
您可以在CPP引用中看到,它需要2个迭代器。一个开始迭代器和一个结束迭代器。迭代器之间的所有内容都将被包容性地复制到向量中。
std::istream_iterator
(请阅读此处)将简单地重复调用提取器运算符>>
,然后读取所有的double,直到读取所有值。
酷!
接下来,我们可以在类的提取器运算符>>
中使用此功能。然后会变成这样;
// Simple extraction operator
friend std::istream& operator >>(std::istream& is, Coefficient& coefficient) {
// Get A and all the B coefficients
coefficient.B = std::move(getDVec(is >> coefficient.A >> std::ws));
// And in a simple for loop, readall C-coeeficients
for (DVec dVec{ getDVec(is) }; is and not dVec.empty(); dVec = getDVec(is))
coefficient.C.push_back(std::move(dVec));
return is;
}
它将首先读取
- A的值(
>> coefficient.A
) - 则流中可能存在的所有空白,然后(
>> std::ws
) - 具有B系数的行(
getDVec(is
)
LAst,但同样重要的是,我们使用s imple for loop,读取C系数的所有行,并将它们添加到2d输出向量中。我们将跳过空行。
std::move
将避免复制大数据,并为我们提供更好的效率。
输出甚至更简单。使用";循环";以显示数据。这里没有太多要解释的。
现在,我们拥有所有功能。我们把大问题分解成小问题,使我们的生活变得更简单。
最后的完整代码如下所示:
#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iterator>
// Some abbreviations for easier typing and reading
using DVec = std::vector<double>;
using DDVec = std::vector<DVec>;
// ---------------------------------------------------------------------------
// A function to retrieve a number of double values from a stream for one line
DVec getDVec(std::istream& is) {
// Read one complete line
std::string line{}; std::getline(is, line);
// Put it in an istringstream for better extracting
std::istringstream iss(line);
// And use the sitream_iterator to iterate over all doubles and put the data in the resulting vector
return { std::istream_iterator<double>(iss), {} };
}
// -------------------------------------------------------------
// Cooeficient class. Holds data and methods to operate on this data
struct Coefficient {
// The data
int A{};
DVec B{};
DDVec C{};
// Simple extraction operator
friend std::istream& operator >>(std::istream& is, Coefficient& coefficient) {
// Get A and all the B coefficients
coefficient.B = std::move(getDVec(is >> coefficient.A >> std::ws));
// And in a simple for loop, readall C-coeeficients
for (DVec dVec{ getDVec(is) }; is and not dVec.empty(); dVec = getDVec(is))
coefficient.C.push_back(std::move(dVec));
return is;
}
// Even more simple inserter operator. Output values in loops
friend std::ostream& operator << (std::ostream& os, const Coefficient& coefficient) {
os << coefficient.A << 'n';
for (const double d : coefficient.B) os << d << ' '; os << 'n';
for (const DVec& dv : coefficient.C) {
for (const double d : dv) os << d << ' '; os << 'n'; }
return os;
}
};
// Some example data in a stream
std::istringstream exampleFile{ R"(4
1 2 3
5 1 0 3
7 0 2 4
4 6 8 9 )" };
// Test/Driver code
int main() {
// Here we have our coefficients
Coefficient coefficient{};
// Simply extract all data from the file and store it in our coefficients variable
exampleFile >> coefficient;
// One-liner debug output
std::cout << coefficient;
}
请再次查看主要的简单语句。
我希望我能帮你一点忙。
一些附加说明。
- 在专业软件开发中,没有注释的代码被认为具有0质量
- 此外,SO指南建议,不仅要转储代码,还要给出全面的解释
- 请不要使用:
while ( isf.good() ) {
这被认为是非常糟糕的做法,而且容易出错。请阅读 - 如果你决定使用C++,你应该尝试摆脱典型的串行C编程,使用更面向对象的方法
如果您还有其他问题,我很乐意回答。谢谢你的提问。