【问题标题】:C++ algorithm for applying a function to consecutive elements将函数应用于连续元素的 C++ 算法
【发布时间】:2016-09-16 04:25:37
【问题描述】:

有没有更简单的方法来写这个,例如通过使用 STL 或 boost 算法?

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container
std::vector<int> result;
std::transform(v.begin(), v.end() - 1, // (0, 1, 2)
               v.begin() + 1,          // (1, 2, 3)
               std::back_inserter(result),
               [](int a, int b){ return a + b; }); // any binary function
// result == { 1, 3, 5 }

【问题讨论】:

  • @erip 迭代器和反向迭代器不是兼容的类型。 v.end()-1v.rbegin() 指向同一个元素,但无法比较。
  • @interjay 哎呀。 :)
  • 似乎这里已经提出了一个未回答的讨论:stackoverflow.com/questions/19927563/… 另外,我确定我已经看到了一篇带有实现的博客文章,如果可以的话,我会发布它作为答案找到它...

标签: c++ algorithm boost stl


【解决方案1】:

我建议使用 for 循环:

for(std::vector::size_type i = 0; i < v.size() - 1; i++)
    result.push_back(v[i] + v[i+1])

一个更通用的双向迭代器循环:

// let begin and end be iterators to corresponding position
// let out be an output iterator
// let fun be a binary function
for (auto it = begin, end_it = std::prev(end); it != end_it; ++it)
   *out++ = fun(*it, *std::next(it));

我们可以更进一步,为前向迭代器编写一个循环:

if(begin != end) {
    for (auto curr = begin,
         nxt = std::next(begin); nxt != end; ++curr, ++nxt) {
        *out++ = fun(*curr, *nxt);
    }
}

最后,输入迭代器的算法。但是,这要求值类型是可复制的。

if(begin != end) {
    auto left = *begin;
    for (auto it = std::next(begin); it != end; ++it) {
        auto right = *it;
        *out++ = fun(left, right);
        left = right;
    }
}

【讨论】:

  • @FelixDombek 我为非随机访问迭代器添加了一个循环(不再那么漂亮了)。
  • v.end() - 1 也需要随机访问,因此您应该改用std::prev
  • @interjay 没错。虽然,这仍然不适用于前向迭代器:(
  • 当容器为空时,您的第三个代码 sn-p 失败。 (超过end
  • 为了准确的文档,“任何迭代器的循环”仅适用于前向迭代器或更好的迭代器,不适用于单纯的输入迭代器。
【解决方案2】:

可以使用binary version of std::transform

std::adjacent_find/std::adjacent_difference 算法可能会被滥用。

【讨论】:

  • 与我的算法的不同之处在于adjacent_difference 将第一个元素复制到未修改的输出序列中。这实际上非常愚蠢,因为它引入了兼容输入和输出类型的需求。 ://
【解决方案3】:

std::adjacent_difference 正是为此,但正如您所提到的,它将第一个元素复制到结果中,这是您不想要的。 使用 Boost.Iterator,很容易创建一个 back_inserter,它会丢弃第一个元素。

#include <boost/function_output_iterator.hpp>

template <class Container>
auto mybackinsrtr(Container& cont) {
    // Throw away the first element
    return boost::make_function_output_iterator(
            [&cont](auto i) -> void { 
              static bool first = true;
              if (first)
                first = false;
              else
                cont.push_back(i);
            });
}

然后你可以#include &lt;boost/range/numeric.hpp&gt; 并这样做:

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container
std::vector<int> result;
boost::adjacent_difference(v, mybackinsrtr(result), std::plus<>{}); // any binary function

See it on ideone


当您希望二进制函数返回不同的类型(例如字符串)时,上述解决方案将不起作用,因为即使插入 cont.push_back(i) 从未调用复制的元素,它仍然必须编译,它不会去。

因此,您可以改为创建一个 back_inserter,它会忽略与进入容器不同类型的任何元素。这将忽略第一个复制的元素,并接受其余的。

template <class Container>
struct ignore_insert {
    // Ignore any insertions that don't match container's type
    Container& cont;
    ignore_insert(Container& c) : cont(c) {}
    void operator() (typename Container::value_type i) {
        cont.push_back(i);
    }
    template <typename T>
    void operator() (T) {}
};

template <class Container>
auto ignoreinsrtr(Container& cont) {
    return boost::make_function_output_iterator(ignore_insert<Container>{cont});
}

那么你可以类似地使用它。

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container
std::vector<std::string> result;
boost::adjacent_difference(v, ignoreinsrtr(result), [](int a, int b){ return std::to_string(a+b); });

On ideone

【讨论】:

  • 很好,但是对于会改变迭代器类型的仿函数([](int a, int b){std::to_string(a+b);})仍然不起作用,但是这个功能可以放在输出迭代器中,消除对任何仿函数的需要在这里,你可以使用std::copy
  • @FelixDombek:我不确定为什么这不起作用。您可以处理 like I did here 的特殊情况,将 lambda 反向插入器替换为忽略 ints 并插入 strings 的结构。
  • 它不适用于adjacent_difference,因为它将第一个元素复制到结果范围而不是应用仿函数。所以——因为结果迭代器需要能够接受源类型的元素——仿函数也不能有任何不兼容的返回类型。
  • @FelixDombek:它不会检查,至少在 libstdc++ 中是这样。它只是将参数 binary_op 的结果分配给输出迭代器:*++__result = __binary_op(__tmp, __value); 这就是为什么它适用于我在评论中链接的示例。
【解决方案4】:

我会编写您自己的算法来将函子应用于容器中的每对元素。

(无耻的宣传)在我今年的 ACCU 演讲中,“STL Algorithms – How to Use Them and How to Write Your Own”展示了如何写一个这样的。我叫它adjacent_pair(大约25:00进入视频)

template <typename ForwardIterator, typename Func>
void adjacent_pair(ForwardIterator first, ForwardIterator last, Func f)
{
    if (first != last)
    {
        ForwardIterator trailer = first;
        ++first;
        for (; first != last; ++first, ++trailer)
            f(*trailer, *first);
    }
}

【讨论】:

  • 好吧,如果你真的想写我自己的算法,我不会阻止你:o)
【解决方案5】:

Stephan T. Lavavej 在这里写了一个很好的 adjacent_iterator 类:

How do I loop over consecutive pairs in an STL container using range-based loop syntax?

这里也可以使用。

【讨论】:

    猜你喜欢
    • 2021-05-04
    • 2018-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-17
    • 1970-01-01
    • 1970-01-01
    • 2018-12-11
    相关资源
    最近更新 更多