STL方式在容器的循环中同时访问更多元素

Dar*_*ioP 31 c++ algorithm loops stl c++14

是否可以重写这个原始循环:

vector<double> v { ... };
for (size_t i = 1; i<v.size(); ++i) {
  v[i]*=v[i-1];
}
Run Code Online (Sandbox Code Playgroud)

或者更加神秘:

for (auto i = v.begin()+1; i<v.end(); ++i) {
  (*i) *= *(i-1);
}
Run Code Online (Sandbox Code Playgroud)

(和类似的,也许以更STLish的方式访问v [i-2],...)?

是否存在与上述形式相同或更好(在风格和表现方面)的其他形式?

Pio*_*cki 40

我能想象的最STLish方式:

std::partial_sum(std::begin(v), std::end(v),
                 std::begin(v), std::multiplies<double>());
Run Code Online (Sandbox Code Playgroud)

例:

#include <iostream>
#include <vector>
#include <iterator>
#include <numeric>
#include <functional>

int main()
{
    std::vector<double> v{ 1.0, 2.0, 3.0, 4.0 };

    std::partial_sum(std::begin(v), std::end(v),
                     std::begin(v), std::multiplies<double>());

    std::copy(std::begin(v), std::end(v),
                             std::ostream_iterator<double>(std::cout, " "));
}
Run Code Online (Sandbox Code Playgroud)

输出:

1 2 6 24 
Run Code Online (Sandbox Code Playgroud)

