【问题标题】:Combining C++ standard algorithms by looping only once通过仅循环一次来组合 C++ 标准算法
【发布时间】:2012-12-12 20:53:32
【问题描述】:

我目前已启动并运行此代码:

string word="test,";
string::iterator it = word.begin();
for (; it != word.end(); it++)
{
    if (!isalpha(*it)) {
        break;
    }
    else {
       *it = toupper(*it);
    }
}
word.erase(it, word.end());
// word should now be: TEST

我想让它更紧凑和可读:

  1. 组合现有的标准 C++ 算法 (*)
  2. 只执行一次循环

(*) 我假设结合现有算法会使我的代码更具可读性...

另一种解决方案

除了按照 jrok 的建议定义自定义 transform_until 算法之外,还可以定义自定义迭代器适配器,该适配器将使用底层迭代器进行迭代,但通过在返回之前修改底层引用来重新定义 operator*() . 类似的东西:

template <typename Iterator, typename UnaryFunction = typename Iterator::value_type (*)(typename Iterator::value_type)>
class sidefx_iterator: public std::iterator<
                         typename std::forward_iterator_tag,
                         typename std::iterator_traits<Iterator>::value_type,
                         typename std::iterator_traits<Iterator>::difference_type,
                         typename std::iterator_traits<Iterator>::pointer,
                         typename std::iterator_traits<Iterator>::reference >
{
  public:
    explicit sidefx_iterator(Iterator x, UnaryFunction fx) : current_(x), fx_(fx) {}

    typename Iterator::reference operator*() const { *current_ = fx_(*current_); return *current_; }
    typename Iterator::pointer operator->() const { return current_.operator->(); }
    Iterator& operator++() { return ++current_; }
    Iterator& operator++(int) { return current_++; }
    bool operator==(const sidefx_iterator<Iterator>& other) const { return current_ == other.current_; }
    bool operator==(const Iterator& other) const { return current_ == other; }
    bool operator!=(const sidefx_iterator<Iterator>& other) const { return current_ != other.current_; }
    bool operator!=(const Iterator& other) const { return current_ != other; }
    operator Iterator() const { return current_; }

  private:
    Iterator current_;
    UnaryFunction fx_;
};

当然,这仍然很原始,但它应该给出想法。 使用上述适配器,我可以编写以下内容:

word.erase(std::find_if(it, it_end, std::not1(std::ref(::isalpha))), word.end());

预先定义以下内容(可以通过一些模板魔术来简化):

using TransformIterator = sidefx_iterator<typename std::string::iterator>;
TransformIterator it(word.begin(), reinterpret_cast<typename std::string::value_type(*)(typename std::string::value_type)>(static_cast<int(*)(int)>(std::toupper)));
TransformIterator it_end(word.end(), nullptr);

如果标准包含这样的适配器,我会使用它,因为这意味着它完美无缺,但由于情况并非如此,我可能会保持我的循环不变。

这样的适配器将允许重用现有算法并以不同方式混合它们,这在今天是不可能的,但它也可能有缺点,我现在可能会忽略...

【问题讨论】:

  • 在我当前的代码中有一个循环。我的观点是,重写后仍然会有一个循环。
  • 基于!isalpha(*it)的过早出走是我认为唯一可能阻止你实现我认为你正在寻找的东西,老实说,任何可能为你做的事情(我可以'不会立即看到任何东西)可能会如此令人费解,你的清晰度因素会消失在窗外。我可能会坚持你所拥有的。
  • 感谢大家鼓舞人心的回答。我现在将坚持我当前的代码。我相信boost::transform_iterator 是我正在寻找的最接近的东西。这个用例将被一个“副作用”迭代器适配器覆盖,使用如下:word.erase(std::find_if(sidefx_iterator(word.begin(), ::toupper), word.end(), std::not1(::isalpha)), word.end());

标签: c++ algorithm stl c++11 std


【解决方案1】:

我认为没有一种干净的方法可以使用单一的标准算法来做到这一点。我所知道的没有一个采用谓词(您需要一个谓词来决定何时提前中断)并允许修改源序列的元素。

如果您真的想以“标准”方式进行,您可以编写自己的通用算法。姑且称之为吧,嗯,transform_until

#include <cctype>
#include <string>
#include <iostream>

template<typename InputIt, typename OutputIt,
         typename UnaryPredicate, typename UnaryOperation>
OutputIt transform_until(InputIt first, InputIt last, OutputIt out,
                         UnaryPredicate p, UnaryOperation op)
{
    while (first != last && !p(*first)) {
        *out = op(*first);
        ++first;
        ++out;
    }
    return first;
}

int main()
{
    std::string word = "test,";
    auto it =
    transform_until(word.begin(), word.end(), word.begin(),
                    [](char c) { return !::isalpha(static_cast<unsigned char>(c)); },
                    [](char c) { return ::toupper(static_cast<unsigned char>(c)); });
    word.erase(it, word.end());
    std::cout << word << '.';
}

这是否比你所拥有的更好是有争议的 :) 有时一个简单的 for 循环是最好的。

【讨论】:

  • +1,这和我认为的 OP 一样好。虽然他可能应该坚持计划 A 并保持他的循环,但这个答案是一个非常好的概念到实现的编写自定义容器转换器,适当的名称,并且仍然回答了 OP 的问题。即使OP不使用它,仍然是一个很好的答案。
  • 我接受了这个答案,因为生成的代码简短易读,并且表达了一种通用的算法,通常有用。所有现有的 STL 算法都有一个固定端迭代器,并且需要在整个容器中循环。我认为能够有条件结束是一种普遍需要。
【解决方案2】:

在更好地理解您的问题后,我有了一个可能可行的想法,但需要Boost

您可以使用transform_iterator 对所有字符调用toupper,并将其用作find_ifremove_if 的输入迭代器。不过,我对 Boost 不够熟悉,无法提供示例。

正如@jrok 指出的那样,transform_iterator 只会在迭代期间转换值,而不会实际修改原始容器。为了解决这个问题,您需要使用remove_copy_if 之类的东西复制到一个新的序列,而不是在相同的序列上操作。只要谓词不正确,就会复制,因此需要std::not1。这将取代 remove_if 的情况。

使用std::copy 进行复制,直到std::find_if 返回的迭代器才能使另一种情况起作用。

最后,如果你的输出字符串是空的,它需要一个std::inserter类型的迭代器来输出。

【讨论】:

  • 它循环了两次 - OP 想避免这种情况。
  • 看起来你的实现中有两个循环......我错了吗?
  • @MariJosé 啊,我现在更好地理解了你的问题,是的,你是对的,有 2 个循环。
  • @MariJosé 是不是更接近你想要的新想法?
  • Roberto,当使用 boost::transform_iterator 时,这两个循环总体上仍在 O(N) 内,因为它们不是两个 完整 循环,除非我遗漏了一些明显的东西
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-23
  • 1970-01-01
  • 2015-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多