www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - number ranges

reply forkit <forkit gmail.com> writes:
so I'm wondering why the code below prints:

1 2 3 4

and not

1 2 3 4 5

as I would expect.

foreach (value; 1..5) writef("%s ", value);

also, why is this not possible:

int[] arr = 1..5.array;
Jan 17 2022
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 17 January 2022 at 10:24:06 UTC, forkit wrote:
 so I'm wondering why the code below prints:

 1 2 3 4

 and not

 1 2 3 4 5

 as I would expect.

 foreach (value; 1..5) writef("%s ", value);
This kind of half-open interval, which includes the lower bound but excludes the upper bound, is used in programming because it lets you write foreach (i; 0 .. array.length) writef("%s ", array[i]); ...without going past the end of the array. Edsger W. Dijkstra, a well-known academic computer scientist, has written in more detail about the advantages of this kind of interval: https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html
 also, why is this not possible:

 int[] arr = 1..5.array;
The `lower .. upper` syntax only works in foreach loops. If you want to create a range of numbers like this in another context, you must use the library function std.range.iota: import std.range: iota; int[] arr = iota(1, 5).array; (Why "iota"? Because in APL, the Greek letter iota (ι) is used to create a range of numbers like this.)
Jan 17 2022
next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 17 January 2022 at 11:58:18 UTC, Paul Backus wrote:
 On Monday, 17 January 2022 at 10:24:06 UTC, forkit wrote:
 Edsger W. Dijkstra, a well-known academic computer scientist, 
 has written in more detail about the advantages of this kind of 
 interval: 
 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html
Thank you for this valuable information you have given. There is a nice feature used in uniform(): ```d import std; enum a = 9; enum b = 10; void main() { auto sonYok = generate!(() => uniform!"[)"(a, b)).take(10); sonYok.writeln; // only 9 (default) auto ilk_son = generate!(() => uniform!"[]"(a, b)).take(10); ilk_son.writeln; // may contain 9 & 10 auto orta = generate!(() => uniform!"()"(a, b + 1)).take(10); orta.writeln; // only 10 auto ilkYok = generate!(() => uniform!"(]"(a, b + 1)).take(10); ilkYok.writeln; // Does not contain 9 } ``` It would be nice if this feature, which we set up with templates, could be applied everywhere in D. Because sometimes it is needed. As for other, I never used this feature until I got used to it. Of course, it's practical like this, it will do 10 reps: ```foreach(_;0..11)``` Salih
Jan 17 2022
prev sibling parent reply forkit <forkit gmail.com> writes:
On Monday, 17 January 2022 at 11:58:18 UTC, Paul Backus wrote:
 This kind of half-open interval, which includes the lower bound 
 but excludes the upper bound, is used in programming because it 
 lets you write

     foreach (i; 0 .. array.length) writef("%s ", array[i]);

 ...without going past the end of the array.
Yes. But the intent here is clearly stated and cannot be misunderstood -> array.length Whereas 1..5 is just an opportunity to shoot yourself in the foot. "the heretic must be cast out not because of the probability that he is wrong but because of the possibility that he is right." - Edsger W. Dijkstra
Jan 17 2022
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Jan 17, 2022 at 09:37:31PM +0000, forkit via Digitalmars-d-learn wrote:
 On Monday, 17 January 2022 at 11:58:18 UTC, Paul Backus wrote:
 
 This kind of half-open interval, which includes the lower bound but
 excludes the upper bound, is used in programming because it lets you
 write
 
     foreach (i; 0 .. array.length) writef("%s ", array[i]);
 
 ...without going past the end of the array.
