user3404276
user3404276

Reputation: 11

How to use couchdb reduce for a map that has multiple elements for values

I can't seem to find an answer for this anywhere, so maybe this is not allowed but I can't find any couchdb info that confirms this. Here is a scenario:

Suppose for a map function, within Futon, I'm emitting a value for a key, ex. K(1). This value is comprised of two separate floating point numbers A(1) and B(1) for key K(1). I would like to have a reduction perform the sample average of the ratio A(N)/B(N) over all K(N) from 1 to N. The issue I'm always running into in the reduce function is for the "values" parameter. Each key is associated with a value pair of (A,B), but I can't break out the A, B floating numbers from "values".

I can't seem to find any examples on how to do this. I've already tried accessing multi-level javascript arrays for "values" but it doesn't work, below is my map function.

function(doc) {
  if(doc['Reqt.ID']) {
    doc['Reqt.ID'].forEach(function(reqt) {
      row_index=doc['Reqt.ID'].indexOf(reqt);
      if(doc.Resource[row_index]=="Joe Smith")
          emit({rid:reqt},{acthrs:doc['Spent.Hours']  [row_index],esthrs:doc['Estimate.Total.Hours'][row_index]});
      });
  }  
}

I can get this to work (i.e. avg ratio) if I just produce a map that emits a single element value of A/B within the map function, but I'm curious about this case of multiple value elements.

How is this generally done within the Futon reduce function?

I've already tried various JSON Javascript notations such as values[key index].esthrs[0] within a for loop of the keys, but none of my combinations work.

Thank you so much.

Upvotes: 1

Views: 1366

Answers (1)

SingleNegationElimination
SingleNegationElimination

Reputation: 156288

There are two ways you could approach this; first, my reccomendation, is to change your map function to make it more of a "keys are keys and values are values", which in your particular case probably means, since you have two "values" you'd like to work with, Spent.Hours and Estimate.Total.Hours, you'll need two views; although you can cheat a little, but issuing multiple emit()'s per row, in the same view, for example:

emit(["Spent.Hours", reqt], doc['Spent.Hours'][row_index]);
emit(["Estimate.Total.Hours", reqt], doc['Estimate.Total.Hours'][row_index]);

with that approach, you can just use the predefined _stats reduce function.

alternatively, you can define a "smart" stats function, which can do the statistics for more elaborate documents.

The standard _stats function provides count, sum, average and standard deviation. the algorithm it uses is to take the sum of the value, the sum of the value squared, and the count of values; from just these, average and standard deviation can be calculated (and is embedded, for convenience in the reduced view)

roughly, that might look like:

function(key, values, rereduce) {
    function getstats(seq, getter) {
        var c, s, s2 = 0, 0, 0;
        values.forEach(function (row) {
            var value = getter(row);
            if (rereduce) {
                c += value.count;
                s += value.sum;
                s2 += value.sumsq;
            } else {
                c += 1;
                s += value;
                s2 += value * value;
            }
        return {
            count: c,
            sum: s,
            sumsq: s2,
            average: s / c,
            stddev: Math.sqrt(c * s2 - s1) / c
        };
    }
    return {esthrs: getstats(function(x){return x.esthrs}),
            acthrs: getstats(function(x){return x.acthrs})};
}

Upvotes: 2

Related Questions