| |
 | Posted by Ali Çehreli in reply to Salih Dincer | Permalink Reply |
|
Ali Çehreli 
Posted in reply to Salih Dincer
| On 1/19/22 04:51, Salih Dincer wrote:
> Is it okay to swap places instead of throwing an error?
I would be happier if my potential mistake is caught instead of the library doing something on its own.
> Let's also
> implement BidirectionalRange, if okay.
I had started experimenting with that as well. The implementation below does not care if popFront() or popBack() are called on empty ranges. The programmer must be careful. :)
> "Does it reverse the result
> in case ```a > b``` like we
> did with foreach_reverse()"
No, it should not reverse the direction that way because we already have retro. :)
inclusiveRange(1, 10).retro;
Improving the range as a BidirectionalRange requires three more functions: save(), back(), and popBack().
I am also removing the silly 'const' qualifiers from member functions because a range object is supposed to be mutated. I came to that conclusion after realizing that save() cannot be 'const' because it break isForwardRange (which is required by isBidirectionalRange). Ok, I will change all 'const's to 'inout's in case someone passes an object to a function that takes by 'const' and just applies e.g. empty() on it.
And adding length() was easy as well.
Finally, I have provided property functions instead of allowing direct access to members.
struct InclusiveRange(T) {
import std.format : format;
T first_;
T last_;
bool empty_;
this(U)(in U first, in U last)
in (first <= last, format!"Invalid range: [%s,%s]."(first, last)) {
this.first_ = first;
this.last_ = last;
this.empty_ = false;
}
T front() inout {
return first_;
}
bool empty() inout {
return empty_;
}
void popFront() {
if (first_ == last_) {
empty_ = true;
} else {
++first_;
}
}
auto save() inout {
return this;
}
T back() inout {
return last_;
}
void popBack() {
if (first_ == last_) {
empty_ = true;
} else {
--last_;
}
}
size_t length() inout {
return last_ - first_ + 1 - empty_;
}
}
auto inclusiveRange(T)(in T first, in T last) {
return InclusiveRange!T(first, last);
}
unittest {
// Invalid range should throw
import std.exception : assertThrown;
assertThrown!Error(inclusiveRange(2, 1));
}
unittest {
// Should not be possible to have an empty range
import std.algorithm : equal;
auto r = inclusiveRange(42, 42);
assert(!r.empty);
assert(r.equal([42]));
}
unittest {
// Should be able to represent all values of a type
import std.range : ElementType;
import std.algorithm : sum;
auto r = inclusiveRange(ubyte.min, ubyte.max);
static assert(is(ElementType!(typeof(r)) == ubyte));
assert(r.sum == (ubyte.max * (ubyte.max + 1)) / 2);
}
unittest {
// Should produce the last value
import std.algorithm : sum;
assert(inclusiveRange(1, 10).sum == 55);
}
unittest {
// Should work with negative values
import std.algorithm : equal;
assert(inclusiveRange(-3, 3).equal([-3, -2, -1, 0, 1, 2, 3]));
assert(inclusiveRange(-30, -27).equal([-30, -29, -28, -27]));
}
unittest {
// length should be correct
import std.range : enumerate, iota;
enum first = 5;
enum last = 42;
auto r = inclusiveRange(first, last);
// Trusting iota's implementation
size_t expectedLength = iota(first, last).length + 1;
size_t i = 0;
do {
assert(r.length == expectedLength);
r.popFront();
--expectedLength;
} while (!r.empty);
}
unittest {
// Should provide the BidirectionalRange interface
import std.range : retro;
import std.algorithm : equal;
auto r = inclusiveRange(1, 10);
assert(!r.save.retro.equal(r.save));
assert(r.save.retro.retro.equal(r.save));
}
void main() {
import std.stdio;
import std.range;
writeln(inclusiveRange(1, 10));
writeln(inclusiveRange(1, 10).retro);
auto r = inclusiveRange(1, 11);
while (true) {
writefln!"%s .. %s length: %s"(r.front, r.back, r.length);
r.popFront();
if (r.empty) {
break;
}
r.popBack();
if (r.empty) {
break;
}
}
}
Ali
|