Yes. But the intent here is clearly stated and cannot be misunderstood -> array.length Whereas 1..5 is just an opportunity to shoot yourself in the foot.
The compiler cannot discern intent. Both `5` and `array.length` are expressions, as far as the compiler is concerned. So is `5 + (array.length - sin(x))/2*exp(array2.length)`, for that matter. The compiler does not understand what the programmer may have intended; it simply follows what the spec says. Of course, to a *human* the semantics of `1..5` can be totally confusing if you're not used to it. The bottom-line is, in D (and in other C-like languages) you just have to get used to 0-based indexing, because ultimately it actually makes more sense than the 1-based counting scheme we were taught in school. 1-based counting schemes are full of exceptions and off-by-1 errors; it's needlessly complex and hard for the mortal brain to keep track of all the places where you have to add or subtract 1. Whereas in 0-based index schemes, you *always* count from 0, and you always use `<` to check your bounds, and you can do arithmetic with indices just by adding and subtracting as usual, without off-by-1 errors. Basically, foreach (i; a .. b) is equivalent to: for (auto i = a; i < b; i++) Just think of that way and it will make sense. And never ever write 1..n unless you actually intend to skip the first element. Remember: 0-based counting, not 1-based counting. You always start from 0, and count up to (but not including) n. Which also means you should always write `<`, never write `<=`. So your upper bound is always the element *past* the last one. I.e., it's the index at which a new element would be added if you were appending to your list. I.e., the index at which a new element should be added is simply array.length (not array.length+1 or array.length-1 or any of that error-prone crap that nobody can remember). If you adhere to these simple rules, you'll never need to add or subtract 1 to your counters, loop indices, and lengths (because nobody can remember when to do that, so not having to do it significantly reduces the chances of bugs). T -- People say I'm arrogant, and I'm proud of it.
Jan 17 2022
parent reply forkit <forkit gmail.com> writes:
On Monday, 17 January 2022 at 22:06:47 UTC, H. S. Teoh wrote:
 Basically,

 	foreach (i; a .. b)

 is equivalent to:

 	for (auto i = a; i < b; i++)

 Just think of that way and it will make sense.
I think it's fair to say, that I'm familiar with 0-based indexing ;-) my concern was with the 1..5 itself. In terms of what makes sense, it actually makes more sense not to use it, at all ;-)
Jan 17 2022
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Jan 17, 2022 at 10:22:19PM +0000, forkit via Digitalmars-d-learn wrote:
[...]
 I think it's fair to say, that I'm familiar with 0-based indexing ;-)
 
 my concern was with the 1..5 itself.
 
 In terms of what makes sense, it actually makes more sense not to use
 it, at all ;-)
If I ever needed to foreach over 1-based indices, I'd write it this way in order to avoid all confusion: foreach (i; 1 .. 5 + 1) { } This will immediately make whoever reads the code (i.e., myself after 2 months :D) wonder, "why +1?" And the answer will become clear and enlightenment ensues. ;-) T -- Change is inevitable, except from a vending machine.
Jan 17 2022
next sibling parent reply forkit <forkit gmail.com> writes:
On Monday, 17 January 2022 at 22:28:10 UTC, H. S. Teoh wrote:
 If I ever needed to foreach over 1-based indices, I'd write it 
 this way in order to avoid all confusion:

 	foreach (i; 1 .. 5 + 1)
 	{
 	}

 This will immediately make whoever reads the code (i.e., myself 
 after 2 months :D) wonder, "why +1?" And the answer will become 
 clear and enlightenment ensues. ;-)


 T
If I were able to write a compiler, my compiler would warn you: "This is ill-advised and you should know better! Please rewrite this."
Jan 17 2022
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Jan 17, 2022 at 10:35:30PM +0000, forkit via Digitalmars-d-learn wrote:
 On Monday, 17 January 2022 at 22:28:10 UTC, H. S. Teoh wrote:
 
 If I ever needed to foreach over 1-based indices, I'd write it this
 way in order to avoid all confusion:
 
 	foreach (i; 1 .. 5 + 1)
 	{
 	}
 
 This will immediately make whoever reads the code (i.e., myself
 after 2 months :D) wonder, "why +1?" And the answer will become
 clear and enlightenment ensues. ;-)
[...]
 If I were able to write a compiler, my compiler would warn you:
 
 "This is ill-advised and you should know better! Please rewrite this."
