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

Reply via email to