Re: Adapting foreign iterators to D ranges
On Thursday, 25 April 2024 at 03:18:36 UTC, cc wrote: On Wednesday, 24 April 2024 at 05:08:25 UTC, Salih Dincer wrote: Yes, `opApply()` works! You just need to use `do while()` instead of `while()` because it skips the first item. It depends on the type of structure being consumed, if it provides "next" as a direct pointer then yeah you would need to consume the item first before iterating to the next in line. However some APIs provide an opaque iterator type where you call a "next" method to get the first element, IIRC Lua does something like this. I've noticed a strange behavior in the Range structure that consumes the List class! If we use foreach(), we should take a backup as we're used to, or use the rewind() function as I did below. These days, I've started to delve deeper into the opApply() feature. I wanted to fix this mistake I made in the past. ```d struct Range { private List list; int opApply(scope int delegate(Node* t) dg) { while(auto current = list.Next) { if (auto r = dg(current)) return r; } list.rewind(); return 0; } } ``` Also, due to the nature of linked lists, we cannot print the number 1 added at the beginning with foreach(). Therefore, when creating the List class, it may be wise to create it without giving any parameters and start adding elements from 1. You might also be interested in this topic about opApply: https://forum.dlang.org/thread/jxzqsxasierzokgcy...@forum.dlang.org SDB@79
Re: Adapting foreign iterators to D ranges
On Wednesday, 24 April 2024 at 05:08:25 UTC, Salih Dincer wrote: Yes, `opApply()` works! You just need to use `do while()` instead of `while()` because it skips the first item. It depends on the type of structure being consumed, if it provides "next" as a direct pointer then yeah you would need to consume the item first before iterating to the next in line. However some APIs provide an opaque iterator type where you call a "next" method to get the first element, IIRC Lua does something like this.
Re: Adapting foreign iterators to D ranges
On Tuesday, 23 April 2024 at 06:02:18 UTC, cc wrote: Just to offer an alternative solution (since it sometimes gets overlooked), there is also the `opApply` approach. You don't get full forward range status, and checking whether it's empty essentially requires doing something like std.algorithm `walkLength`, but if all you need is basic iteration, it can be a simpler solution: Yes, `opApply()` works! You just need to use `do while()` instead of `while()` because it skips the first item. ```d struct Node { int item; Node* next; } class List { Node* root, iter; this(int item = 0) { iter = new Node(item, null); root = iter; } List dup() { auto backup = new List(); backup.root = root; backup.iter = iter; return backup; } void insertFront(T)(T item) { (*iter).next = new Node(item, null); this.Next; } bool empty() const => iter is null; auto front() inout => iter; auto popFront()=> iter = this.Next; auto getItem() => iter.item; auto rewind() => iter = root; } auto Next(List list) => list.iter = list.iter.next; auto gaussian(T)(T n)=> (n * n + n) / 2; void main() { import std.stdio; enum LIMIT = 10; auto list = new List(1); foreach(t; 2 .. LIMIT + 1) { list.insertFront(t); } auto tmp = list.dup; list.rewind(); size_t sum; do sum += list.getItem; while(list.Next); assert(gaussian(LIMIT) == sum); sum.writeln; // 55 auto next = LIMIT + 1; tmp.insertFront(next); tmp.rewind(); sum = 0; foreach(t; tmp) sum += t.item; assert(gaussian(LIMIT) + next == sum); sum.writeln; // 66 tmp.rewind(); auto range = Range(tmp); foreach(r; range) r.item.write(" "); writeln; // 2 3 4 5 6 7 8 9 10 11 // ? (1) --^ } struct Range { private List iter; int opApply(scope int delegate(Node* t) dg) { while(auto current = iter.Next) { if (auto r = dg(current)) return r; } return 0; } } ``` SDB@79
Re: Adapting foreign iterators to D ranges
On Monday, 22 April 2024 at 11:36:43 UTC, Chloé wrote: I wish to adapt this interface to a forward range for use with foreach and Phobos' range utilities. This amounts to implementing empty, front, and popFront, in terms of next and some state. But there is a choice to be made regarding the first call to next. Just to offer an alternative solution (since it sometimes gets overlooked), there is also the `opApply` approach. You don't get full forward range status, and checking whether it's empty essentially requires doing something like std.algorithm `walkLength`, but if all you need is basic iteration, it can be a simpler solution: ```d struct Range { private I iter; this(I iter) { this.iter = iter; } int opApply(scope int delegate(T* t) dg) { while (auto current = next(iter)) { if (auto r = dg(current)) return r; } return 0; } } void main() { I someIter; // = ... auto range = Range(someIter); foreach (const t; range) { writeln(*t); } } ```
Re: Adapting foreign iterators to D ranges
On Monday, 22 April 2024 at 11:36:43 UTC, Chloé wrote: The first implementation has the advantage is being simpler and empty being const, but has the downside that next is called even if the range ends up not being used. Is either approach used consistently across the D ecosystem? I always go for the simplest approach. So that means, pre-fill in the constructor. Yes, the downside is, if you don't use it, then the iterator has moved, but the range hasn't. But returning to the iterator after using the range is always a dicey proposition anyway. The huge benefit is that all the functions become simple and straightforward. But there is no "right" approach. And using composition, you may be able to achieve all approaches with wrappers. Phobos does various things depending on what people thought was good at the time. It sometimes causes some very unexpected behavior. I recommend always using the same approach for the same library, that way your users know what to expect! -Steve
Re: Adapting foreign iterators to D ranges
On Monday, 22 April 2024 at 11:36:43 UTC, Chloé wrote: The first implementation has the advantage is being simpler and empty being const, but has the downside that next is called even if the range ends up not being used. Is either approach used consistently across the D ecosystem? You can also place initialization logic inside front, then empty could become const. I don't think there is a preffered way of initializing such ranges, so imho consider what's best for your use case.
Adapting foreign iterators to D ranges
Assume a third-party API of the following signature: T* next(I iter); which advances an iterator of sorts and returns the next element, or null when iteration is done. No other information about the state of the iterator is available. I wish to adapt this interface to a forward range for use with foreach and Phobos' range utilities. This amounts to implementing empty, front, and popFront, in terms of next and some state. But there is a choice to be made regarding the first call to next. One could call next during range construction: struct Range { private I iter; private T* current; this(I iter) { this.iter = iter; current = next(iter); } bool empty() const => current is null; inout(T)* front() inout => current; void popFront() { current = next(iter); } } Or do not call it until the first call to empty: struct Range { private bool initialized; private I iter; private T* current; this(I iter) { this.iter = iter; } bool empty() { if (!initialized) { current = next(iter); initialized = true; } return current is null; } inout(T)* front() inout => current; void popFront() { current = next(iter); } } The first implementation has the advantage is being simpler and empty being const, but has the downside that next is called even if the range ends up not being used. Is either approach used consistently across the D ecosystem?
Re: Key and value with ranges
On Tuesday, 3 October 2023 at 20:22:30 UTC, Andrey Zherikov wrote: On Tuesday, 3 October 2023 at 19:57:06 UTC, christian.koestlin wrote: On Tuesday, 3 October 2023 at 01:55:43 UTC, Andrey Zherikov wrote: On Monday, 2 October 2023 at 18:46:14 UTC, christian.koestlin wrote: [...] Slightly improved: ```d import std; [...] Thanks .. the thing with ref result is very clever! Should `ref result` be `return result`? `ref` might not be necessary fo AA but I'm not sure. I found this in regards to AAs: https://forum.dlang.org/post/baewchcnyfibkvuiy...@forum.dlang.org So it might be more efficient in this case to pass the AA as reference. `fold` requires function to return something (see [doc](https://dlang.org/phobos/std_algorithm_iteration.html#fold)): for each element x in range, result = fun(result, x) gets evaluated. It's clear that the function needs to return something, but I was thinking if it would make sense to "document" how one works with the accumulator by indicating it as `return` instead of `ref`. I just tried to read through: https://dlang.org/spec/function.html#param-storage, but there is more to it .. like `return ref`, `return ref scope`, and what not .. so I am not so sure anymore.
Re: Key and value with ranges
On Tuesday, 3 October 2023 at 19:57:06 UTC, christian.koestlin wrote: On Tuesday, 3 October 2023 at 01:55:43 UTC, Andrey Zherikov wrote: On Monday, 2 October 2023 at 18:46:14 UTC, christian.koestlin wrote: [...] Slightly improved: ```d import std; [...] Thanks .. the thing with ref result is very clever! Should `ref result` be `return result`? `ref` might not be necessary fo AA but I'm not sure. `fold` requires function to return something (see [doc](https://dlang.org/phobos/std_algorithm_iteration.html#fold)): for each element x in range, result = fun(result, x) gets evaluated.
Re: Key and value with ranges
On Tuesday, 3 October 2023 at 01:55:43 UTC, Andrey Zherikov wrote: On Monday, 2 October 2023 at 18:46:14 UTC, christian.koestlin wrote: [...] Slightly improved: ```d import std; [...] Thanks .. the thing with ref result is very clever! Should `ref result` be `return result`? Kind regards, Christian
Re: Key and value with ranges
On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: ```d import std; auto data=“I went for a walk, and fell down a hole.”; ``` [snip] How can I improve this code? Like avoiding using foreach. This works for me: ```d import std; auto data="I went for a walk, and fell down a hole."; void main(string[] args) { if (args.length>1 && args[1].exists) data=readText(args[1]); data .toLower .map!(c => lowercase.canFind(std.uni.toLower(c)) ? c : ' ') .to!string .split .fold!((ref result, element) { result[element]+=1; return result; })(uint[string].init) .byKeyValue .array .sort!"a.value>b.value" .each!(pair => writeln("Word: ", pair.key, " - number of instances: ", pair.value)); } ```
Re: Key and value with ranges
On Monday, 2 October 2023 at 18:46:14 UTC, christian.koestlin wrote: On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: How can I improve this code? Like avoiding using foreach. You could fold into a hash that counts the occurrences like that: ```d import std.uni : toLower; import std.array : split, array; import std.stdio : writeln; import std.algorithm : fold, sort, map; auto data="I went for a walk, and fell down a hole. a went"; int main(string[] args) { int[string] wc; data .toLower .split .fold!((result, element) { result[element] += 1; return result; })(wc) .byKeyValue .array .sort!((pair1, pair2) => pair1.value > pair2.value) .map!(pair => pair.key) .writeln ; return 0; } ``` Not sure how to get rid of the declaration of the empty wc hash though. Kind regards, Christian Slightly improved: ```d import std; auto data="I went for a walk, and fell down a hole. a went"; int main(string[] args) { data .toLower .split .fold!((ref result, element) { ++result[element]; return result; })(uint[string].init) .byKeyValue .array .sort!((pair1, pair2) => pair1.value > pair2.value) .each!(pair => writeln("Word: ", pair.key, " - number of instances: ", pair.value)) ; return 0; } ``` Output: ``` Word: a - number of instances: 3 Word: went - number of instances: 2 Word: and - number of instances: 1 Word: i - number of instances: 1 Word: hole. - number of instances: 1 Word: for - number of instances: 1 Word: down - number of instances: 1 Word: fell - number of instances: 1 Word: walk, - number of instances: 1 ```
Re: Key and value with ranges
On Monday, 2 October 2023 at 20:20:44 UTC, Joel wrote: I want the output sorted by value. Look: https://forum.dlang.org/post/qjlmiohaoeolmoavw...@forum.dlang.org ```d struct SIRALA(T) { } ``` You can use SIRALA(T). Okay, his language is Turkish but our common language is D. His feature is that he uses double AA. You will like it. Here is the source: https://gist.github.com/run-dlang/808633909615dbda431baa01f795484f SDB@79
Re: Key and value with ranges
On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: ```d import std; auto data=“I went for a walk, and fell down a hole.”; void main(string[] args) { int[string] dic; struct WordCnt { string word; ulong count; string toString() const { return text("Word: ", word, " - number of instances: ", count); } } WordCnt[] wc; data .map!(c => lowercase.canFind(std.uni.toLower(c)) ? c : ' ') .to!string .splitter .each!(d => dic[d]+=1); foreach(key, value; dic) wc~=WordCnt(key, value); wc.sort!"a.count>b.count".each!writeln; } ``` How can I improve this code? Like avoiding using foreach. This is what I've got so far. Is there a way to do it any better? ```d import std; auto data="I went for a walk, and fell down a hole."; void main() { int[string] aa; struct WordCnt { string word; ulong count; string toString() const { return text("Word: ", word, " - number of instances: ", count); } } WordCnt[] wc; data .map!(c => lowercase.canFind(std.uni.toLower(c)) ? c : ' ') .to!string .splitter .each!(d => aa[d]+=1); aa.each!((key, value) { wc~=WordCnt(key, value); }); wc.sort!"a.count>b.count" .each!writeln; } ```
Re: Key and value with ranges
On Monday, 2 October 2023 at 06:19:29 UTC, Imperatorn wrote: On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: ```d import std; auto data=“I went for a walk, and fell down a hole.”; You can improve it further by inlining ```d import std; auto data = "I went for a walk, and fell down a hole."; void main(string[] args) { int[string] dic; data.split(' ').each!(w => dic[w]++); sort(dic.keys).each!(w => writeln(dic[w], " ",w)); } ``` I want the output sorted by value.
Re: Key and value with ranges
On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: How can I improve this code? Like avoiding using foreach. You could fold into a hash that counts the occurrences like that: ```d import std.uni : toLower; import std.array : split, array; import std.stdio : writeln; import std.algorithm : fold, sort, map; auto data="I went for a walk, and fell down a hole. a went"; int main(string[] args) { int[string] wc; data .toLower .split .fold!((result, element) { result[element] += 1; return result; })(wc) .byKeyValue .array .sort!((pair1, pair2) => pair1.value > pair2.value) .map!(pair => pair.key) .writeln ; return 0; } ``` Not sure how to get rid of the declaration of the empty wc hash though. Kind regards, Christian
Re: Key and value with ranges
On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: ```d import std; auto data=“I went for a walk, and fell down a hole.”; You can improve it further by inlining ```d import std; auto data = "I went for a walk, and fell down a hole."; void main(string[] args) { int[string] dic; data.split(' ').each!(w => dic[w]++); sort(dic.keys).each!(w => writeln(dic[w], " ",w)); } ```
Re: Key and value with ranges
On Monday, 2 October 2023 at 02:47:37 UTC, Joel wrote: ```d import std; auto data=“I went for a walk, and fell down a hole.”; void main(string[] args) { int[string] dic; struct WordCnt { string word; ulong count; string toString() const { return text("Word: ", word, " - number of instances: ", count); } } WordCnt[] wc; data .map!(c => lowercase.canFind(std.uni.toLower(c)) ? c : ' ') .to!string .splitter .each!(d => dic[d]+=1); foreach(key, value; dic) wc~=WordCnt(key, value); wc.sort!"a.count>b.count".each!writeln; } ``` How can I improve this code? Like avoiding using foreach. You don't need a struct at all, you can just have an int[string] aa
Key and value with ranges
```d import std; auto data=“I went for a walk, and fell down a hole.”; void main(string[] args) { int[string] dic; struct WordCnt { string word; ulong count; string toString() const { return text("Word: ", word, " - number of instances: ", count); } } WordCnt[] wc; data .map!(c => lowercase.canFind(std.uni.toLower(c)) ? c : ' ') .to!string .splitter .each!(d => dic[d]+=1); foreach(key, value; dic) wc~=WordCnt(key, value); wc.sort!"a.count>b.count".each!writeln; } ``` How can I improve this code? Like avoiding using foreach.
Re: Ranges
On Sunday, 7 August 2022 at 21:57:50 UTC, Ali Çehreli wrote: On 8/7/22 08:34, pascal111 wrote: > but after that in advanced level in programming, we should > use pointers to do same tasks we were doing with slices (the easy way of > beginners). That is an old thought. Today, we see that no matter how experienced, every person needs and appreciates help to prevent bugs. There are many cases of bugs killing people, jeopardizing expensive projects, loss of personal information, etc. Ali I think you are right that this is an old thought, I didn't noticed that, maybe it's because I didn't study C++ and know only about C, so I applied C features on D.
Re: Ranges
On 8/6/22 22:58, Salih Dincer wrote: > Ranges are not like that, all they do is > generate. You may be right. I've never seen it that way. I've been under the following impression: - C++'s iterators are based on an existing concept: pointers. Pointers are iterators. - D's ranges are based on an existing concept: slices. Slices are ranges. However, I can't find where I read that. Ali
Re: Ranges
On 8/7/22 08:34, pascal111 wrote: > Everyone knows that slices are not pointers D's slices are "fat pointers": In D's case, that translates to a pointer plus length. > that pointers are real work, Agreed. Pointers are fundamental features of CPUs. > but slices are like a simple un-deep technique that is appropriate for > beginners, That is not correct. Slices are designed by a C expert to prevent horrible bugs caused by C experts. Most C experts use slices very happily. > but after that in advanced level in programming, we should > use pointers to do same tasks we were doing with slices (the easy way of > beginners). That is an old thought. Today, we see that no matter how experienced, every person needs and appreciates help to prevent bugs. There are many cases of bugs killing people, jeopardizing expensive projects, loss of personal information, etc. Ali
Re: Ranges
On Sunday, 7 August 2022 at 19:53:06 UTC, ag0aep6g wrote: On Sunday, 7 August 2022 at 15:34:19 UTC, pascal111 wrote: Everyone knows that slices are not pointers that pointers are real work, but slices are like a simple un-deep technique that is appropriate for beginners, but after that in advanced level in programming, we should use pointers to do same tasks we were doing with slices (the easy way of beginners). I can't tell if this is a joke or not. It's just an opinion.
Re: Ranges
On Saturday, 6 August 2022 at 15:37:32 UTC, pascal111 wrote: On Friday, 5 August 2022 at 04:05:08 UTC, Salih Dincer wrote: On Thursday, 4 August 2022 at 22:54:42 UTC, pascal111 wrote: I didn't notice that all what we needs to pop a range forward is just a slice, yes, we don't need variable here. Ranges and Slices are not the same thing. Slicing an array is easy. This is a language possibility. For example, you need an incrementing variable for the Fibonacci Series. SDB@79 What!!! so where's ranges?! I thought slices of any array are ranges, and understood it like that, and also there's no data type called ranges, it's like if you are talking about Ghostly data type! A range is like an iterator in any other language (Java, C++, python3, javascript, etc), it is how D implements (lazy) generators https://en.wikipedia.org/wiki/Lazy_evaluation . Ranges/Iterators don't necessarily have to be backed by memory, they just have to implement the interface. In D, a `empty` bool function that tells you whether you are at the end of the range or not; a `front` function to get the current value if the range is not `empty`; and a void function named `popFront` to advance to the next value if the range is not `empty`. Once you have implemented this interface, you can use your "range" object with any function that accept a range; with `foreach`; etc. Example of a range that is not backed by memory is a range with all the integer numbers. ```D struct Integers { private int z = 0; /* or make it a bool attribute that starts as false, and you set to * true when popFront is called while z is equal to int.min */ public bool empty() { return false; } public int front() { return this.z; } public void popFront() { /* if (this.z == int.min) { this.empty = false; return; } */ this.z *= -1; if (this.z <= 0) --this.z; } } void main() { import std.stdio : writeln; /* foreach is syntax sugar for * for (auto r = Integers(); !r.empty(); r.popFront()) { * auto z = r.front(); /+ or const z = r.front(); or ... +/ * ... * } * that is why it only works with ranges. */ foreach (const z; Integers()) { writeln(z); if (z == 5) break; } } ``` output: ``` 0 -1 1 -2 2 -3 3 -4 4 -5 5 ``` This will iterate all the integers, and the integers are of course, not all in memory, and don't remain in memory after they are used, since that would require infinite memory. (in the case of a range of integers, not infinite, because they are constrained by being int.sizeof bytes, but you could use a bignum implemenation that is not constrained by that and they would actually be infinite.) --- The equivalent in Java is the Iterable/Iterator interface. ```java import java.util.Iterator; public class Integers implements Iterable { public class IntegersIterator implements Iterator { private int z = 0; private boolean first = true; public IntegersIterator(Integer z) { this.z = z; } @Override public boolean hasNext() { return true; } @Override public Integer next() { if (this.first) { this.first = false; return this.z; } this.z *= -1; if (this.z <= 0) --this.z; return this.z; } } @Override public IntegersIterator iterator() { return new IntegersIterator(0); } public static void main(String[] args) { /* syntax sugar for * { * final var it = newIntegers.iterator(); * while (it.hasNext()) { * final int z = it.next(); * ... * } * } */ for (final int z : new Integers()) { System.out.println(z); if (z == 5) break; } } } ``` The equivalent in python is a generator function: ```python def integers(): z = 0 yield z while True: z *= -1 if z <= 0: z -= 1 yield z for z in integers(): print(z) if z == 5: break ``` etc
Re: Ranges
On Sunday, 7 August 2022 at 15:34:19 UTC, pascal111 wrote: Everyone knows that slices are not pointers that pointers are real work, but slices are like a simple un-deep technique that is appropriate for beginners, but after that in advanced level in programming, we should use pointers to do same tasks we were doing with slices (the easy way of beginners). I can't tell if this is a joke or not.
Re: Ranges
On Sunday, 7 August 2022 at 15:34:19 UTC, pascal111 wrote: Everyone knows that slices are not pointers that pointers are real work, but slices are like a simple un-deep technique that is appropriate for beginners, but after that in advanced level in programming, we should use pointers to do same tasks we were doing with slices (the easy way of beginners). The following information about slices may be helpful: Slices are objects from type T[] for any given type T. Slices provide a view on a subset of an array of T values - or just point to the whole array. Slices and dynamic arrays are the same. A slice consists of two members - a pointer to the starting element and the length of the slice: ```d T* ptr; size_t length; // unsigned 32 bit on 32bit, unsigned 64 bit on 64bit ``` [...] **Source:** https://tour.dlang.org/tour/en/basics/slices SDB@79
Re: Ranges
On Sunday, 7 August 2022 at 05:12:38 UTC, Ali Çehreli wrote: On 8/6/22 14:10, pascal111 wrote: > a powerful point in the account of C. I missed how you made that connection. Everyone knows that slices are not pointers that pointers are real work, but slices are like a simple un-deep technique that is appropriate for beginners, but after that in advanced level in programming, we should use pointers to do same tasks we were doing with slices (the easy way of beginners).
Re: Ranges
On Saturday, 6 August 2022 at 17:29:30 UTC, Ali Çehreli wrote: On 8/6/22 09:33, Salih Dincer wrote: > the slices feel like ranges, don't they? Yes because they are ranges. :) (Maybe you meant they don't have range member functions, which is true.) Slices use pointers. Do I need to tell you what the pointers do! Each of them points to a data. Ranges are not like that, all they do is generate. Ok, you use a slice just as if it were a range. But they are not ranges. SDB@79
Re: Ranges
On 8/6/22 14:10, pascal111 wrote: > the problem is that ranges in D lack the usage of pointers as > an essential tool to make all of ranges functions they need. If ranges > exist in C, they would use pointers, and this is There are a few cases where pointers provide functionality that ranges cannot: 1) Some algorithms don't make much sense with ranges. For example, most of the time find() can return just the element that we seek. In D, find() returns a range so that we can chain it with other algorithms. 2) Some algorithms like partition() better use three pointers. Other than that, ranges are superior to pointers in every aspect. (I resent the fact that some C++ "experts" used those two points to decide ranges are inferior and helped deprive the C++ community of ranges for a very long time. The same "experts" did the same with 'static if'.) > a powerful point in the account of C. I missed how you made that connection. Ali
Re: Ranges
On Saturday, 6 August 2022 at 15:54:57 UTC, H. S. Teoh wrote: On Sat, Aug 06, 2022 at 03:37:32PM +, pascal111 via Digitalmars-d-learn wrote: On Friday, 5 August 2022 at 04:05:08 UTC, Salih Dincer wrote: > On Thursday, 4 August 2022 at 22:54:42 UTC, pascal111 wrote: > > [...] > > Ranges and Slices are not the same thing. Slicing an array > is easy. This is a language possibility. For example, you > need an incrementing variable for the Fibonacci Series. > > SDB@79 What!!! so where's ranges?! I thought slices of any array are ranges, and understood it like that, and also there's no data type called ranges, it's like if you are talking about Ghostly data type! A range is any type that supports the Range API defined in std.range (i.e., .empty, .front, .popFront). For more explanations, read: http://www.informit.com/articles/printerfriendly.aspx?p=1407357&rll=1 http://ddili.org/ders/d.en/ranges.html http://dconf.org/2015/talks/davis.html http://tour.dlang.org/tour/en/basics/ranges http://wiki.dlang.org/Component_programming_with_ranges T You know, the problem is that ranges in D lack the usage of pointers as an essential tool to make all of ranges functions they need. If ranges exist in C, they would use pointers, and this is a powerful point in the account of C.
Re: Ranges
On 8/6/22 09:33, Salih Dincer wrote: > the slices feel like ranges, don't they? Yes because they are ranges. :) (Maybe you meant they don't have range member functions, which is true.) D's slices happen to be the most capable range kind: RandonAccessRange. All of the following operations are supported on them as long as one imports std.array (or std.range, which publicly does so): - empty - front - popFront - save - back - popBack - indexed element access Slices have the optional length property as well (i.e. hasLength). Those operations are not supported by member functions but by free-standing functions. Ali
Re: Ranges
On Saturday, 6 August 2022 at 16:30:55 UTC, Salih Dincer wrote: Indeed, the slices (we can call it a dynamic array) feel like slice, don't they? Edit: Indeed, the slices feel like ranges, don't they? Sorry... SDB@79
Re: Ranges
On Saturday, 6 August 2022 at 15:37:32 UTC, pascal111 wrote: On Friday, 5 August 2022 at 04:05:08 UTC, Salih Dincer wrote: On Thursday, 4 August 2022 at 22:54:42 UTC, pascal111 wrote: I didn't notice that all what we needs to pop a range forward is just a slice, yes, we don't need variable here. Ranges and Slices are not the same thing. Slicing an array is easy. This is a language possibility. For example, you need an incrementing variable for the Fibonacci Series. SDB@79 What!!! so where's ranges?! I thought slices of any array are ranges, and understood it like that, and also there's no data type called ranges, it's like if you are talking about Ghostly data type! Well, that's very normal. Because as you work with ranges, you will understand better. Indeed, the slices (we can call it a dynamic array) feel like slice, don't they? SDB@79
Re: Ranges
On Sat, Aug 06, 2022 at 03:37:32PM +, pascal111 via Digitalmars-d-learn wrote: > On Friday, 5 August 2022 at 04:05:08 UTC, Salih Dincer wrote: > > On Thursday, 4 August 2022 at 22:54:42 UTC, pascal111 wrote: > > > > > > I didn't notice that all what we needs to pop a range forward is > > > just a slice, yes, we don't need variable here. > > > > Ranges and Slices are not the same thing. Slicing an array is easy. > > This is a language possibility. For example, you need an > > incrementing variable for the Fibonacci Series. > > > > SDB@79 > > What!!! so where's ranges?! I thought slices of any array are ranges, > and understood it like that, and also there's no data type called > ranges, it's like if you are talking about Ghostly data type! A range is any type that supports the Range API defined in std.range (i.e., .empty, .front, .popFront). For more explanations, read: http://www.informit.com/articles/printerfriendly.aspx?p=1407357&rll=1 http://ddili.org/ders/d.en/ranges.html http://dconf.org/2015/talks/davis.html http://tour.dlang.org/tour/en/basics/ranges http://wiki.dlang.org/Component_programming_with_ranges T -- No! I'm not in denial!
Re: Ranges
On Friday, 5 August 2022 at 04:05:08 UTC, Salih Dincer wrote: On Thursday, 4 August 2022 at 22:54:42 UTC, pascal111 wrote: I didn't notice that all what we needs to pop a range forward is just a slice, yes, we don't need variable here. Ranges and Slices are not the same thing. Slicing an array is easy. This is a language possibility. For example, you need an incrementing variable for the Fibonacci Series. SDB@79 What!!! so where's ranges?! I thought slices of any array are ranges, and understood it like that, and also there's no data type called ranges, it's like if you are talking about Ghostly data type!
Re: Ranges
On Fri, Aug 05, 2022 at 08:06:00AM -0700, Ali Çehreli via Digitalmars-d-learn wrote: > [...] I realized that the following fails with a RangeError: > > void main() { > auto arr = [1, 2, 3]; > arr[0..$-1] = arr[1..$];// <-- Runtime error > } > > I suspect the length of the array is stamped too soon. (?) > > Should that operation be supported? [...] This is why in C there's a difference between memcpy and memmove. I don't know how to express the equivalent in D, though. In general, you can't tell until runtime whether two slices overlap (`arr` could be aliased by another slice, for example, so you can't just tell by whether you're copying an overlapping range from the same variable). But if you know beforehand the ranges being copied are overlapping, you could use std.algorithm.bringToFront which would do the Right Thing(tm) in this case. T -- Why are you blatanly misspelling "blatant"? -- Branden Robinson
Re: Ranges
On 8/5/22 01:59, frame wrote: > On Thursday, 4 August 2022 at 22:14:26 UTC, Ali Çehreli wrote: > >> No element is copied or moved. :) >> >> Ali > > I know that :) And I know that. :) We don't know who else is reading these threads, so I didn't want to give wrong impression. Copying would happen if we added slicing on the left-hand side. However, I realized that the following fails with a RangeError: void main() { auto arr = [1, 2, 3]; arr[0..$-1] = arr[1..$];// <-- Runtime error } I suspect the length of the array is stamped too soon. (?) Should that operation be supported? Ali
Re: Ranges
On Thursday, 4 August 2022 at 22:14:26 UTC, Ali Çehreli wrote: No element is copied or moved. :) Ali I know that :) I just found that this user has problems to understand basics in D, so I tried not to go in detail and keep at its kind of logical layer. It seems the better way to help until the user asks specific questions.
Re: Ranges
On Thursday, 4 August 2022 at 22:54:42 UTC, pascal111 wrote: I didn't notice that all what we needs to pop a range forward is just a slice, yes, we don't need variable here. Ranges and Slices are not the same thing. Slicing an array is easy. This is a language possibility. For example, you need an incrementing variable for the Fibonacci Series. SDB@79
Re: Ranges
On Thursday, 4 August 2022 at 22:14:26 UTC, Ali Çehreli wrote: On 8/4/22 11:05, frame wrote: > `popFront()` The function was this: void popFront() { students = students[1 .. $]; } > copies all > elements except the first one into the variable (and overwrites it), so > it moves the data forward. That would be very slow. :) What actually happens is, just the two variables that define a slice is adjusted. Slices consist of two members: struct __an_int_D_array_behind_the_scenes { size_t length; int * ptr; } So, students = students[1..$]; is the same as doing the following: students.length = (students.length - 1); students.ptr = students.ptr + 1; (ptr's value would change by 4 bytes because 'int'.) No element is copied or moved. :) Ali I didn't notice that all what we needs to pop a range forward is just a slice, yes, we don't need variable here.
Re: Ranges
On 8/4/22 11:05, frame wrote: > `popFront()` The function was this: void popFront() { students = students[1 .. $]; } > copies all > elements except the first one into the variable (and overwrites it), so > it moves the data forward. That would be very slow. :) What actually happens is, just the two variables that define a slice is adjusted. Slices consist of two members: struct __an_int_D_array_behind_the_scenes { size_t length; int * ptr; } So, students = students[1..$]; is the same as doing the following: students.length = (students.length - 1); students.ptr = students.ptr + 1; (ptr's value would change by 4 bytes because 'int'.) No element is copied or moved. :) Ali
Re: Ranges
On 8/4/22 06:08, pascal111 wrote: > In next code from > "https://www.tutorialspoint.com/d_programming/d_programming_ranges.htm";, That page seems to be adapted from this original: http://ddili.org/ders/d.en/ranges.html > we have two issues: > > 1) Why the programmer needs to program "empty()", "front()", and > "popFront()" functions for ranges The programmer almost never needs to implement those functions. Existing data structures and algorithms are almost always sufficient. (I did need to implement them but really rarely.) I tried to explain what those functions do. I don't like my Students example much because wrapping a D slice does not make much sense. Again, I just try to explain them. > while they exist in the language > library? The existing front, popFronh, etc. are only for arrays (slices). > it seems there's no need to exert efforts for that. Exactly. > "https://dlang.org/phobos/std_range_primitives.html"; > > 2) "front()", and "popFront()" are using fixed constants to move forward > the range, while they should use variables. Well, 0 is always the first element and 1..$ are always the rest. Variables would not add any value there. However, the example could use the existing library function that you mention: > @property bool empty() const { >return students.length == 0; Better: import std.array : empty; return students.empty; > } > @property ref Student front() { >return students[0]; Better: import std.array : front; return students.front; > } > void popFront() { >students = students[1 .. $]; Better: import std.array : popFront; students.popFront(); But I think those implementations might confuse the reader. Ali
Re: Ranges
On Thursday, 4 August 2022 at 13:08:21 UTC, pascal111 wrote: 1) Why the programmer needs to program "empty()", "front()", and "popFront()" functions for ranges while they exist in the language library? it seems there's no need to exert efforts for that. "https://dlang.org/phobos/std_range_primitives.html"; - These functions are wrappers to use something as range - Ranges need to implement the functions to keep their data private, also there are complex types the need to handle data differently - Ranges must implement the functions so other function can recognize it as such (eg. `isInputRange`) - there is no common interface, it's determined by compile time 2) "front()", and "popFront()" are using fixed constants to move forward the range, while they should use variables. `front()` is always using the first element BUT `popFront()` copies all elements except the first one into the variable (and overwrites it), so it moves the data forward.
Re: Ranges
On Thursday, 4 August 2022 at 13:08:21 UTC, pascal111 wrote: ```D import std.stdio; import std.string; struct Student { string name; int number; string toString() const { return format("%s(%s)", name, number); } } struct School { Student[] students; } struct StudentRange { Student[] students; this(School school) { this.students = school.students; } @property bool empty() const { return students.length == 0; } @property ref Student front() { return students[0]; } void popFront() { students = students[1 .. $]; } } void main() { auto school = School([ Student("Raj", 1), Student("John", 2), Student("Ram", 3)]); auto range = StudentRange(school); writeln(range); writeln(school.students.length); writeln(range.front); range.popFront; writeln(range.empty); writeln(range); } ``` 😀
Ranges
In next code from "https://www.tutorialspoint.com/d_programming/d_programming_ranges.htm";, we have two issues: 1) Why the programmer needs to program "empty()", "front()", and "popFront()" functions for ranges while they exist in the language library? it seems there's no need to exert efforts for that. "https://dlang.org/phobos/std_range_primitives.html"; 2) "front()", and "popFront()" are using fixed constants to move forward the range, while they should use variables. '''D import std.stdio; import std.string; struct Student { string name; int number; string toString() const { return format("%s(%s)", name, number); } } struct School { Student[] students; } struct StudentRange { Student[] students; this(School school) { this.students = school.students; } @property bool empty() const { return students.length == 0; } @property ref Student front() { return students[0]; } void popFront() { students = students[1 .. $]; } } void main() { auto school = School([ Student("Raj", 1), Student("John", 2), Student("Ram", 3)]); auto range = StudentRange(school); writeln(range); writeln(school.students.length); writeln(range.front); range.popFront; writeln(range.empty); writeln(range); } '''
Re: Why does inputRangeObject fail to derive correctly for RandomAccessInfinite ranges?
On Saturday, 16 July 2022 at 12:40:09 UTC, Paul Backus wrote: On Saturday, 16 July 2022 at 08:40:10 UTC, D Lark wrote: On Wednesday, 13 July 2022 at 01:40:43 UTC, Paul Backus wrote: On Wednesday, 13 July 2022 at 01:23:35 UTC, D Lark wrote: [...] Yes, it should behave the way you expect. The current behavior is a bug. I've submitted a report for it here: https://issues.dlang.org/show_bug.cgi?id=23242 It looks like the resolution is that this can't be fixed? I'm not sure I understand the conclusion. What does this mean for this part of the library then? Should the RandomAccessInfinite!E interface be removed since it's not fully supported? The interface works fine, it's just that the `InputRangeObject!R` instance itself does not satisfy `isRandomAccessRange`: ```d auto seqObj = sequence!((a, n) => n).inputRangeObject; RandomAccessInfinite!size_t seqIface = seqObj; static assert( isRandomAccessRange!(typeof(seqIface))); // interface passes static assert(!isRandomAccessRange!(typeof(seqObj))); // object fails ``` So if you code to the interfaces and ignore the concrete type of the range object, you should not have any problems. Thanks this definitely alleviates the issue somewhat. However it is definitely surprising that an object which literally derives from an interface cannot be tested to implement said interface. Is this inconsistency not a problem?
Re: Why does inputRangeObject fail to derive correctly for RandomAccessInfinite ranges?
On Saturday, 16 July 2022 at 08:40:10 UTC, D Lark wrote: On Wednesday, 13 July 2022 at 01:40:43 UTC, Paul Backus wrote: On Wednesday, 13 July 2022 at 01:23:35 UTC, D Lark wrote: First, please can someone clarify if the behaviour I expect in the last line is consistent with the intention of the library? Yes, it should behave the way you expect. The current behavior is a bug. I've submitted a report for it here: https://issues.dlang.org/show_bug.cgi?id=23242 It looks like the resolution is that this can't be fixed? I'm not sure I understand the conclusion. What does this mean for this part of the library then? Should the RandomAccessInfinite!E interface be removed since it's not fully supported? The interface works fine, it's just that the `InputRangeObject!R` instance itself does not satisfy `isRandomAccessRange`: ```d auto seqObj = sequence!((a, n) => n).inputRangeObject; RandomAccessInfinite!size_t seqIface = seqObj; static assert( isRandomAccessRange!(typeof(seqIface))); // interface passes static assert(!isRandomAccessRange!(typeof(seqObj))); // object fails ``` So if you code to the interfaces and ignore the concrete type of the range object, you should not have any problems.
Re: Why does inputRangeObject fail to derive correctly for RandomAccessInfinite ranges?
On Saturday, 16 July 2022 at 08:40:10 UTC, D Lark wrote: On Wednesday, 13 July 2022 at 01:40:43 UTC, Paul Backus wrote: On Wednesday, 13 July 2022 at 01:23:35 UTC, D Lark wrote: First, please can someone clarify if the behaviour I expect in the last line is consistent with the intention of the library? Yes, it should behave the way you expect. The current behavior is a bug. I've submitted a report for it here: https://issues.dlang.org/show_bug.cgi?id=23242 It looks like the resolution is that this can't be fixed? I'm not sure I understand the conclusion. What does this mean for this part of the library then? Should the RandomAccessInfinite!E interface be removed since it's not fully supported? What if infinite-ness is a class/struct level property distinct from empty-ness like `const enum bool infinite`? It appears to me that this is what we try to achieve by the unfortunate coupling of declaring `empty` as a manifest constant to denote an infinite range. If we have a distinct convention for declaring infiniteness then for the infinite range case we can insert `bool empty(){return false;}` (or `const enum bool = false)` automatically or check agreement. We can even have a mixin like the `ImplementLength` one that can add both `infinite` and `empty` in one go, which might be useful for implementing infinite ranges.
Re: Why does inputRangeObject fail to derive correctly for RandomAccessInfinite ranges?
On Wednesday, 13 July 2022 at 01:40:43 UTC, Paul Backus wrote: On Wednesday, 13 July 2022 at 01:23:35 UTC, D Lark wrote: First, please can someone clarify if the behaviour I expect in the last line is consistent with the intention of the library? Yes, it should behave the way you expect. The current behavior is a bug. I've submitted a report for it here: https://issues.dlang.org/show_bug.cgi?id=23242 It looks like the resolution is that this can't be fixed? I'm not sure I understand the conclusion. What does this mean for this part of the library then? Should the RandomAccessInfinite!E interface be removed since it's not fully supported?
Re: Why does inputRangeObject fail to derive correctly for RandomAccessInfinite ranges?
On Wednesday, 13 July 2022 at 01:23:35 UTC, D Lark wrote: First, please can someone clarify if the behaviour I expect in the last line is consistent with the intention of the library? Yes, it should behave the way you expect. The current behavior is a bug. I've submitted a report for it here: https://issues.dlang.org/show_bug.cgi?id=23242
Why does inputRangeObject fail to derive correctly for RandomAccessInfinite ranges?
This is slightly related to this issue I reported earlier: https://forum.dlang.org/post/wghdwxptjfcjwptny...@forum.dlang.org Here's the snippet that captures what fails for me ```dlang import std.range: ElementType, isRandomAccessRange, inputRangeObject, sequence, MostDerivedInputRange, RandomAccessInfinite; auto seq = sequence!((a, n) => n); alias SeqType = typeof(seq); static assert(isRandomAccessRange!SeqType); static assert(is(MostDerivedInputRange!SeqType == RandomAccessInfinite!(ElementType!SeqType))); auto seqInputRange = seq.inputRangeObject; static assert(isRandomAccessRange!(typeof(seqInputRange))); // returns 'false'; I expect this to return 'true' as the most derived type of 'seq' is of 'RandomAccessInfinite' as demonstrated above ``` I am compiling using dmd v2.100.1 First, please can someone clarify if the behaviour I expect in the last line is consistent with the intention of the library? I have poked around the source and I can see that the `InputRangeObject` (the object returned by `inputRangeObject` derives from the return type of the `MostDerivedInputRange` template, which as shown above returns the correct interface. However it seems that the implementation of `InputRangeObject` does not implement `enum bool empty = false` in the case of the `RandomAccessInfinite` ranges, which leads to the template `isRandomAccessRange` returning false.
Re: Dynamic chain for ranges?
On Monday, 13 June 2022 at 14:03:13 UTC, Steven Schveighoffer wrote: Merge sort only works if it's easy to manipulate the structure, like a linked-list, or to build a new structure, like if you don't care about allocating a new array every iteration. The easiest option is to have two buffers that can hold all items, in the last merge you merge back to the input-storage. But yeah, it is only «fast» for very large arrays.
Re: Dynamic chain for ranges?
On 6/13/22 9:44 AM, Ola Fosheim Grøstad wrote: On Monday, 13 June 2022 at 13:22:52 UTC, Steven Schveighoffer wrote: I would think sort(joiner([arr1, arr2, arr3])) should work, but it's not a random access range. Yes, I got the error «must satisfy the following constraint: isRandomAccessRange!Range`». It would be relatively easy to make it work as a random access range if arr1, arr2, etc were fixed size slices. Or I guess, use insertion-sort followed by merge-sort. Merge sort only works if it's easy to manipulate the structure, like a linked-list, or to build a new structure, like if you don't care about allocating a new array every iteration. -Steve
Re: Dynamic chain for ranges?
On Monday, 13 June 2022 at 13:22:52 UTC, Steven Schveighoffer wrote: I would think sort(joiner([arr1, arr2, arr3])) should work, but it's not a random access range. Yes, I got the error «must satisfy the following constraint: isRandomAccessRange!Range`». It would be relatively easy to make it work as a random access range if arr1, arr2, etc were fixed size slices. Or I guess, use insertion-sort followed by merge-sort.
Re: Dynamic chain for ranges?
On 6/13/22 4:51 AM, Ola Fosheim Grøstad wrote: Is there a dynamic chain primitive, so that you can add to the chain at runtime? Context: the following example on the front page is interesting. ```d void main() { int[] arr1 = [4, 9, 7]; int[] arr2 = [5, 2, 1, 10]; int[] arr3 = [6, 8, 3]; sort(chain(arr1, arr2, arr3)); writefln("%s\n%s\n%s\n", arr1, arr2, arr3); } ``` But it would be much more useful in practice if "chain" was a dynamic array. `chain` allows ranges of different types. `joiner` should be the equivalent for a dynamic range of ranges of the same type. I would think sort(joiner([arr1, arr2, arr3])) should work, but it's not a random access range. Most likely because the big-O constants are no longer constant. -Steve
Re: Dynamic chain for ranges?
On Monday, 13 June 2022 at 09:08:40 UTC, Salih Dincer wrote: On Monday, 13 June 2022 at 08:51:03 UTC, Ola Fosheim Grøstad wrote: But it would be much more useful in practice if "chain" was a dynamic array. Already so: I meant something like: chain = [arr1, arr2, …, arrN] I don't use ranges, but I thought this specific use case could be valuable. Imagine you have a chunked datastructure of unknown lengths, and you want to "redistribute" items without reallocation.
Re: Dynamic chain for ranges?
On Monday, 13 June 2022 at 08:51:03 UTC, Ola Fosheim Grøstad wrote: But it would be much more useful in practice if "chain" was a dynamic array. Already so: ```d int[] arr = [4, 9, 7, 5, 2, 1, 10, 6, 8, 3]; int[] arr1 = arr[0..3]; int[] arr2 = arr[3..7]; int[] arr3 = arr[7..$]; sort(chain(arr1, arr2, arr3)); writefln("%s\n%s\n%s\n", arr1, arr2, arr3); typeid(arr).writeln(": ", arr); writeln(&arr[0], " == ", &arr1[0]); /* Print Out: [1, 2, 3] [4, 5, 6, 7] [8, 9, 10] int[]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 7F7FBE348000 == 7F7FBE348000 */ ```
Dynamic chain for ranges?
Is there a dynamic chain primitive, so that you can add to the chain at runtime? Context: the following example on the front page is interesting. ```d void main() { int[] arr1 = [4, 9, 7]; int[] arr2 = [5, 2, 1, 10]; int[] arr3 = [6, 8, 3]; sort(chain(arr1, arr2, arr3)); writefln("%s\n%s\n%s\n", arr1, arr2, arr3); } ``` But it would be much more useful in practice if "chain" was a dynamic array.
Re: number ranges
> 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.
Re: number ranges
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
Re: number ranges
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
Re: number ranges
On Thursday, 20 January 2022 at 16:33:20 UTC, Ali Çehreli wrote: So if we add the 1.0 value after 0.900357627869 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); } ```
Re: number ranges
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. 0.300119209290 0.600238418579 0.900357627869 <-- Where is 1.0? Decrementing: 1. 0.699880790710 0.399761581421 0.099642372131 <-- Where is 0.0? So if we add the 1.0 value after 0.900357627869 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
Re: number ranges
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
Re: number ranges
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
Re: number ranges
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 } ```
Re: number ranges
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..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...
Re: number ranges
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..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...
Re: number ranges
On Tuesday, 18 January 2022 at 17:58:54 UTC, H. S. Teoh wrote: On Tue, Jan 18, 2022 at 04:02:42PM +, Tejas via Digitalmars-d-learn wrote: [...] Newer languages nowadays use `start..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 "" for n in 5 ..< 9: #5 is included but 9 is excluded echo n ``` In Odin also: ``` for i in 0..<10 { fmt.println(i) } // or for i in 0..9 { fmt.println(i) } ```
Re: number ranges
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.
Re: number ranges
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
Re: number ranges
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)
Re: number ranges
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
Re: number ranges
On Tuesday, 18 January 2022 at 16:02:42 UTC, Tejas wrote: Newer languages nowadays use `start..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)); }
Re: number ranges
On Tue, Jan 18, 2022 at 04:02:42PM +, Tejas via Digitalmars-d-learn wrote: [...] > Newer languages nowadays use `start.. 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
Re: number ranges
On Monday, 17 January 2022 at 22:48:17 UTC, H. S. Teoh wrote: On Mon, Jan 17, 2022 at 10:35:30PM +, 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..think it's something we should follow?
Re: number ranges
On Mon, Jan 17, 2022 at 10:35:30PM +, 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...
Re: number ranges
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."
Re: number ranges
On Mon, Jan 17, 2022 at 10:22:19PM +, 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.
Re: number ranges
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 ;-)
Re: number ranges
On Mon, Jan 17, 2022 at 09:37:31PM +, 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.
Re: number ranges
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
Re: number ranges
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
Re: number ranges
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.)
number ranges
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;
Re: bool empty() const for ranges
On 11/26/21 5:44 AM, Salih Dincer wrote: Hi All; I have two questions that make each other redundant. Please answer one of them. I'm implementing ```bool empty() const``` for ranges as below: ```d bool empty() // const { bool result; if(!head) { result = true; fastRewind(); } return result; // head ? false : true; } ``` * Is the const essential for ranges? No, there is no specification of whether any range methods have to be const. As Stanislav says, it is only a requirement that subsequent calls return the same value as long as popFront has not been called. * Is it possible to rewind the pointer (```Node * head;```) when my head is empty by the const? If const is set, then any members are treated as const, and anything they point to are treated as const. So no. That being said, if `fastRewind` changes `empty` from true to false, the method is invalid. -Steve
Re: bool empty() const for ranges
On Friday, 26 November 2021 at 10:44:10 UTC, Salih Dincer wrote: * Is the const essential for ranges? * Is it possible to rewind the pointer (```Node * head;```) when my head is empty by the const? `empty` is not required to be `const`, but it is required to yield the same result if called multiple times without mutating the range (see https://dlang.org/phobos/std_range_primitives.html#.isInputRange). In other words, you really ought not to mutate the range in implementation of `empty`, or at least not to the extent you seem to want to.
bool empty() const for ranges
Hi All; I have two questions that make each other redundant. Please answer one of them. I'm implementing ```bool empty() const``` for ranges as below: ```d bool empty() // const { bool result; if(!head) { result = true; fastRewind(); } return result; // head ? false : true; } ``` * Is the const essential for ranges? * Is it possible to rewind the pointer (```Node * head;```) when my head is empty by the const? Thanks...
Re: A little help with Ranges
On 8/27/21 12:41 AM, Merlin Diavova wrote: On Friday, 27 August 2021 at 04:01:19 UTC, Ali Çehreli wrote: On 8/26/21 7:17 PM, Merlin Diavova wrote: [...] Then the operations downstream will not produce any results. For example, the array will be empty below: import std.stdio; import std.range; import std.algorithm; import std.string; import std.functional; void main() { auto significantLines = stdin .byLineCopy .map!strip .filter!(not!empty) .filter!(line => line.front != '#') .array; if (significantLines.empty) { writeln("There were no significant lines."); } else { writefln!"The lines: %-(\n%s%)"(significantLines); } } Ali And there it is! I was missing ```d .filter!(not!empty) ``` My code now works exactly how I wanted. Thanks! Be careful with this! `not!empty` is *only* working because you are using arrays (where `empty` is a UFCS function defined in std.range). Other ranges this will not work on. Instead, I would recommend a lambda (which will work with arrays too): ```d .filter!(r => !r.empty) ``` -Steve
Re: A little help with Ranges
On Friday, 27 August 2021 at 04:01:19 UTC, Ali Çehreli wrote: On 8/26/21 7:17 PM, Merlin Diavova wrote: [...] Then the operations downstream will not produce any results. For example, the array will be empty below: import std.stdio; import std.range; import std.algorithm; import std.string; import std.functional; void main() { auto significantLines = stdin .byLineCopy .map!strip .filter!(not!empty) .filter!(line => line.front != '#') .array; if (significantLines.empty) { writeln("There were no significant lines."); } else { writefln!"The lines: %-(\n%s%)"(significantLines); } } Ali And there it is! I was missing ```d .filter!(not!empty) ``` My code now works exactly how I wanted. Thanks!
Re: A little help with Ranges
On 8/26/21 7:17 PM, Merlin Diavova wrote: What I meant about the handling an empty filter is, what if I want to take an alternative route if the filter returns empty? Then the operations downstream will not produce any results. For example, the array will be empty below: import std.stdio; import std.range; import std.algorithm; import std.string; import std.functional; void main() { auto significantLines = stdin .byLineCopy .map!strip .filter!(not!empty) .filter!(line => line.front != '#') .array; if (significantLines.empty) { writeln("There were no significant lines."); } else { writefln!"The lines: %-(\n%s%)"(significantLines); } } Ali
Re: A little help with Ranges
On Friday, 27 August 2021 at 02:17:21 UTC, Merlin Diavova wrote: On Friday, 27 August 2021 at 02:10:48 UTC, Stefan Koch wrote: On Friday, 27 August 2021 at 01:51:42 UTC, Merlin Diavova wrote: Hi all, I'm Merlin, I'm just starting out in D and super excited. My questions are:- 1. In a range pipeline how does one handle the event of a filter range returning empty? 2. How does one unwrap a single result from a range operation? Look forward to your assistance! Merlin 1. you don't have to handle it. it just won't go. 2. `takeOne` put it into the search on the dlang site Thanks for the quick response! Took a look at takeOne and yep that's the ticket. What I meant about the handling an empty filter is, what if I want to take an alternative route if the filter returns empty? You check if it's by calling empty. or do you want a default value? I am sure there is a convince function for that somewhere in phobos.
Re: A little help with Ranges
On Friday, 27 August 2021 at 02:10:48 UTC, Stefan Koch wrote: On Friday, 27 August 2021 at 01:51:42 UTC, Merlin Diavova wrote: Hi all, I'm Merlin, I'm just starting out in D and super excited. My questions are:- 1. In a range pipeline how does one handle the event of a filter range returning empty? 2. How does one unwrap a single result from a range operation? Look forward to your assistance! Merlin 1. you don't have to handle it. it just won't go. 2. `takeOne` put it into the search on the dlang site Thanks for the quick response! Took a look at takeOne and yep that's the ticket. What I meant about the handling an empty filter is, what if I want to take an alternative route if the filter returns empty?
Re: A little help with Ranges
On Friday, 27 August 2021 at 01:51:42 UTC, Merlin Diavova wrote: Hi all, I'm Merlin, I'm just starting out in D and super excited. My questions are:- 1. In a range pipeline how does one handle the event of a filter range returning empty? 2. How does one unwrap a single result from a range operation? Look forward to your assistance! Merlin 1. you don't have to handle it. it just won't go. 2. `takeOne` put it into the search on the dlang site
A little help with Ranges
Hi all, I'm Merlin, I'm just starting out in D and super excited. My questions are:- 1. In a range pipeline how does one handle the event of a filter range returning empty? 2. How does one unwrap a single result from a range operation? Look forward to your assistance! Merlin
Re: foreach() behavior on ranges
On Wednesday, 25 August 2021 at 11:02:23 UTC, Steven Schveighoffer wrote: On 8/25/21 4:31 AM, frame wrote: On Tuesday, 24 August 2021 at 21:15:02 UTC, Steven Schveighoffer wrote: I'm surprised you bring PHP as an example, as it appears their foreach interface works EXACTLY as D does: Yeah, but the point is, there is a rewind() method. That is called every time on foreach(). It seems what you are after is forward ranges. Those are able to "rewind" when you are done with them. It's just not done through a rewind method, but via saving the range before iteration: ```d foreach(val; forwardRange.save) { ... break; } // forwardRange hasn't been iterated here ``` -Steve This could be any custom method for my ranges or forward range returned by some function. But that doesn't help if some thirdparty library function would break and return just an input range. Then it seems that it must be very properly implemented like postblit technics mentioned before. Some author may never care about. That it works in 99% of all cases should not be an excuse for a design flaw. The documentation really need to mention this.
Re: foreach() behavior on ranges
On Wednesday, 25 August 2021 at 19:51:36 UTC, H. S. Teoh wrote: What I understand from what Andrei has said in the past, is that a range is merely a "view" into some underlying storage; it is not responsible for the contents of that storage. My interpretation of this is that .save will only save the *position* of the range, but it will not save the contents it points to, so it will not (should not) deep-copy. That definition is potentially misleading if we take into account that a range is not necessarily iterating over some underlying storage: ranges can also be defined by algorithmic processes. (Think e.g. iota, or pseudo-RNGs, or a range that iterates over the Fibonacci numbers.) However, if the range is implemented by a struct that contains a reference to its iteration state, then yes, to satisfy the definition of .save it should deep-copy this state. Right. And in the case of algorithmic ranges (rather than container-derived ranges), the state is always and only the iteration state. And then as well as that there are ranges that are iterating over external IO, which in most cases can't be treated as forward ranges but in a few cases might be (e.g. saving the cursor position when iterating over a file's contents). Arguably I think a lot of problems in the range design derive from not thinking through those distinctions in detail (external-IO-based vs. algorithmic vs. container-based), even though superficially those seem to map well to the input vs forward vs bidirectional vs random-access range distinctions. That's also not taking into account edge cases, e.g. stuff like RandomShuffle or RandomSample: here one can in theory copy the "head" of the range but one arguably wants to avoid correlations in the output of the different copies (which can arise from at least 2 different sources: copying under-the-hood pseudo-random state of the sampling/shuffling algorithm itself, or copying the underlying pseudo-random number generator). Except perhaps in the case where one wants to take advantage of the pseudo-random feature to reproduce those sequences ... but then one wants that to be a conscious programmer decision, not happening by accident under the hood of some library function. (Rabbit hole, here we come.) Andrei has mentioned before that in retrospect, .save was a design mistake. The difference between an input range and a forward range should have been keyed on whether the range type has reference semantics (input range) or by-value semantics (forward range). But for various reasons, including the state of the language at the time the range API was designed, the .save route was chosen, and we're stuck with it unless Phobos 2.0 comes into existence. Either way, though, the semantics of a forward range pretty much dictates that whatever type a range has, if it claims to be a forward range then .save must preserve whatever iteration state it has at that point in time. If this requires deep-copying some state referenced from a struct, then that's what it takes to satisfy the API. This may take the form of a .save method that copies state, or a copy ctor that does the same, or simply storing iteration state as PODs in the range struct so that copying the struct equates to preserving the iteration state. Yes. FWIW I agree that when _implementing_ a forward range one should probably make sure that copying by value and the `save` method produce the same results. But as a _user_ of code implemented using the current range API, it might be a bad idea to assume that a 3rd party forward range implementation will necessarily guarantee that.
Re: foreach() behavior on ranges
On Wed, Aug 25, 2021 at 04:46:54PM +, Joseph Rushton Wakeling via Digitalmars-d-learn wrote: > On Wednesday, 25 August 2021 at 10:59:44 UTC, Steven Schveighoffer wrote: > > structs still provide a mechanism (postblit/copy ctor) to properly > > save a forward range when copying, even if the guts need copying > > (unlike classes). In general, I think it was a mistake to use > > `.save` as the mechanism, as generally `.save` is equivalent to > > copying, so nobody does it, and code works fine for most ranges. > > Consider a struct whose internal fields are just a pointer to its > "true" internal state. Does one have any right to assume that the > postblit/copy ctor would necessarily deep-copy that? [...] > If that struct implements a forward range, though, and that pointed-to > state is mutated by iteration of the range, then it would be > reasonable to assume that the `save` method MUST deep-copy it, because > otherwise the forward-range property would not be respected. [...] What I understand from what Andrei has said in the past, is that a range is merely a "view" into some underlying storage; it is not responsible for the contents of that storage. My interpretation of this is that .save will only save the *position* of the range, but it will not save the contents it points to, so it will not (should not) deep-copy. However, if the range is implemented by a struct that contains a reference to its iteration state, then yes, to satisfy the definition of .save it should deep-copy this state. > With that in mind, I am not sure it's reasonable to assume that just > because a struct implements a forward-range API, that copying the > struct instance is necessarily the same as saving the range. [...] Andrei has mentioned before that in retrospect, .save was a design mistake. The difference between an input range and a forward range should have been keyed on whether the range type has reference semantics (input range) or by-value semantics (forward range). But for various reasons, including the state of the language at the time the range API was designed, the .save route was chosen, and we're stuck with it unless Phobos 2.0 comes into existence. Either way, though, the semantics of a forward range pretty much dictates that whatever type a range has, if it claims to be a forward range then .save must preserve whatever iteration state it has at that point in time. If this requires deep-copying some state referenced from a struct, then that's what it takes to satisfy the API. This may take the form of a .save method that copies state, or a copy ctor that does the same, or simply storing iteration state as PODs in the range struct so that copying the struct equates to preserving the iteration state. T -- Why waste time reinventing the wheel, when you could be reinventing the engine? -- Damian Conway
Re: foreach() behavior on ranges
On Wednesday, 25 August 2021 at 17:01:54 UTC, Steven Schveighoffer wrote: In a world where copyability means it's a forward range? Yes. We aren't in that world, it's a hypothetical "if we could go back and redesign". OK, that makes sense. Technically this is true. In practice, it rarely happens. The flaw of `save` isn't that it's an unsound API, the flaw is that people get away with just copying, and it works 99.9% of the time. So code is simply untested with ranges where `save` is important. This is very true, and makes it quite reasonable to try to pursue "the obvious/lazy thing == the thing you're supposed to do" w.r.t. how ranges are defined. I'd be willing to bet $10 there is a function in phobos right now, that takes forward ranges, and forgets to call `save` when iterating with foreach. It's just so easy to do, and works with most ranges in existence. I'm sure you'd win that bet! The idea is to make the meaning of a range copy not ambiguous. Yes, this feels reasonable. And then one can reserve the idea of a magic deep-copy method for special cases like pseudo-RNGs where one wants them to be copyable on user request, but without code assuming it can copy them.
Re: foreach() behavior on ranges
On 8/25/21 12:46 PM, Joseph Rushton Wakeling wrote: On Wednesday, 25 August 2021 at 10:59:44 UTC, Steven Schveighoffer wrote: structs still provide a mechanism (postblit/copy ctor) to properly save a forward range when copying, even if the guts need copying (unlike classes). In general, I think it was a mistake to use `.save` as the mechanism, as generally `.save` is equivalent to copying, so nobody does it, and code works fine for most ranges. Consider a struct whose internal fields are just a pointer to its "true" internal state. Does one have any right to assume that the postblit/copy ctor would necessarily deep-copy that? In a world where copyability means it's a forward range? Yes. We aren't in that world, it's a hypothetical "if we could go back and redesign". If that struct implements a forward range, though, and that pointed-to state is mutated by iteration of the range, then it would be reasonable to assume that the `save` method MUST deep-copy it, because otherwise the forward-range property would not be respected. With that in mind, I am not sure it's reasonable to assume that just because a struct implements a forward-range API, that copying the struct instance is necessarily the same as saving the range. Technically this is true. In practice, it rarely happens. The flaw of `save` isn't that it's an unsound API, the flaw is that people get away with just copying, and it works 99.9% of the time. So code is simply untested with ranges where `save` is important. Indeed, IIRC quite a few Phobos library functions program defensively against that difference by taking a `.save` copy of their input before iterating over it. I'd be willing to bet $10 there is a function in phobos right now, that takes forward ranges, and forgets to call `save` when iterating with foreach. It's just so easy to do, and works with most ranges in existence. What should have happened is that input-only ranges should not have been copyable, and copying should have been the save mechanism. Then it becomes way way more obvious what is happening. Yes, this means forgoing classes as ranges. I think there's a benefit of a method whose definition is explicitly "If you call this, you will get a copy of the range which will replay exactly the same results when iterating over it". Just because the meaning of "copy" can be ambiguous, whereas a promise about how iteration can be used is not. The idea is to make the meaning of a range copy not ambiguous. -Steve