Thread overview
Getting equivalent elements in a range/array
May 08, 2011
Andrej M.
May 08, 2011
Ali Çehreli
May 08, 2011
Andrej Mitrovic
May 08, 2011
bearophile
May 08, 2011
Andrej Mitrovic
May 08, 2011
I want to turn this:
auto arr = [1, 1, 2, 3, 4, 4];

into this:
auto arr2 = [[1, 1], [2], [3], [4, 4]];

I want an array of arrays of the same elements. Lazy or not, I don't care.

I thought I could get away with this inside some while loop:
auto equals = array(filter!"a == b"(arr));
arr = arr[equals.length-1..$];

Nope.

I need this for some buffered output, where the requirement is the elements of the buffer all need to have the same properties so a function can output a buffer of elements in one call instead of calling the function for each element (the function call is expensive).
May 08, 2011
On 05/07/2011 09:07 PM, Andrej M. wrote:
> I want to turn this:
> auto arr = [1, 1, 2, 3, 4, 4];
>
> into this:
> auto arr2 = [[1, 1], [2], [3], [4, 4]];
>
> I want an array of arrays of the same elements. Lazy or not, I don't care.
>
> I thought I could get away with this inside some while loop:
> auto equals = array(filter!"a == b"(arr));
> arr = arr[equals.length-1..$];
>
> Nope.
>
> I need this for some buffered output, where the requirement is the elements of the buffer all need to have the same properties so a function can output a buffer of elements in one call instead of calling the function for each element (the function call is expensive).

This seems to work, but needs more work. :)

import std.stdio;
import std.array;

struct EquivalentElements
{
    int[] range;
    int[] front_;

    this(int[] range)
    {
        this.range = range;
        this.front_ = popEqualFront(this.range);
    }

    bool empty()
    {
        return front_.empty;
    }

    int[] front()
    {
        return front_;
    }

    void popFront()
    {
        this.front_ = popEqualFront(this.range);
    }

    private int[] popEqualFront(ref int[] range)
    {
        int[] front;

        if (!range.empty) {
            do {
                front ~= range[0];
                range = range[1..$];

            } while (!range.empty &&
                     (front[$-1] == range[0]));
        }

        return front;
    }
}

void main()
{
    writeln(EquivalentElements([1, 1, 2, 3, 4, 4]));
}
May 08, 2011
Fantastic work, thanks! I'll look into more detail tomorrow, but it looks good so far. Just added a function helper and made the struct typed:

import std.array;
import std.range;

struct EquivalentElements(T)
{
   T range;
   T front_;

   this(T range)
   {
       this.range = range;
       this.front_ = popEqualFront(this.range);
   }

   bool empty()
   {
       return front_.empty;
   }

   T front()
   {
       return front_;
   }

   void popFront()
   {
       this.front_ = popEqualFront(this.range);
   }

   private T popEqualFront(ref T range)
   {
       T front;

       if (!range.empty) {
           do {
               front ~= range[0];
               range = range[1..$];

           } while (!range.empty &&
                    (front[$-1] == range[0]));
       }

       return front;
   }
}

EquivalentElements!(Range) equivalentElements(Range)(Range r)
{
    return typeof(return)(r);
}

// test
void main()
{
    foreach (elem; equivalentElements([1, 1, 2, 3, 4, 4]))
    {
        writeln(elem);
    }
}
May 08, 2011
Andrej M.:

> I want to turn this:
> auto arr = [1, 1, 2, 3, 4, 4];
> 
> into this:
> auto arr2 = [[1, 1], [2], [3], [4, 4]];
> 
> I want an array of arrays of the same elements. Lazy or not, I don't care.

Currently if you use group like this:
writeln(arr.group());

You get:
[Tuple!(int,uint)(1, 2), Tuple!(int,uint)(2, 1), Tuple!(int,uint)(3, 1), Tuple!(int,uint)(4, 2)]

Andrei has recently said he wants to modify group() to make it work more like python itertools.groupby(), so it will do what you want (the duplicated items too will be lazy, as in Python).

Bye,
bearophile
May 08, 2011
Thanks, group seems to work fine too.