Skip to content
Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Exercising Ranges (part 5)

elbeno, 1 July, 20151 July, 2015

(Start at the beginning of the series if you want more context.)

Generalising iota

To pretty-print a reversible polynomial, we need to provide for reversibility of iota. So what is iota? It makes a range by repeatedly incrementing a value. What if instead of increment, we use an arbitrary function? We could do this with generate_n:

template 
std::string to_string_reverse(Rng&& r)
{
  auto s = ranges::size(r);
  auto powers = ranges::view::generate_n(
      [s] () mutable { return --s; }, s);
  return detail::to_string(
      ranges::view::zip(ranges::view::reverse(std::forward(r)), 
                        powers));
}

But this isn’t quite as clean as it could be: the lambda mutates state. An alternative is found in the Haskell Prelude:

iterate :: (a -> a) -> a -> [a] 

iterate applies a function repeatedly to an argument, feeding the result of the last iteration to the input of the next one. It’s very similar to generate, and we can write both iterate and iterate_n. The view takes a function and an initial value, and produces the sequence:

[latex] x, f(x), f(f(x)), f(f(f(x))), … [/latex]

Here’s the C++ for the skeleton of iterate_view:

template
struct iterate_view
  : view_facade, infinite>
{
private:
  friend struct range_access;
  using result_t = concepts::Function::result_t;
  semiregular_t gen_;
  semiregular_t val_;
  ...
  explicit iterate_view(G g, T&& t)
    : gen_(std::move(g)), val_(std::move(t))
  {}
  ...
};

The cursor is easily written just like generate‘s cursor: next() caches a new value, and current() returns it. Like generate, the view can only be traversed once, so the cached value is kept in the view itself rather than the iterator. And there is no end cursor.

struct cursor
{
  iterate_view *view_;
  ...
  result_t current() const
  {
    return view_->val_;
  }
  void next()
  {
    view_->next();
  }
};
void next()
{
  val_ = gen_(val_);
}

For iterate_n, the approach is almost identical, except that next() decrements a count, and size() is also provided. Using iterate_n, we can generate a decreasing sequence and use it in the reverse print function for a polynomial, which we know is a SizedRange.

template 
std::string to_string_reverse(Rng&& r)
{
  auto s = ranges::size(r);
  auto powers = ranges::view::iterate_n(
      [] (auto i) { return i - 1; }, s-1, s);
  return detail::to_string(
      ranges::view::zip(ranges::view::reverse(std::forward(r)), 
                        powers));
}

As for iterate itself, it’s potentially useful in lots more places. The obvious use cases (and test cases) are mathematical in nature, for instance my test case using the Collatz conjecture (aka Ulam conjecture, 3n+1 conjecture) sequence starting with 27:

int collatz(int n)
{
  if (n & 1) return 3*n+1;
  return n>>1;
}

DEF_TEST(Collatz27, Iterate)
{
  auto m = view::iterate(collatz, 27)
    | view::take_while([] (int n) { return n != 1; });
  EXPECT(ranges::find(m, 9232) != ranges::end(m));
  return true;
}

But there are also plenty of applications in other code, where we are updating the state of something based on its previous state: iterate separates the loop code and allows us to write a clean function that simply produces a new state from an old state. This is useful for games and simulations of all kinds. So iterate is a useful addition to the range views.

Now we can pretty-print polynomials properly, in the next part I’m going to take on the trickier subject of how to multiply them.

C++ Programming

Post navigation

Previous post
Next post

Related Posts

Exercising Ranges (part 2)

1 July, 20151 July, 2015

(Start at the beginning of the series if you want more context.) First steps with power series A power series (or polynomial, to give it a more familiar term in the case where it’s finite) is represented simply as a sequence of coefficients of increasing powers of x. This is…

Read More

A persistent myth about STL’s remove (and friends)

8 March, 201530 June, 2015

There seems to be a persistent myth about STL’s remove, remove_if, etc. Ask even a relatively experienced C++ programmer to explain this code. vector v = { 1,2,3,4,5 }; v.erase(remove_if(v.begin(), v.end(), [] (int i) { return (i & 1) == 0; }), v.end()); They’ll recognize the erase-remove idiom and correctly…

Read More

(Part of) what I do all day

12 September, 200812 September, 2008

Lately I’ve been asked what I do at work. What I really do. Well, if Great Biggary can frequently expound on his latest Maya scripts and python excursions, here goes with my explanations… Most of what I do all day is read code. Just like an author or poet will…

Read More

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

©2026 Why is a raven like a writing desk? | WordPress Theme by SuperbThemes