"使用算法;不要为多步骤逻辑编写代码"?

“use algorithms; don’t write code” for multi-step logic?

本文关键字:quot 代码 算法      更新时间:2023-10-16

这个问题让我觉得"根本不要使用显式循环!使用 STL/Boost 算法"但仔细观察,我注意到有一个adjacent_differenceaccumulate和 Boost 在某处有一个zip

while (i<l-1){
ans = ans + max(abs(X[i]-X[i+1]), abs(Y[i]-Y[i+1]));
i++;
}

它们根本不会堆叠在一起,但每个只能自己完成整个传递。 因此,以直接的方式使用它们将需要许多包含部分结果的中间副本。 也就是说,adjacent_difference编写一个新的向量,即zip的参数等。

现在在"现代"C++的口头禅是,我们不应该"编写代码",也很少需要明确的循环。

但我的真实世界经验更像是这种情况:要做的事情不是一个简单的步骤,结果也不是像那样批量收集的。

那么,如何简化的方式编写它,引用要执行的操作,但不在范围上循环,也不显式拉取每个元素。

Boost迭代器滤波器通常可以构建更复杂的逻辑,最终进入驱动循环(因此没有中间结果的完整副本(,但此示例有几个功能说明了我发现 Boost 范围滤波器的限制! 设置它比仅仅编写for循环更复杂!

所以,如果"谁是谁"C++说我们应该能够用新的语言和库功能来写,你怎么做,一个比他们在讲座中展示的更真实的简单案例?

仅使用增强范围,您想编写:

auto ans = boost::accumulate(
boost::combine(X|differential|abs, Y|differential|abs),
0ull,
[](auto accum, auto const& xy) { return accum + std::max(boost::get<0>(xy), boost::get<1>(xy)); }
);

这可以通过一点点的手工工作来实现。


abs

用于绝对值的范围适配器

我在这里作弊了一点,因为我不想在这里费尽心思创建一个真正的适配器系列:

auto abs = transformed([](auto x) { return std::abs(x); });

就这样。


differential

用于adjacent_difference的系列适配器

请注意,我没有复制std::adjacent_difference的行为,因为它在结果中包含第一个源值(我们不想要(。相反,我们想要 n-1 个差分值。

我在文档中采用了 §3.1 中的说明,并结合了一些减少打字iterator_facade

namespace boost { namespace adaptors {
template <typename R>
struct differential_range {
public:
using base_iterator = typename boost::range_iterator<R const>::type;
struct iterator : boost::iterator_facade<iterator, int, typename boost::iterator_category<base_iterator>::type, int>
{
iterator(base_iterator raw) : _raw(raw) {}
private:
friend class boost::iterator_core_access;
bool equal(iterator other) const { return _raw == other._raw; }
void decrement() { --_raw; }
void increment() { ++_raw; }
int dereference() const { return *next() - *_raw; }
ptrdiff_t distance_to(iterator other) const { return std::distance(_raw, other._raw); }
base_iterator _raw;
base_iterator next() const { return std::next(_raw); }
};
using const_iterator = iterator;
differential_range(R &r) : _b(boost::begin(r)), _e(boost::end(r)) {
if (_b != _e)
--_e;
}
const_iterator begin() const { return _b; }
const_iterator end()   const { return _e; }
iterator begin() { return _b; }
iterator end()   { return _e; }
private:
iterator _b, _e;
};

没什么特别的。现在我们需要安装转发器,以便我们可以使用| differential语法速记:

namespace detail {
struct adjacent_difference_forwarder {
};
}
template <class BidirectionalRng>
inline differential_range<BidirectionalRng> operator|(BidirectionalRng &r,
detail::adjacent_difference_forwarder) {
return differential_range<BidirectionalRng>(r);
}
template <class BidirectionalRng>
inline differential_range<const BidirectionalRng> operator|(const BidirectionalRng &r,
 detail::adjacent_difference_forwarder) {
return differential_range<const BidirectionalRng>(r);
}
static const detail::adjacent_difference_forwarder differential = {};
} }

演示

这个演示程序测试了100个不同的随机范围以获得正确的结果:它从问题(foo(和范围化版本(foo_ex(运行原始算法并验证结果。

住在科里鲁

#include <vector>
#include <vector>
#include <algorithm>
#include <cassert>
template <typename Range>
int64_t foo(Range const& X, Range const& Y) {
assert(Y.size() == X.size());
size_t const l = X.size();
int64_t ans = 0;
for (size_t i=0; i<l-1; ++i) {
ans = ans + std::max(std::abs(X[i]-X[i+1]), std::abs(Y[i]-Y[i+1]));
}
return ans;
}
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/combine.hpp>
#include <boost/range/numeric.hpp>
#include <boost/iterator/iterator_facade.hpp>
using namespace boost::adaptors;
namespace boost { namespace adaptors {
template <typename R>
struct differential_range {
public:
using base_iterator = typename boost::range_iterator<R const>::type;
struct iterator : boost::iterator_facade<iterator, int, typename boost::iterator_category<base_iterator>::type, int>
{
iterator(base_iterator raw) : _raw(raw) {}
private:
friend class boost::iterator_core_access;
bool equal(iterator other) const { return _raw == other._raw; }
void decrement() { --_raw; }
void increment() { ++_raw; }
int dereference() const { return *next() - *_raw; }
ptrdiff_t distance_to(iterator other) const { return std::distance(_raw, other._raw); }
base_iterator _raw;
base_iterator next() const { return std::next(_raw); }
};
using const_iterator = iterator;
differential_range(R &r) : _b(boost::begin(r)), _e(boost::end(r)) {
if (_b != _e)
--_e;
}
const_iterator begin() const { return _b; }
const_iterator end()   const { return _e; }
iterator begin() { return _b; }
iterator end()   { return _e; }
private:
iterator _b, _e;
};
namespace detail {
struct adjacent_difference_forwarder {
bool absolute = false;
};
}
template <class BidirectionalRng>
inline differential_range<BidirectionalRng> operator|(BidirectionalRng &r,
detail::adjacent_difference_forwarder) {
return differential_range<BidirectionalRng>(r);
}
template <class BidirectionalRng>
inline differential_range<const BidirectionalRng> operator|(const BidirectionalRng &r,
 detail::adjacent_difference_forwarder) {
return differential_range<const BidirectionalRng>(r);
}
static const detail::adjacent_difference_forwarder differential = {};
} }
template <typename Range>
int64_t foo_ex(Range const& X, Range const& Y) {
auto abs = transformed([](auto x) { return std::abs(x); });
return boost::accumulate(
boost::combine(X|differential|abs, Y|differential|abs),
0ull,
[](auto accum, auto const& xy) { return accum + std::max(boost::get<0>(xy), boost::get<1>(xy)); }
);
}
#include <iostream>
#include <random>
int main() {
std::vector<int> x(100), y=x;
std::mt19937 rng { std::random_device{}() };
std::uniform_int_distribution<int> dist(-50, 50);
auto gen = [&] { return dist(rng); };
int n = 100;
while (n--) {
std::generate(x.begin(), x.end(), gen);
std::generate(y.begin(), y.end(), gen);
auto ans = foo(x,y),
ans_ex = foo_ex(x,y);
std::cout << ans << " " << ans_ex << "t" << std::boolalpha << (ans==ans_ex) << "n";
}
}

打印正确的结果,例如:

4769 4769   true
5027 5027   true
4471 4471   true
4495 4495   true
4774 4774   true
4429 4429   true
4331 4331   true
4951 4951   true
4095 4095   true
...

想法, 总结

你可能会想象differential更像...adjacent_transformed,你可以说的地方

auto differential = adj_transformed([](auto x, auto y) { return y - x; });

这将使代码重用变得更加容易,不需要任何新的相邻二进制转换的全范围适配器。有关指导,请参见 §3.2。

它现在可能对实际生产代码有帮助,也可能没有帮助,但这似乎可以通过 v3 Range 库直接解决,v3 Range 库是最终将成为标准库一部分的原型。

范围相对于迭代器的最大优势是它们的可组合性。它们允许一种函数式编程,其中数据通过一系列组合器来操作。此外,组合器可以是懒惰的,只在请求答案时才做工作,并且纯粹是函数式的,不会改变原始数据。

此介绍页面上的第一个示例是将管道操作放在一起,记录的第一件事(按字母顺序排列的事故(是view::adjacent_filter

我还没有安装并尝试过它,并学会了如何编写这个特定示例,但我确实认为这是缺失的部分。 我只是希望它在今天的代码中足够可用。