我需要将double
值格式化为具有非常特定格式的坐标字符串,"DDMMSS.SSX"
其中:
字段需要用零填充。空间不能被接受。格式化的示例如下:
47.2535 ==> "471512.45N"
-0.123345 ==> "000724.04S"
我已设法创建以下程序来完成这项工作。不过我有一些问题:
locls
规则有更优雅的方式吗?它的目的是将绝对值存储到局部变量value
中。是否有(希望更优雅)的方式来访问fabs()
函数?_1
(_1 = _val
等)的分配是不必要的,因为我在局部变量value
中有值。但是,如果我删除这些作业,我得到的只是"000000.00N"
。 value
后使用它。有更好的方法吗?我很乐意收到一些反馈
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/bind.hpp>
namespace karma = boost::spirit::karma;
typedef std::back_insert_iterator<std::string> iterator_type;
struct genLongitude : karma::grammar<iterator_type, double()>
{
genLongitude()
: genLongitude::base_type(start)
{
using karma::eps;
using karma::int_;
using karma::char_;
using karma::_1;
using karma::_val;
using karma::right_align;
using boost::phoenix::static_cast_;
using boost::phoenix::ref;
using boost::phoenix::if_;
start = locls
<< degrees << minutes << seconds
<< ( eps(_val < 0.0) << char_('E') | char_('W') );
locls = eps[_1 = _val, if_(_val < 0.0) [ref(value) = - _val] .else_ [ref(value) = _val]];
degrees = right_align(3,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]]
<< eps[ref(value) = (ref(value) - static_cast_<int>(ref(value))) * 60 ];
minutes = right_align(2,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]]
<< eps[ref(value) = (ref(value) - static_cast_<int>(ref(value))) * 60 ];
seconds = right_align(2,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]]
<< char_(".")
<< eps[ref(value) = (ref(value) - static_cast_<int>(ref(value))) * 100 ]
<< right_align(2,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]];
}
private:
double value;
karma::rule<iterator_type, double()> start, locls, degrees, minutes, seconds;
};
int main()
{
for(auto & value : std::vector<double>{ 47.25346, 13.984364, -0.1233453, -44.3 })
{
std::string generated;
iterator_type outiter(generated);
auto rv = karma::generate(outiter, genLatitude(), value);
std::cout << "(" << rv << ") " << value << " ==> " << generated << std::endl;
}
}
更新
只是为了完整性,这在任何示例(和答案)中修复实际上是微不足道的
纬度格式为"DDMMSS.SSX"
,经度为"DDDMMSS.SSX"
。这是因为纬度范围是-90到+90,而经度是-180到+180。
答案 0 :(得分:6)
关注点分离。
你的语法已经变得一团糟,因为你试图将所有逻辑都放在一个地方,而这实际上并不是真的。
与此同时,你让发电机成为有状态的,这意味着性能也会下降。
相反,要意识到你有一个数学变换(真正的价值) - &gt;元组(度,分,秒,半球)。让我们创建一个小帮手来模拟:
struct LatLongRep {
bool _hemi; double _deg, _min, _sec;
LatLongRep(double val)
: _hemi(0 < val),
_min(60 * std::modf(std::abs(val), &_deg)),
_sec(60 * std::modf(_min, &_min))
{ }
};
现在,你可以有这样的规则:
karma::rule<iterator_type, LatLongRep()> latitude, longitude;
他们很容易实现:
latitude =
right_align(3, '0') [ uint_ ]
<< right_align(2, '0') [ uint_ ]
<< right_align(5, '0') [ seconds ]
<< east_west;
所以整个程序变成:
<强> Live On Coliru 强>
#include <boost/spirit/include/karma.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <cmath>
namespace karma = boost::spirit::karma;
typedef std::back_insert_iterator<std::string> iterator_type;
struct LatLongRep {
bool _hemi; double _deg, _min, _sec;
LatLongRep(double val)
: _hemi(0 < val),
_min(60 * std::modf(std::abs(val), &_deg)),
_sec(60 * std::modf(_min, &_min))
{ }
};
BOOST_FUSION_ADAPT_STRUCT(LatLongRep, _deg, _min, _sec, _hemi)
struct genLatLong : karma::grammar<iterator_type, double()> {
genLatLong() : genLatLong::base_type(start)
{
using namespace karma;
east_west.add (true, 'E')(false, 'W');
north_south.add(true, 'N')(false, 'S');
start = latitude;
latitude =
right_align(3, '0') [ uint_ ]
<< right_align(2, '0') [ uint_ ]
<< right_align(5, '0') [ seconds ]
<< east_west;
longitude =
right_align(3, '0') [ uint_ ]
<< right_align(2, '0') [ uint_ ]
<< right_align(5, '0') [ seconds ]
<< north_south;
}
private:
struct secfmt : karma::real_policies<double> {
unsigned precision(double) const { return 2; }
bool trailing_zeros(double) const { return true; }
};
karma::real_generator<double, secfmt> seconds;
karma::symbols<bool, char> east_west, north_south;
karma::rule<iterator_type, double()> start;
karma::rule<iterator_type, LatLongRep()> latitude, longitude;
};
int main()
{
genLatLong const gen;
for(auto & value : std::vector<double>{ 47.25346, 13.984364, -0.1233453, -44.3 })
{
std::string generated;
iterator_type outiter(generated);
auto rv = karma::generate(outiter, gen, value);
std::cout << "(" << std::boolalpha << rv << ") " << value << " ==> " << generated << std::endl;
}
}
打印
(true) 47.2535 ==> 0471512.46E
(true) 13.9844 ==> 0135903.71E
(true) -0.123345 ==> 0000724.04W
(true) -44.3 ==> 0441760.00W
使用名为real_policy
的派生secfmt
格式化带有2位小数的秒数;见documentation
使用融合自适应获取LatLongRep
的字段而不过度使用语义动作和/或凤凰绑定(请参阅tutorial example)。另请参阅Boost Spirit: "Semantic actions are evil"?
使用karma::symbols<>
格式化半球指标:
karma::symbols<bool, char> east_west, north_south;
east_west.add (true, 'E')(false, 'W');
north_south.add(true, 'N')(false, 'S');
发电机结构现在不在循环中 - 这大大提高了速度
使用定义的纬度和经度作为读者的练习
答案 1 :(得分:3)
再多想一想,让我回答一下
问。 对于这类问题通常有更好的解决方案吗?
在这种情况下,使用Boost格式可能会更好。重用LatLongRep
- 我的另一个答案的计算工作马,你可以很容易地创建IO操纵器:
namespace manip {
struct LatLongRepIO : LatLongRep {
LatLongRepIO(double val, char const* choices) : LatLongRep(val), _display(choices) { }
private:
char const* _display;
friend std::ostream& operator<<(std::ostream& os, LatLongRepIO const& llr) {
return os << boost::format("%03d%02d%05.2f%c")
% llr._deg % llr._min % llr._sec
% (llr._display[llr._hemi]);
}
};
LatLongRepIO as_latitude (double val) { return { val, "WE" }; }
LatLongRepIO as_longitude(double val) { return { val, "NS" }; }
}
这完全放弃了Boost Spirit,Phoenix和Fusion的使用,并且使用变得轻而易举:
int main() {
using namespace helpers::manip;
for(double value : { 47.25346, 13.984364, -0.1233453, -44.3 })
std::cout << as_latitude(value) << "\t" << as_longitude(value) << "\n";
}
#include <boost/format.hpp>
#include <cmath>
namespace helpers {
struct LatLongRep {
bool _hemi; double _deg, _min, _sec;
LatLongRep(double val)
: _hemi(0 < val),
_min(60 * std::modf(std::abs(val), &_deg)),
_sec(60 * std::modf(_min, &_min))
{ }
};
namespace manip {
struct LatLongRepIO : LatLongRep {
LatLongRepIO(double val, char const* choices) : LatLongRep(val), _display(choices) { }
private:
char const* _display;
friend std::ostream& operator<<(std::ostream& os, LatLongRepIO const& llr) {
return os << boost::format("%03d%02d%05.2f%c")
% llr._deg % llr._min % llr._sec
% (llr._display[llr._hemi]);
}
};
LatLongRepIO as_latitude (double val) { return { val, "WE" }; }
LatLongRepIO as_longitude(double val) { return { val, "NS" }; }
}
}
#include <iostream>
int main() {
using namespace helpers::manip;
for(double value : { 47.25346, 13.984364, -0.1233453, -44.3 })
std::cout << as_latitude(value) << "\t" << as_longitude(value) << "\n";
}
打印
0471512.46E 0471512.46S
0135903.71E 0135903.71S
0000724.04W 0000724.04N
0441760.00W 0441760.00N