patterncppMinor
Pythonish integer range in C++
Viewed 0 times
rangeintegerpythonish
Problem
Python has that
>>> range(10, -20, -3)
[10, 7, 4, 1, -2, -5, -8, -11, -14, -17]
I have this C++ implementation of exactly the same facility:
range.h:
With demo main.cpp:
```
#include
#include "range.h"
using std::cin;
using std::cout;
using std
range:>>> range(10, -20, -3)
[10, 7, 4, 1, -2, -5, -8, -11, -14, -17]
I have this C++ implementation of exactly the same facility:
range.h:
/*
* File: range.h
* Author: Rodion "rodde" Efremov
* Version: (Nov 30, 2015)
*/
#ifndef RANGE_H
#define RANGE_H
#include
namespace coderodde {
template
class range {
private:
Int m_start;
Int m_end;
Int m_step;
class range_iterator {
private:
Int m_value;
Int m_step;
size_t m_count;
public:
range_iterator(Int value,
Int step,
size_t count) : m_value{value},
m_step{step},
m_count{count} {}
int operator*() { return m_value; }
bool operator!=(range_iterator& other) { return m_count != other.m_count; }
void operator++() { m_value += m_step; ++m_count; }
};
public:
range(Int start, Int end, Int step) : m_start{start},
m_end{end},
m_step{step} {}
range(Int start, Int end) : range(start, end, 1) {}
range_iterator begin() { return range_iterator(m_start, m_step, 0); }
range_iterator end() {
if (m_step == 0) throw std::runtime_error("The step is zero.");
if (m_start 0)
{
return range_iterator(0, 0, 0);
}
m_step = -m_step;
return range_iterator(m_start, m_step,
(m_start - m_end) / m_step +
(((m_start - m_end) % m_step) ? 1 : 0));
}
}
};
}
#endif /* RANGE_H */With demo main.cpp:
```
#include
#include "range.h"
using std::cin;
using std::cout;
using std
Solution
Keeping a Count
You implemented
Wrong Type
Usage
This is awkward:
Rather than having it be up to the user to provide the correct type, you could instead take advantage of template deduction:
You'll have to rename your class template to get this to work. Even better would be to make the function a
Keep
Once we rewrite
This means that
Other Overloads
Python also allows for something like
Iterator Interface
Even though it won't be fully utilized in the simple example of
Additionally, you should inherit from
For example, it'd be nice to support the following:
which currently will fail to compile.
You implemented
range_iterator to keep a count which you use for equality comparisons. This works, but involves a really awkward formula for getting the count right. Instead, it would be simpler to just round end to be one past the last one, so that range(0, 11, 2) sets start to 0 and end to 12. This would make range_iterator only require two members, and operator!= to just compare the m_value's.Wrong Type
range_iterator dereferences to int, but should be Int.Usage
This is awkward:
for (auto i : range<>(start, end, step))
// ^^^Rather than having it be up to the user to provide the correct type, you could instead take advantage of template deduction:
template
Range range(Int start, Int end, Int step) { ... }You'll have to rename your class template to get this to work. Even better would be to make the function a
friend of the class, and make the constructors private. Keep
begin() and end() simpleOnce we rewrite
range() to be a function template that returns an object, you can move all of the logical checking into it:template
Range range(Int start, Int end, Int step) {
if (step == 0) throw ...;
if (start <= end) {
if (step < 0) {
return {0,0,0};
}
return {start, (end + step - 1) / step * step};
}
else {
// etc.
}
}This means that
begin() just returns range_iterator{m_start, m_step} and end() just returns range_iterator{m_end, m_step}. Other Overloads
Python also allows for something like
range(10), so that's just:template
Range range(Int end) {
return {0, end, 1};
}Iterator Interface
Even though it won't be fully utilized in the simple example of
for (auto i : range(10)), you should prefer to provide a complete interface for range_iterator. You're missing operator== and postfix-increment. Prefix-increment (and postfix-increment) should return references to this. Additionally, you should inherit from
std::iterator just in case somebody wants to use range() as a normal container somewhere else. For example, it'd be nice to support the following:
auto r = range(10);
std::vector v(r.begin(), r.end());which currently will fail to compile.
Code Snippets
for (auto i : range<>(start, end, step))
// ^^^template <typename Int>
Range<Int> range(Int start, Int end, Int step) { ... }template <typename Int>
Range<Int> range(Int start, Int end, Int step) {
if (step == 0) throw ...;
if (start <= end) {
if (step < 0) {
return {0,0,0};
}
return {start, (end + step - 1) / step * step};
}
else {
// etc.
}
}template <class Int>
Range<Int> range(Int end) {
return {0, end, 1};
}auto r = range(10);
std::vector<int> v(r.begin(), r.end());Context
StackExchange Code Review Q#112363, answer score: 8
Revisions (0)
No revisions yet.