:-D If *I* were to write a compiler, it'd come with a GC built-in. It'd throw up 90% of programs you feed it with the error "this program is garbage, please throw it away and write something better". :-D T -- If blunt statements had a point, they wouldn't be blunt...
Jan 17 2022
parent reply Tejas <notrealemail gmail.com> writes:
On Monday, 17 January 2022 at 22:48:17 UTC, H. S. Teoh wrote:
 On Mon, Jan 17, 2022 at 10:35:30PM +0000, forkit via 
 Digitalmars-d-learn wrote:
 On Monday, 17 January 2022 at 22:28:10 UTC, H. S. Teoh wrote:
 [...]
[...]
 If I were able to write a compiler, my compiler would warn you:
 
 "This is ill-advised and you should know better! Please 
 rewrite this."
:-D If *I* were to write a compiler, it'd come with a GC built-in. It'd throw up 90% of programs you feed it with the error "this program is garbage, please throw it away and write something better". :-D T
Newer languages nowadays use `start..<end` to denote the intent, think it's something we should follow?
Jan 18 2022
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Jan 18, 2022 at 04:02:42PM +0000, Tejas via Digitalmars-d-learn wrote:
[...]
 Newer languages nowadays use `start..<end` to denote the intent, think
 it's something we should follow?
I've never seen that before. Which languages use that? T -- "If you're arguing, you're losing." -- Mike Thomas
Jan 18 2022
parent Tejas <notrealemail gmail.com> writes:
On Tuesday, 18 January 2022 at 17:58:54 UTC, H. S. Teoh wrote:
 On Tue, Jan 18, 2022 at 04:02:42PM +0000, Tejas via 
 Digitalmars-d-learn wrote: [...]
 Newer languages nowadays use `start..<end` to denote the 
 intent, think it's something we should follow?
I've never seen that before. Which languages use that? T
In Nim for example: ``` for n in 5 .. 9: #Both 5 and 9 are included echo n echo "" echo n ``` In Odin also: ``` for i in 0..<10 { fmt.println(i) } // or for i in 0..9 { fmt.println(i) } ```
Jan 18 2022
prev sibling parent reply forkit <forkit gmail.com> writes:
On Tuesday, 18 January 2022 at 16:02:42 UTC, Tejas wrote:
 Newer languages nowadays use `start..<end` to denote the 
 intent, think it's something we should follow?
I've decided to avoid using number ranges 'directly', and instead use a wrapper function... auto range(T:T)(T a, T b) { import std.range : iota; return iota(a, (b+1)); }
Jan 18 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/18/22 12:43, forkit wrote:

 wrapper function...


 auto range(T:T)(T a, T b)
 {
      import std.range : iota;
      return iota(a, (b+1));
 }
Needs a little more work to be correct. The following produces and empty range. ;) range(uint.min, uint.max) Also, is it important for the result to be the same as T? For example, even if T is ubyte, because b+1 is 'int', the range will produce ints. Ali
Jan 18 2022
parent reply forkit <forkit gmail.com> writes:
On Tuesday, 18 January 2022 at 20:50:06 UTC, Ali Çehreli wrote:
 Needs a little more work to be correct. The following produces 
 and empty range. ;)

   range(uint.min, uint.max)

 Also, is it important for the result to be the same as T? For 
 example, even if T is ubyte, because b+1 is 'int', the range 
 will produce ints.

 Ali
a change of mind... never use number ranges.. not ever! ;-) (except in combination with iota)
Jan 18 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/18/22 14:08, forkit wrote:

 never use number ranges.. not ever!  ;-)

 (except in combination with iota)
