How to use ranges?

anonymous via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sun Aug 23 12:26:17 PDT 2015


On Sunday 23 August 2015 19:58, Doolan wrote:

>> You can use typeof to get the type of a range expression when
>> typing it out is impractical/impossible.
> What if I want to save a range in a struct? Or is a range more of
> a verb than a noun..?

Can still use typeof then:
----
struct S
{
    import std.range: tee;
    import std.stdio: writeln;
    
    typeof((int[]).init.tee!writeln) teeing;
    
    this(int[] a)
    {
        teeing = a.tee!writeln;
    }
}

void main()
{
    auto s = S([1, 2, 3, 4]);
    foreach(x; s.teeing) {}
}
----

Alternatively you can get classy and use std.range.interfaces:

----
struct S
{
    import std.range: tee;
    import std.range.interfaces: InputRange, inputRangeObject;
    import std.stdio: writeln;
    
    InputRange!int teeing;
    
    this(int[] a)
    {
        teeing = inputRangeObject(a.tee!writeln);
    }
}

void main()
{
    auto s = S([1, 2, 3, 4]);
    foreach(x; s.teeing) {}
}
----

> I need to compress some data, and luckily it's very suited for
> Running Length Encoding, so I've gone with doing that.
> Occasionally changes need to be made to this data and so rather
> than extracting the data, changing it, and then recompressing it,
> I can just do the equivalent of leaving a post-it note reminding
> the decompression function to sprinkle these changes in after
> decompression. Occasionally, I also need to grab some values out
> of this data without decompressing it, but for every additional
> value I look for I have to search the post-it notes and it gets a
> little fiddly.

Here's a quick implementation of a run-length decoder range, and some 
example usage.

I'm not sure how to go about the post-it note concept, or how practical it 
is. The decoder is not a random-access range, so when you want to edit the 
data and access individual items, then decode-edit-encode may work better. 
You don't have to shoehorn something into ranges just because ranges are 
cool.

But if you want to apply a bunch of transformations to the whole thing, then 
ranges shine.

----
import std.range: empty, front, popFront, save;
import std.stdio;

struct RunLengthItem(T)
{
    T value;
    size_t length;
}

struct RunLengthDecoder(T)
{
    RunLengthItem!T[] items;
    size_t currentRun = 0;
    
    @property bool empty() const {return items.empty;}
    @property T front() {return items.front.value;}
    void popFront()
    {
        ++currentRun;
        if(currentRun >= items.front.length)
        {
            items.popFront();
            currentRun = 0;
        }
    }
    @property RunLengthDecoder save() {return this;}
}

void main()
{
    /* two 'f's, three 'o's, five 'b's, seven 'a's, eleven 'r's */
    RunLengthItem!dchar[] data = [
        RunLengthItem!dchar('f', 2),
        RunLengthItem!dchar('o', 3),
        RunLengthItem!dchar('b', 5),
        RunLengthItem!dchar('a', 7),
        RunLengthItem!dchar('r', 11)
    ];
    
    auto decoder = RunLengthDecoder!dchar(data);
    
    /* Just print the elements (writeln is aware of ranges): */
    writeln(decoder.save); /* ffooobbbbbaaaaaaarrrrrrrrrrr */
    
    /* Uppercase when printing: */
    import std.uni: asUpperCase;
    writeln(decoder.save.asUpperCase); /* FFOOOBBBBBAAAAAAARRRRRRRRRRR */
    
    /* Filter out the 'b's, uppercase, and put an underscore every five 
characters: */
    import std.algorithm: filter, joiner;
    import std.range: chunks;
    auto r = decoder.save
        .filter!(c => c != 'b')
        .asUpperCase
        .chunks(5).joiner("_");
    writeln(r); /* FFOOO_AAAAA_AARRR_RRRRR_RRR */
}
----


> So, I vaguely know what ranges are, and I've heard you can chain
> them together, and my code would be much more readable if I could
> cut up access to the data and splice in changes... but I don't
> even know how to define a range of the right type...

Sounds like you may have some specific code you could use help with. If so, 
don't be afraid to post that code and ask how it can be range-ified.

> So slices are random-access ranges... I understand the
> random-access part... but are they inheriting from Range, do they
> just include a Range?

There's no inheritance going on. Inheritance is a class thing. Arrays/slices 
are not classes.

std.range defines the necessary range operations for slices: empty, front, 
popFront, etc. That's how slices are ranges.

> Why is int[] an array when I declare, but
> variable[] a Range?? Or isn't it a Range?

Every int[] is a dynamic array, a slice, and a range. Doesn't matter where 
that int[] comes from.



More information about the Digitalmars-d-learn mailing list