Thread overview
sum across an array of objects
Jul 28, 2012
Philip Daniels
Jul 28, 2012
Jonathan M Davis
Jul 28, 2012
Philip Daniels
Jul 28, 2012
bearophile
Jul 28, 2012
Philip Daniels
Jul 28, 2012
Ali Çehreli
Jul 28, 2012
Ali Çehreli
Jul 28, 2012
Philip Daniels
July 28, 2012
I have an array of objects that have a size() property. Code to sum them manually works fine, as expected:

  auto total = 0;
  foreach (wt; _word_tables)
    total += wt.size();
  return total;


How can I do this with reduce? (BTW I second the old comments and bugs about not having sum() built into the library, it's annoying.)

I tried

  auto total = reduce!(
    (int a, WordTable b) { return a + b.size(); })
    (0, _word_tables);

but it seems that reduce expects a and b to be of type WordTable, so it won't instantiate the template. It's also as long as the manual version :-(

I really want to do

  auto total = sum(a => a.size(), _word_tables)

if that's possible.



July 28, 2012
On Saturday, July 28, 2012 09:58:32 Philip Daniels wrote:
> I have an array of objects that have a size() property. Code to sum them manually works fine, as expected:
> 
>    auto total = 0;
>    foreach (wt; _word_tables)
>      total += wt.size();
>    return total;
> 
> 
> How can I do this with reduce? (BTW I second the old comments and
> bugs about not having sum() built into the library, it's
> annoying.)
> 
> I tried
> 
>    auto total = reduce!(
>      (int a, WordTable b) { return a + b.size(); })
>      (0, _word_tables);
> 
> but it seems that reduce expects a and b to be of type WordTable, so it won't instantiate the template. It's also as long as the manual version :-(
> 
> I really want to do
> 
>    auto total = sum(a => a.size(), _word_tables)
> 
> if that's possible.

How about

import std.algorithm;
import std.array;
import std.stdio;

void main()
{
    auto stuff = ["hello world",
                  "Goodbye, Shirley",
                  "Babylon 5 rocks"];

    auto result = reduce!"a + b.length"(0L, stuff);
    assert(result == join(stuff).length);
}

The first parameter is the sum, and the second is the item. So, I expect that

auto total = reduce!((a, b) => a + b.size())(0, _word_tables);

would work for you.


- Jonathan M Davis
July 28, 2012
On Saturday, 28 July 2012 at 08:32:57 UTC, Jonathan M Davis wrote:
> void main()
> {
>     auto stuff = ["hello world",
>                   "Goodbye, Shirley",
>                   "Babylon 5 rocks"];
>
>     auto result = reduce!"a + b.length"(0L, stuff);
>     assert(result == join(stuff).length);
> }
>
> The first parameter is the sum, and the second is the item. So, I expect that
>
> auto total = reduce!((a, b) => a + b.size())(0, _word_tables);
>
> would work for you.
>
>
> - Jonathan M Davis

Jonathan, I tried the above suggestion, and also this equivalent based on your code:

  auto total = reduce!"a + b.size()"(0, _word_tables);

in both cases I get


/usr/include/i386-linux-gnu/dmd/phobos/std/conv.d(3330): Error: cannot implicitly convert expression (_param_1) of type WordDatabase.WordTable to int
/usr/include/i386-linux-gnu/dmd/phobos/std/algorithm.d(743): Error: template instance std.conv.emplace!(int,WordTable) error instantiating
WordDatabase.d(150):        instantiated from here: reduce!(int,WordTable[30u])
WordDatabase.d(150): Error: template instance std.algorithm.reduce!("a + b.size()").reduce!(int,WordTable[30u]) error instantiating


July 28, 2012
Philip Daniels:

> cannot implicitly convert expression (_param_1) of type WordDatabase.WordTable to int

That error tells you to not use an int as first value for the reduce. Maybe this is enough to solve your problem.

Bye,
bearophile
July 28, 2012
On Saturday, 28 July 2012 at 10:58:45 UTC, bearophile wrote:
> Philip Daniels:
>
>> cannot implicitly convert expression (_param_1) of type WordDatabase.WordTable to int
>
> That error tells you to not use an int as first value for the reduce. Maybe this is enough to solve your problem.
>
> Bye,
> bearophile

Well...sort of, but I am not sure why. I was able to get to the right syntax by first doing it in parts, using map! and reduce! on separate lines, which then gave me a pointer to the original problem. It seems to be something to do with whether the array is static or dynamic. If the array is static, which mine was, then this form will work - note the extra [] on _word_tables:

  auto total = reduce!"a + b.size()"(0L, _word_tables[]);

But if the array is dynamic then both of these forms work

  auto total = reduce!"a + b.size()"(0L, _word_tables);
  auto total = reduce!"a + b.size()"(0L, _word_tables[]);

That seems counter-intuitive to me at the moment, coming brand new to the language. I know that the [] meaans "take a slice of the array" but I don't see why that should be considered different, since it is the entire array.

July 28, 2012
On 07/28/2012 06:49 AM, Philip Daniels wrote:

> It seems to
> be something to do with whether the array is static or dynamic.

Good catch! :) Welcome to D where static arrays (aka "fixed-length arrays") and dynamic arrays (aka "slices") are different beasts.

A number of factors are at play here:

1) Static arrays are value types. As a result, the entire array gets copied when pass to a function (e.g. reduce) as an argument.

2) The length of static arrays cannot change.

3) Phobos is a range-based library. reduce() is an algorithm that not more than an InputRange.[*] InputRanges are naturally consumed as they are being iterated over.

For the reasons above, a static array is not usable by reduce(). Your solution of taking a slice of all of the elements first by _word_tables[] is the correct solution (and very cheap, thanks to D's slices).

Ali

[*] Arguably, reduce() could have a specialization that worked on RandomAccessRanges (static arrays included), making life easier. I don't see why that could not be added to Phobos.

July 28, 2012
On 07/28/2012 07:36 AM, Ali Çehreli wrote:

> reduce() is an algorithm that not more than an InputRange.

That should be:

reduce() is an algorithm that _does not require_ more than an InputRange.

(That is also misleading: reduce() requires an "iterable object", which can be any type of a range, as well as an object of a type that defines the opApply() member function.)

Ali

July 28, 2012
On Saturday, 28 July 2012 at 14:36:26 UTC, Ali Çehreli wrote:
> On 07/28/2012 06:49 AM, Philip Daniels wrote:
>
> > It seems to
> > be something to do with whether the array is static or
> dynamic.
>
> Good catch! :) Welcome to D where static arrays (aka "fixed-length arrays") and dynamic arrays (aka "slices") are different beasts.
>
> A number of factors are at play here:
>
> 1) Static arrays are value types. As a result, the entire array gets copied when pass to a function (e.g. reduce) as an argument.
>
> 2) The length of static arrays cannot change.
>
> 3) Phobos is a range-based library. reduce() is an algorithm that not more than an InputRange.[*] InputRanges are naturally consumed as they are being iterated over.
>
> For the reasons above, a static array is not usable by reduce(). Your solution of taking a slice of all of the elements first by _word_tables[] is the correct solution (and very cheap, thanks to D's slices).
>
> Ali
>
> [*] Arguably, reduce() could have a specialization that worked on RandomAccessRanges (static arrays included), making life easier. I don't see why that could not be added to Phobos.

Thanks for the explanation Ali. I was planning on converting the array to dynamic anyway, since the ultimate size is dependent upon the program's input, so I think I'll do that now.