Thread overview
Using algorithms with ranges
Oct 03, 2019
Brett
Oct 03, 2019
mipri
Oct 03, 2019
mipri
Oct 03, 2019
Andrea Fontana
Oct 03, 2019
mipri
Oct 03, 2019
mipri
October 03, 2019
I routinely have to generate data using points sequentially and refer to previous points in the data set, maybe even search them. I also may have to break up the algorithm in to parts.

I'd like to get more in to ranges but I simply do not use them much because I don't know all the fancy stuff that makes them more useful over just doing it manually(usually for loop or 3).


Is there a way to express an algorithm that generates a data point that may depend on previous data points and "rangify" them?

One usually starts with some integer range: iota(-5,5,1)

The idea then is to be able to generates points off the range but have access to the previous generated points without having to regenerate them(very inefficient).

so if P_k is our kth point then we need to have access to P_j for j < k.

The algorithm may "branch"(multiple sub algorithms). For example, maybe for negatives we have a different algorithm.

The idea is to do it as efficiently and simple as possible. If it doesn't beat for loops and adds complexity then it is really not better than using loops.


I would think of something like

iota(-5,5,1).kmap!((k,History)=>{return History[k-1]+1;})

But the problems is that what if we want to have a different algorithm for the negatives? Obviously we could add an if statement but then that is executed for every k. We could split iota but we then break History up and that causes problems when we really want a contiguous History. Then there is the issue of accessing invalid history values which then requires checks and fixes.

For example, the above algorithm works fine but only has an issue when k = 0. The entire rest of the algorithm suffers because of this issue. There are ways around it such as being able to provide "out of bounds" values such as having History[k] = 0 when k < 0.

These are the types of differences between

for(int k = 0; ....)
if (k < 1) { return 0; }
else return History[k-1]+1;

and

if (k == 0) return 0;
for(int k = 1; ....)
return History[k-1]+1;

One could maybe have something like

iota(-5,5,1).kmap!((-5,History)=>{return 0;}).kmap!((k>-5,History)=>{return History[k-1]+1;})


but of course doesn't make a lot of syntactical sense... yet it could be done with the appropriate machinery. Of course these are simple examples.

The idea here is that I would like to generate everything range like without too much work.

Is it possible in D?

Again, if the range code is very complicated and one has to jump through a lot of hoops then it is easier to just do it manually. The main thing I'm interested in is the "History" feature and maybe a way to easily partition the range(range of ranges).




October 03, 2019
On Thursday, 3 October 2019 at 04:33:10 UTC, Brett wrote:
> I routinely have to generate data using points sequentially and refer to previous points in the data set, maybe even search them. I also may have to break up the algorithm in to parts.
>
> I'd like to get more in to ranges but I simply do not use them much because I don't know all the fancy stuff that makes them more useful over just doing it manually(usually for loop or 3).
>
>
> Is there a way to express an algorithm that generates a data point that may depend on previous data points and "rangify" them?
>
> One usually starts with some integer range: iota(-5,5,1)
>
> The idea then is to be able to generates points off the range but have access to the previous generated points without having to regenerate them(very inefficient).
>
> so if P_k is our kth point then we need to have access to P_j for j < k.
>
...
> Again, if the range code is very complicated and one has to jump through a lot of hoops then it is easier to just do it manually. The main thing I'm interested in is the "History" feature and maybe a way to easily partition the range(range of ranges).

Basic use (which is all I know) of ranges is easy-peasy.  You just
define empty/front/popFront and use it as a range.  You can keep
whatever state you want in addition to that.  I've got a range that
internally has an open file handle and PCRE pointers in it. The only
complication is that I have to put my destructor code in a normal
function so that both the destructor (in case of unintended use that
leaves the GC cleaning things up) and exhausting the range (in the
intended case) can both call it.

Here:

  #! /usr/bin/env rdmd
  import std.stdio;

  struct iotaPlus(T) {
      import std.range : iota;

      typeof(iota(0,0)) r;
      T[] history;

      this(T a, T b) {
          r = iota(a, b);
      }

      bool empty() {
          return r.empty;
      }

      void popFront() {
          r.popFront;
      }

      T front() {
          T x = r.front;
          history ~= x;
          if (history.length > 1) {
              return x + history[$-2];
          } else {
              return x;
          }
      }
  }

  void main() {
      foreach (x; iotaPlus!int(1, 10))
          writeln(x);
  }

Output:

  1
  3
  5
  7
  9
  11
  13
  15
  17

It'd be nicer to do compose a range over iota, as in

  iota(1, 10).newThingWithHistory

but I don't know how to do that yet. I guess c.f. std.range.retro

October 03, 2019
On Thursday, 3 October 2019 at 05:20:47 UTC, mipri wrote:
> It'd be nicer to do compose a range over iota, as in
>
>   iota(1, 10).newThingWithHistory
>
> but I don't know how to do that yet. I guess c.f. std.range.retro

This is a bit better:

#! /usr/bin/env rdmd
import std.stdio;

auto withHistory(Range)(Range r) {
    import std.traits : Unqual;
    import std.range.primitives : ElementType;

    static struct Result {
    private:
        alias R = Unqual!Range;
        alias T = ElementType!R;

        R source;
        T[] history;

    public:
        bool empty() {
            return source.empty;
        }

        void popFront() {
            source.popFront;
        }

        T front() {
            T x = source.front;
            history ~= x;
            if (history.length > 1) {
                return x + history[$-2];
            } else {
                return x;
            }
        }
    }

    return Result(r);
}

void main() {
    import std.range : iota;

    foreach (x; iota(1, 10).withHistory)
        writeln(x);
}

October 03, 2019
On Thursday, 3 October 2019 at 05:33:04 UTC, mipri wrote:
> void main() {
>     import std.range : iota;
>
>     foreach (x; iota(1, 10).withHistory)
>         writeln(x);
> }


This doesn't work as expected, I think.

auto r = iota(1,10).withHistory;

writeln(r.front);
writeln(r.front);
October 03, 2019
On Thursday, 3 October 2019 at 08:52:22 UTC, Andrea Fontana wrote:
> On Thursday, 3 October 2019 at 05:33:04 UTC, mipri wrote:
>> void main() {
>>     import std.range : iota;
>>
>>     foreach (x; iota(1, 10).withHistory)
>>         writeln(x);
>> }
>
>
> This doesn't work as expected, I think.
>
> auto r = iota(1,10).withHistory;
>
> writeln(r.front);
> writeln(r.front);

Oops. That output should be the same, since popFront hasn't been called.

The code's also bad for preserving the entire history when only the
latest is needed for what it's doing.
October 03, 2019
On Thursday, 3 October 2019 at 08:52:22 UTC, Andrea Fontana wrote:
> On Thursday, 3 October 2019 at 05:33:04 UTC, mipri wrote:
>> void main() {
>>     import std.range : iota;
>>
>>     foreach (x; iota(1, 10).withHistory)
>>         writeln(x);
>> }
>
>
> This doesn't work as expected, I think.
>
> auto r = iota(1,10).withHistory;
>
> writeln(r.front);
> writeln(r.front);

Oops. That output should be the same, since popFront hasn't been called.
I made this mistake in my other ranges as well...

The code's also bad for preserving the entire history when only the
latest is needed for what it's doing.