How to best translate this C++ algorithm into D?

monarch_dodra via Digitalmars-d digitalmars-d at puremagic.com
Sat Jun 7 08:39:52 PDT 2014


On Saturday, 7 June 2014 at 13:31:21 UTC, logicchains wrote:
> On Saturday, 7 June 2014 at 13:25:07 UTC, Timon Gehr wrote:
>> What about (untested)?:
>>
>> static forest_t[] possible_meals = [{-1, -1, +1}, {-1, +1, 
>> -1}, {+1, -1, -1}];
>> return forests.map!(a=>possible_meals.map!(b=>b+a))
>>    .join.partition!forest_invalid.sort.uniq.array;
>>
>> (Of course, this still allocates two arrays.)
>
> That seems around 5% slower, for some reason.

Testing with dmd head, with input "300 300 300" (for what it's 
worth).

I was able to identify two "choke points". The first, is building 
the initial forest. A straight up allocation + loop gave me about 
a 25% improvement (10s => 8S)

     forest_t[] arr = 
uninitializedArray!(forest_t[])(forests.length * 3);
     size_t j = size_t.max;
     foreach ( ref const a; forests )
     {
         arr[++j] = forest_t(-1, -1, +1) + a;
         arr[++j] = forest_t(-1, +1, -1) + a;
         arr[++j] = forest_t(+1, -1, -1) + a;
     }

Then, you are using the code:
.partition!(forest_invalid).sort.uniq.array;

The "advantage" of "partition" over "filter" is that "partition" 
is a greedy inplace algorithm. But then, you go on to use "uniq", 
which is lazy, and requires "array". I implemented a (very) 
simplistic in-place "removeDuplicates":

T[] removeDuplicates(T)(T[] r)
{
     if (r.empty) return r;
     size_t i = 0, j = 1;
     for ( ; j < r.length ; ++j)
     {
        if (r[i] != r[j])
          r[++i] = r[j];
     }
     return r[0 .. ++i];
}

=> .partition!(forest_invalid).sort.removeDuplicates;

This gave me a some speed up (but nothing huge). Tweaking 
removeDuplicates to detect "runs at once" could prove to squeeze 
out more juice but ...

... instead of keeping "true" to your code, there is a faster way 
than "sort" to remove duplicates. Hashing. The code becomes:

     bool[forest_t] hash;
     foreach (tree ; arr.partition!forest_invalid)
         hash[tree] = true;
     return hash.keys;

This give me about a 90% speedup (8s => 4.5s). Note though that 
this is not in-place at all. Also, I don't know if having the 
ouput actually sorted was important for you, or if it was only an 
implementation detail. In any case, if you want sorted, then you 
can:
return hash.keys.sort();
But at that point, there has to be *some* duplication to justify 
using the hash table.

The downside to all of this, is that the solution is now 
not-so-functional:

   forest_t[] meal(forest_t[] forests) {
     forest_t[] arr = 
uninitializedArray!(forest_t[])(forests.length * 3);
     size_t j = size_t.max;
     foreach ( ref const a; forests )
     {
         arr[++j] = forest_t(-1, -1, +1) + a;
         arr[++j] = forest_t(-1, +1, -1) + a;
         arr[++j] = forest_t(+1, -1, -1) + a;
     }
     bool[forest_t] hash;
     foreach (tree ; arr.partition!forest_invalid)
         hash[tree] = true;
     return hash.keys;
   }


More information about the Digitalmars-d mailing list