现场演示链接.

  • 使用上`()开始/结束()`ADL和用C++ 14 TRANSPARANT函子,可以将其写为一个班轮:`的std :: partial_sum(开始(v)中,端部(V),开始(ⅴ ),std :: multiplies <>());` (12认同)
  • 或者如果您只对最终结果感兴趣,请使用[std :: accumulate](http://en.cppreference.com/w/cpp/algorithm/accumulate) (10认同)
  • @TimothyShields:"scan"是`partial-sum`的概括,而"fold"是`accumulate`d-sum的概括:http://en.wikipedia.org/wiki/Prefix_sum#Scan_higher_order_function (5认同)
  • +1很棒的发现.一种较少暴露的算法.事实上,它甚至不在`<algorithm>`...... :-( (4认同)
  • @MaximYegorushkin:这是一个非常着名的标准术语,来自基础数学.你希望你的程序员团队熟悉的那种数学.你只需要认识到足以了解你是否需要在阅读时深入挖掘,或者你是否乐意继续前进. (3认同)
  • @KerrekSB我发现有点讽刺的是`partial_sum`实际上并没有在这里求和. (2认同)
  • @TimothyShields我不认为你们不得不浪费太多的墨水精心制作和传福音,如果它只是一个直截了当的,并且立即清楚读者`for`循环. (2认同)
  • @immibis我4年前学习c ++.今天我无法理解这里发布的一半代码.我还在用C编程. (2认同)

Max*_*kin 10

您可以使用,带有std::transform两个输入序列的重载:

int container[] = {1,2,3};
std::transform(
    std::begin(container), std::end(container) - 1,
    std::begin(container) + 1, std::begin(container) + 1,
    [](auto a, auto b) { return a * b; }
    );
Run Code Online (Sandbox Code Playgroud)

但手动编码循环更具可读性.

  • 而不是`begin(container)+ 1`,我更喜欢`std :: next(begin(container))`这样它也适用于`std :: list` (8认同)
  • @KerrekSB软件工程中的许多问题都可以通过添加另一层抽象来解决.除了有太多抽象层的问题. (6认同)
  • 你也可以在这里使用`std :: multiplies`. (4认同)
  • 啊,当然.我们应该能够看到代码真正在做什么.所有这些抽象只是模糊了代码:-) (4认同)
  • @KerrekSB可能,如果我想让它变得更加神秘......使用`std :: multiplies`优于lambda会有什么好处? (2认同)

STU*_*STU 7

如果你想要一个通用的方法来做滑动窗口而不是一个不可转移的STL-ish方式来回答你的特定问题,你可以考虑以下荒谬的废话:

#include <array>
#include <cstddef>
#include <memory>
#include <tuple>

namespace detail {
  template<std::size_t, typename>
  class slide_iterator;
}
template<std::size_t N, typename I>
detail::slide_iterator<N, I> slide_begin(const I&);
template<std::size_t N, typename I>
detail::slide_iterator<N, I> slide_end(const I&);

namespace detail {

template<std::size_t N, typename T, typename... Args>
struct repeat {
  typedef typename repeat<N - 1, T, T, Args...>::type type;
  template<typename I>
  type operator()(const I& it, Args&... args) const {
    auto jt = it;
    return repeat<N - 1, T, T, Args...>()(++jt, args..., *it);
  }
};
template<typename T, typename... Args>
struct repeat<0, T, Args...> {
  typedef std::tuple<Args&...> type;
  template<typename I>
  type operator()(const I&, Args&... args) const {
    return type(args...);
  }
};

template<std::size_t N, typename I /* forward iterator */>
class slide_iterator {
public:

  typedef slide_iterator iterator;
  typedef decltype(*I{}) reference;
  typedef typename repeat<N, reference>::type window_tuple;

  slide_iterator() = default;
  ~slide_iterator() = default;
  slide_iterator(const iterator& it) = default;
  iterator& operator=(const iterator& it) = default;

  window_tuple operator*() const {
    return repeat<N, reference>()(first_);
  }

  iterator& operator++() { // prefix
    ++first_;
    ++last_;
    return *this;
  }

  iterator operator++(int) { // postfix
    auto tmp{*this};
    operator++();
    return tmp;
  }

  friend void swap(iterator& lhs, iterator& rhs) {
    swap(lhs.first_, rhs.first_);
    swap(lhs.last_, rhs.last_);
    swap(lhs.dirty_, rhs.dirty_);
    swap(lhs.window_, rhs.window_);
  }

  friend bool operator==(const iterator& lhs, const iterator& rhs) {
    return lhs.last_ == rhs.last_;
  }

  friend bool operator!=(const iterator& lhs, const iterator& rhs) {
    return !operator==(lhs, rhs);
  }

  friend iterator slide_begin<N, I>(const I& it);
  friend iterator slide_end<N, I>(const I& it);

private:

  I first_;
  I last_; // for equality only

};

template<typename T, std::size_t N>
struct slide_helper {
  T& t;
  auto begin() -> decltype(slide_begin<N>(t.begin())) {
    return slide_begin<N>(t.begin());
  }
  auto end() -> decltype(slide_end<N>(t.end())) {
    return slide_end<N>(t.end());
  }
};

} // ::detail

// note it is undefined to call slide_begin<N>() on an iterator which cannot
// be incremented at least N - 1 times
template<std::size_t N, typename I>
detail::slide_iterator<N, I> slide_begin(const I& it) {
  detail::slide_iterator<N, I> r;
  r.first_ = r.last_ = it;
  std::advance(r.last_, N - 1);
  return r;
}

template<std::size_t N, typename I>
detail::slide_iterator<N, I> slide_end(const I& it) {
  detail::slide_iterator<N, I> r;
  r.last_ = it;
  return r;
}

template<std::size_t N, typename T>
detail::slide_helper<T, N> slide(T& t) {
  return {t};
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

#include <iostream>
#include <vector>

int main() {
  std::vector<int> v{1, 2, 3, 4};
  /* helper for
     for (auto it = slide_begin<2>(v.begin()),
               et = slide_end<2>(v.end()); it != et ... BLAH BLAH BLAH */
  for (const auto& t : slide<2>(v)) {
    std::get<1>(t) *= std::get<0>(t);
  }
  for (const auto& i : v) {
    std::cout << i << std::endl;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这种解决方案看起来非常好,产生了一个美妙的主要.但是我觉得这不是完全免费的...... (2认同)
  • 在N和M元素大小的窗口上迭代时,这会增加2(MN)+ NM迭代器的增量吗?理想版本只会执行N迭代器增量.我想用一个简单的数组(或向量),这样的操作被省略到简单的指针算术,但是使用`std :: map`它们可能不会.没有真正的迭代器特性告诉你哪个更好:我猜随机访问可以用作"比商店更容易推进"的代理. (2认同)