Indeed, the following is an elegant but slow (tested with dmd) implementation with Phobos: auto range(T)(T a, T b) in (a <= b) { import std.range : chain, iota, only; return chain(iota(a, b), only(b)); } But I like the following one better because it is fast and I think it works correctly. However, I am reminded of one of the reasons why exclusive ranges are better: It is not possible to represent an empty range with the same syntax. For example, range(42, 42) includes 42. Hmmm. Should range(42, 41) mean empty? Looks weird. struct InclusiveRange(T) { T front; T last; bool empty; this(U)(in U front, in U last) in (front <= last) { this.front = front; this.last = last; this.empty = false; } void popFront() { if (front == last) { empty = true; } else { ++front; } } } auto inclusiveRange(T)(T first, T last) { return InclusiveRange!T(first, last); } unittest { // Impossible to be empty import std.algorithm : equal; auto r = inclusiveRange(42, 42); assert(!r.empty); assert(r.equal([42])); } unittest { // Can 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 { // Really inclusive import std.algorithm : sum; assert(inclusiveRange(1, 10).sum == 55); } unittest { // Works 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])); } import std.stdio; void main() { } Ali
Jan 18 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Tuesday, 18 January 2022 at 23:13:14 UTC, Ali Çehreli wrote:
 But I like the following one better because
 it is fast and I think it works correctly.
Is it okay to swap places instead of throwing an error? Let's also implement BidirectionalRange, if okay. This great struct will now run 4x4 like a Jeep. 😀 Moreover, the iota doesn't even care if define char type. And no throwing an error. The real question to ask is: "Does it reverse the result in case ```a > b``` like we did with foreach_reverse()" Salih ```d import std; struct InclusiveRange(T) { T front, last; this(U)(in U front, in U last) { this.front = front; this.last = last; if(empty) toogleFrontLast(); } bool empty() { return front > last; } void popFront() { if(!empty) ++front; } T back() { return last; } void popBack() { if(!empty) --last; } void toogleFrontLast() { auto temp = this.last; this.last = this.front; this.front = temp; } } auto inclusiveRange(T)(T first, T last) { return InclusiveRange!T(first, last); } enum a = 8; // ASCII 80: P enum b = 7; // ASCII 70: F alias type = char; alias test = inclusiveRange; void main() { string str; // for tests... auto io = iota!type(a * 10, b * 10); io.writeln("\n", typeof(io).stringof, "\n"); str = to!string(io); assert(str == "[]"); // OMG, why? auto ir = test!type(a * 10, b * 10); ir.writeln("\n", typeof(ir).stringof, "\n"); str = to!string(ir); assert(str == "FGHIJKLMNOP"); // Ok foreach_reverse(c; ir) str ~= c; assert(str == "FGHIJKLMNOPPONMLKJIHGF"); // Ok } ```
Jan 19 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Jan 19 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
Hi,

It looks so delicious. 😀 Thank you.

On Wednesday, 19 January 2022 at 18:59:10 UTC, Ali Çehreli wrote:
 And adding length() was easy as well.

 Finally, I have provided property functions instead of allowing 
 direct access to members.
It doesn't matter as we can't use a 3rd parameter. But it doesn't work for any of these types: real, float, double. My solution: ```d size_t length() inout { //return last_ - first_ + 1 - empty_;/* auto len = 1 + last_ - first_; return cast(size_t)len;//*/ } ``` But it only works on integers. In this case, we have two options! The first is to require the use of integers, other 3 parameter usage: ```d // ... size_t length() inout { auto len = 1 + (last - front) / step; return cast(size_t)len; } } unittest { enum { ira = 0.1, irb = 2.09, irc = 0.11 } auto test = inclusiveRange(ira, irb, irc); assert(test.count == 19); auto arr = iota(ira, irb, irc).array; assert(test.length == arr.length); } ``` Salih
Jan 19 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/19/22 21:24, Salih Dincer wrote:

 ```d
    size_t length() inout {
      //return last_ - first_ + 1 - empty_;/*
      auto len = 1 + last_ - first_;
      return cast(size_t)len;//*/
    }
 ```
Good catch but we can't ignore '- empty_'. Otherwise an empty range will return 1.
 But it only works on integers.
After fixing the size_t issue, it should work on user-defined types as well. In fact, it is better to leave the return type as auto so that it works with user-defined types that support the length expression but is a different type like e.g. MyDiffType. Having said that, floating point types don't make sense with the semantics of a *bidirectional and inclusive* range. :) Let's see how it looks for ranges where the step size is 0.3: import std.stdio; void main() { float beg = 0.0; float end = 1.0; float step = 0.3; writeln("\nIncrementing:"); for (float f = beg; f <= end; f += step) { report(f); } writeln("\nDecrementing:"); for (float f = end; f >= beg; f -= step) { report(f); } } void report(float f) { writefln!"%.16f"(f); } Here is the output: Incrementing: 0.0000000000000000 0.3000000119209290 0.6000000238418579 0.9000000357627869 <-- Where is 1.0? Decrementing: 1.0000000000000000 0.6999999880790710 0.3999999761581421 0.0999999642372131 <-- Where is 0.0? So if we add the 1.0 value after 0.9000000357627869 to be *inclusive*, then that last step would not be 0.3 anymore. (Thinking about it, step would mess up things for integral types as well; so, it must be checked during construction.) The other obvious issue in the output is that a floating point iota cannot be bidirectional because the element values would be different. Ali
Jan 20 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Thursday, 20 January 2022 at 16:33:20 UTC, Ali Çehreli wrote:
 So if we add the 1.0 value after 0.9000000357627869 to be 
 *inclusive*, then that last step would not be 0.3 anymore. 
 (Thinking about it, step would mess up things for integral 
 types as well; so, it must be checked during construction.)

 The other obvious issue in the output is that a floating point 
 iota cannot be bidirectional because the element values would 
 be different.
The test that did not pass now passes. There is the issue of T.min being excluded from the property list for the double type. I tried to solve it, how is it? Salih ```d auto inclusiveRange(T)(T f, T l, T s = cast(T)0) in(!isBoolean!T) { static assert(!isBoolean!T, "\n Cannot be used with bool type\n"); if(!s) s++; return InclusiveRange!T(f, l, s); } struct InclusiveRange(T) { private: T first, last; bool empty_; public: T step; this(U)(in U first, in U last, in U step) in (first <= last, format!"\n Invalid range:[%s,%s]."(first, last)) { this.first = first; this.last = last; this.step = step; this.empty_ = false; } bool opBinaryRight(string op:"in")(T rhs) { foreach(r; this) { if(r == rhs) return true; } return false; } auto save() inout { return this; } bool empty() inout { return empty_; } T front() inout { return first; } T back() inout { return last; } void popFront() { if(!empty) { if(last >= first + step) { first += step; } else { empty_ = true; if(T.max <= first + step) { first += step; } } } } void popBack() { if(!empty) { if(first <= last-step) { last -= step; } else { empty_ = true; if(!T.max >= last - step) { last -= step; } } } } size_t length() inout { auto len = 1 + (last - first) / step; return cast(size_t)len; } } import std.algorithm, std.math; import std.range, std.traits; import std.stdio, std.format, std.conv; void main() { // Pi Number Test auto GregorySeries = inclusiveRange!double(1, 0x1.0p+27, 2); double piNumber = 0; foreach(e, n; GregorySeries.enumerate) { if(e & 1) piNumber -= 1/n; else piNumber += 1/n; } writefln!"%.21f (constant)"(PI); writefln!"%.21f (calculated)"(piNumber * 4); } unittest { // Should not be possible to have an empty range auto r = inclusiveRange(ubyte.min, ubyte.max); static assert(is(ElementType!(typeof(r)) == ubyte)); assert(r.sum == (ubyte.max * (ubyte.max + 1)) / 2); } ```
Jan 21 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/21/22 08:58, Salih Dincer wrote:

 ```d
 auto inclusiveRange(T)(T f, T l, T s = cast(T)0)
 in(!isBoolean!T) {
'in' contracts are checked at runtime. The one above does not make sense because you already disallow compilation for 'bool' below. You could add a template constraint there: auto inclusiveRange(T)(T f, T l, T s = cast(T)0) if(!isBoolean!T) { (Note 'if' vs. 'in'.) However, people who instantiate the struct template directly would bypass that check anyway.
    static assert(!isBoolean!T, "\n
        Cannot be used with bool type\n");
    if(!s) s++;
    return InclusiveRange!T(f, l, s);
 }
    bool opBinaryRight(string op:"in")(T rhs) {
      foreach(r; this) {
        if(r == rhs) return true;
      }
      return false;
    }
Ouch! I tried the following code, my laptop got very hot, it's been centuries, and it's still running! :p auto looong = inclusiveRange(ulong.min, ulong.max); foreach (l; looong) { assert(l in looong); }
    size_t length() inout {
      auto len = 1 + (last - first) / step;
      return cast(size_t)len;
    }
Does that not return 1 for an empty range? Additionally, just because we *provide* a step, now we *require* division from all types (making it very cumbersome for user-defined types).
    // Pi Number Test
    auto GregorySeries = inclusiveRange!double(1, 0x1.0p+27, 2);
Very smart! ;) So, this type can support floating point values if we use that syntax. Ali
Jan 21 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Friday, 21 January 2022 at 17:25:20 UTC, Ali Çehreli wrote:
 Ouch! I tried the following code, my laptop got very hot, it's 
 been centuries, and it's still running! :p
:)
 ```d
    size_t length() inout {
      auto len = 1 + (last - first) / step;
      return cast(size_t)len;
    }
 ```
 Does that not return 1 for an empty range?
Yes, but it will never return an empty range: ```d enum e = 1; auto o = inclusiveRange(e, e); // only one element assert(!o.empty); assert(o.length == e); assert(o.equal([e])); ```
 Additionally, just because we *provide* a step, now we 
 *require* division from all types (making it very cumbersome 
 for user-defined types).
I don't quite understand what you mean? Salih
Jan 21 2022
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
 On Friday, 21 January 2022 at 17:25:20 UTC, Ali Çehreli wrote:
[...]
 Additionally, just because we *provide* a step, now we *require*
 division from all types (making it very cumbersome for user-defined
 types).
[...] It doesn't have to be this way. We could just use DbI to inspect whether the incoming type supports division; if it does, we provide stepping, otherwise, just plain ole iteration. DbI rocks. T -- Doubt is a self-fulfilling prophecy.
Jan 21 2022
prev sibling parent reply Tejas <notrealemail gmail.com> writes:
On Tuesday, 18 January 2022 at 20:43:08 UTC, forkit wrote:
 On Tuesday, 18 January 2022 at 16:02:42 UTC, Tejas wrote:
 Newer languages nowadays use `start..<end` to denote the 
 intent, think it's something we should follow?
I've decided to avoid using number ranges 'directly', and instead use a wrapper function... auto range(T:T)(T a, T b) { import std.range : iota; return iota(a, (b+1)); }
Aww, come on; it ain't that bad...
Jan 18 2022
parent forkit <forkit gmail.com> writes:
On Wednesday, 19 January 2022 at 03:00:49 UTC, Tejas wrote:
 On Tuesday, 18 January 2022 at 20:43:08 UTC, forkit wrote:
 On Tuesday, 18 January 2022 at 16:02:42 UTC, Tejas wrote:
 Newer languages nowadays use `start..<end` to denote the 
 intent, think it's something we should follow?
I've decided to avoid using number ranges 'directly', and instead use a wrapper function... auto range(T:T)(T a, T b) { import std.range : iota; return iota(a, (b+1)); }
Aww, come on; it ain't that bad...
Well that depends on entirely on what the code is doing ;-) The key is to *always* *remember* the stop index is not included. I sure hope they 'remembered' this in the code running on that telescope floating out into open space...
Jan 18 2022
prev sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Monday, 17 January 2022 at 22:28:10 UTC, H. S. Teoh wrote:
 This will immediately make whoever reads the code (i.e., myself 
 after 2 months :D) wonder, "why +1?" And the answer will become 
 clear and enlightenment ensues. ;-)
In those cases i find myself rewriting said code. Generally to say **for(int i=1; i<=5; i++)** or something, where it includes the last one but doesn't add oddities that doesn't explain the magic numbers or odd +1. Then again the big issue *probably* comes from people coming from BASIC of some description where the **FOR A=1 TO 5**, where index starts at 1 and includes the number listed; And you aren't given other conditions to test against. It really does take a little getting used to. Maybe we don't use Qbasic or 8bit MSBASIC much anymore, but Visual Basic and legacy code grandfathers those in, and maybe a few other interpreted languages too.
Jan 18 2022