31

Is it possible to rewrite this raw loop:

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

or the even more cryptic:

for (auto i = v.begin()+1; i<v.end(); ++i) {
  (*i) *= *(i-1);
}

(and similar, maybe accessing also v[i-2], ...) in a more STLish way?

Are there other forms which are equal or better (both in style and performances) than the ones above?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
DarioP
  • 5,377
  • 1
  • 33
  • 52

4 Answers4

40

The most STLish way I can imagine:

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

Example:

#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, " "));
}

Output:

1 2 6 24 

Live demo link.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • 4
    +1 Excellent find. One of the less exposed algorithms. In fact, it's not even in ``... :-( – Kerrek SB Sep 03 '14 at 09:26
  • 1
    @KerrekSB In other words, one has to look up what `partial_sum` does when reading code, is that right? – Maxim Egorushkin Sep 03 '14 at 09:28
  • 3
    @MaximYegorushkin: It's a pretty well known, standard term from basic mathematics. The kind of mathematics you would hope your programmer team is familiar with. You just need to recognize enough to get an idea whether you need to dig deeper when reading or whether you're happy to move on. – Kerrek SB Sep 03 '14 at 09:29
  • 10
    or if you're only interested in the end result, use [std::accumulate](http://en.cppreference.com/w/cpp/algorithm/accumulate) – Sander De Dycker Sep 03 '14 at 09:29
  • 1
    That's cool, but there are no ways to extend this to a ternary operation, right? I have to go for the raw loop in that case... – DarioP Sep 03 '14 at 09:30
  • @DarioP: STL algorithms accept either unary or binary functor, I can't imagine myself a "generic algorithm" that operates on ranges and accepts 3 arguments obtained from that range, but it always depends on what you want to achieve, sometimes you may adjust generic algorithm to your needs. – Piotr Skotnicki Sep 03 '14 at 09:46
  • 2
    @KerrekSB I find it a bit ironic that `partial_sum` does not actually sum here. – Maxim Egorushkin Sep 03 '14 at 09:55
  • 1
    @DarioP I'm adding this solution here: `for (size_t i = 1; i < v.size(); ++i) v[i] = std::accumulate(std::begin(v) + i, std::begin(v) + i + 1, v[i-1], std::multiplies());`. Also: going 3-way (not in the bad sense :) means you'll have to check for having enough elements. It would probably make sense to write your own code in that case. – Marco A. Sep 03 '14 at 09:58
  • 12
    using ADL on `begin()/end()` and using C++14 transparant functors, you can write it as a one-liner: `std::partial_sum(begin(v), end(v), begin(v), std::multiplies<>());` – TemplateRex Sep 03 '14 at 10:23
  • 1
    @MaximYegorushkin This is because in mathematics there is a concept of a "partial sum" (or "series") that uses the addition operator at each step. (Which is what the `std::partial_sum` function does by default, if you don't pass a fourth argument.) But there isn't a mathematics term for the same idea with a different operator, such as multiplication. So the STL just uses the same name. – Timothy Shields Sep 03 '14 at 14:53
  • 1
    @TimothyShields sure there is a term: `scan` or `fold`, and it's heavily used in functional programming – TemplateRex Sep 03 '14 at 19:00
  • 5
    @TimothyShields: "scan" is a generalization of `partial-sum`, whereas "fold" is a generalization of `accumulate`d-sum: http://en.wikipedia.org/wiki/Prefix_sum#Scan_higher_order_function – Matt Sep 03 '14 at 19:04
  • @TemplateRex "there isn't a mathematics term" There is of course a functional programming term. – Timothy Shields Sep 03 '14 at 19:04
  • 1
    Does anyone else think this is actually harder to read than the original `vector` indexing version? – user253751 Sep 04 '14 at 04:50
  • 2
    @TimothyShields I do not think that you guys would have to waste so much ink elaborating and evangelizing, if it was just a straightforward and immediately clear to the reader `for` loop. – Maxim Egorushkin Sep 04 '14 at 08:25
  • 2
    @immibis I was learning c++ 4 years ago. Today I cant understand half of code posted here. I am still programming in C. – Kubuxu Sep 09 '14 at 12:39
10

You can do that with std::transform, the overload that takes two input sequences:

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; }
    );

But the hand-coded loop is much more readable.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 4
    You could use `std::multiplies` here, too. – Kerrek SB Sep 03 '14 at 09:27
  • 2
    @KerrekSB Probably, if I wanted to make it even more cryptic... What would be the advantage of using `std::multiplies` over lambda? – Maxim Egorushkin Sep 03 '14 at 09:29
  • 4
    Ah, of course. We should be able to see what the code is really doing. All this abstraction is just obscuring the code :-) – Kerrek SB Sep 03 '14 at 09:31
  • 6
    @KerrekSB Many problems in software engineering can be solved by adding another layer of abstraction. Apart from the problem of having too many layers of abstraction. – Maxim Egorushkin Sep 03 '14 at 09:35
  • 8
    instead of `begin(container) + 1`, I'd prefer `std::next(begin(container))` so that it would also work with `std::list` – TemplateRex Sep 03 '14 at 10:30
7

If you want a generic way to do sliding windows rather than a non-transferable STL-ish way to answer your particular problem, you could consider the following ridiculous nonsense:

#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};
}

Example usage:

#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;
  }
}
STU
  • 121
  • 5
  • 2
    This kind of solution looks very nice, producing a wonderful main. However I have the feeling that is not totally overhead free... – DarioP Sep 04 '14 at 06:59
  • 1
    @DarioP Indeed, the original code was something like 100 times slower than the obvious way. I have updated the code to fix it: by allowing `*it` to return a temporary rather than a (useless) reference, we can avoid the tuple allocation, and end up generating essentially identical assembly to the naive raw loop under -O3. [Live demo here](http://ideone.com/aHE4Zu). – STU Sep 04 '14 at 11:36
  • 2
    When iterating over a window of size N and M elements, this does 2(M-N)+NM iterator increments? The ideal version would only do N iterator increments. I suppose with a simple array (or vector), such operations get elided into simple pointer arithmetic, but with a `std::map` they probably would not be. There isn't really an iterator trait that tells you which is better: I guess random access could be used as a proxy for "easier to advance than store". – Yakk - Adam Nevraumont Sep 04 '14 at 13:09
  • You're right, of course. There's certainly a trade-off between advance and store; handling both would make the code considerably more complicated though so probably best left as an exercise for the reader. FWIW, sliding window makes a lot more sense on something like a vector, so defaulting to advance over store probably isn't too unreasonable. – STU Sep 04 '14 at 14:39
4

This is an implementation that keeps an array of iterators of size N under the hood to produce a sliding window:

namespace details {
  template<unsigned...>struct indexes { using type=indexes; };
  template<unsigned max, unsigned... is>struct make_indexes:make_indexes<max-1, max-1, is...>{};
  template<unsigned... is>struct make_indexes<0,is...>:indexes<is...>{};
  template<unsigned max>using make_indexes_t=typename make_indexes<max>::type;

  template<bool b, class T=void>
  using enable_if_t=typename std::enable_if<b,T>::type;
  struct list_tag {};
  struct from_iterator_tag {};

  template<unsigned N, class Iterator>
  struct iterator_array {
  private:
    std::array<Iterator,N> raw;
    size_t index = 0;

    static Iterator to_elem(Iterator& it, Iterator end, bool advance=true) {
      if (it == end) return end;
      if (advance) return ++it;
      return it;
    }
    template< unsigned...Is>
    iterator_array( indexes<Is...>, from_iterator_tag, Iterator& it, Iterator end ):
      raw( {to_elem(it, end, false), (void(Is), to_elem(it,end))...} )
    {}
  public:
    Iterator begin() const { return raw[index]; }
    Iterator end() const { return std::next(raw[(index+N-1)%N]); }
    void push_back( Iterator it ) {
      raw[index] = it;
      index = (index+1)%N;
    }
    iterator_array( from_iterator_tag, Iterator& it, Iterator end ):iterator_array( make_indexes<N-1>{}, from_iterator_tag{}, it, end ) {}
    iterator_array( iterator_array const& o )=default;
    iterator_array() = default; // invalid!
    iterator_array& operator=( iterator_array const& o )=delete;
    typedef decltype(*std::declval<Iterator>()) reference_type;
    reference_type operator[](std::size_t i)const{return *(raw[ (i+index)%N ]);}
  };

  struct sentinal_tag {};

  template<class I>using value_type_t=typename std::iterator_traits<I>::value_type;

  template<class I, unsigned N>
  class slide_iterator:public std::iterator<
    std::forward_iterator_tag,
    iterator_array<N,I>,
    iterator_array<N,I>*,
    iterator_array<N,I> const&
  > {
    I current;
    mutable bool bread = false;
    typedef iterator_array<N,I> value_type;
    mutable value_type data;
    void ensure_read() const {
      if (!bread) {
        data.push_back(current);
      }
      bread = true;
    }
  public:
    slide_iterator& operator++() { ensure_read(); ++current; bread=false; return *this; }
    slide_iterator operator++(int) { slide_iterator retval=*this; ++*this; return retval; }
    value_type const& operator*() const { ensure_read(); return data; }
    bool operator==(slide_iterator const& o){return current==o.current;}
    bool operator!=(slide_iterator const& o){return current!=o.current;}
    bool operator<(slide_iterator const& o){return current<o.current;}
    bool operator>(slide_iterator const& o){return current>o.current;}
    bool operator<=(slide_iterator const& o){return current<=o.current;}
    bool operator>=(slide_iterator const& o){return current>=o.current;}
    explicit slide_iterator( I start, I end ):current(start), bread(true), data(from_iterator_tag{}, current, end) {}
    explicit slide_iterator( sentinal_tag, I end ):current(end) {}

  };
}

template<class Iterator, unsigned N>
struct slide_range_t {
  using iterator=details::slide_iterator<Iterator, N>;
  iterator b;
  iterator e;
  slide_range_t( Iterator start, Iterator end ):
    b( start, end ),
    e( details::sentinal_tag{}, end )
  {}
  slide_range_t( slide_range_t const& o )=default;
  slide_range_t() = delete;
  iterator begin() const { return b; }
  iterator end() const { return e; }
};

template<unsigned N, class Iterator>
slide_range_t< Iterator, N > slide_range( Iterator b, Iterator e ) {
  return {b,e};
}

live example

Note that the elements of your slide range are themselves iterable. A further improvement would be to specialize for random-access iterators and only store the begin/end pair in that case.

Sample use:

int main() {
    std::vector<int> foo(33);
    for (int i = 0; i < foo.size(); ++i)
        foo[i]=i;
    for( auto&& r:slide_range<3>(foo.begin(), foo.end()) ) {
        for (int x : r) {
            std::cout << x << ",";
        }
        std::cout << "\n";
    }
    // your code goes here
    return 0;
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524