5
\$\begingroup\$

I have a CouchDB view with the following view/reduce function. After working over 2 hours on it, I have a result I am happy with, but I believe there is a lot of room for improvement. Basically, I would like the code a bit more slim.

map: function (doc) {
    if (doc.type == 'ticket') {
        emit([doc._id, doc.applications, doc.clients, doc.release, doc.departments], doc)
    } else if (doc.type == 'application') {
        emit([doc._id, 0], doc)
    } else if (doc.type == 'client') {
        emit([doc._id, 1], doc)
    } else if (doc.type == 'release') {
        emit([doc._id, 2], doc)
    } else if (doc.type == 'department') {
        emit([doc._id, 3], doc)
    }
}, 
reduce: function (keys, values, rereduce) {
    var result = null
    var applications = []
    var clients = []
    var release
    var departments = []
    for (var i = 0; i < values.length; i++) {
        if (values[i]) {
            if (values[i].type == 'ticket') {
                result = values[i]
            } else if (values[i].type == 'application') {
                applications.push(values[i])
            } else if (values[i].type == 'client') {
                clients.push(values[i])
            } else if (values[i].type == 'release') {
                release = values[i]
            } else if (values[i].type == 'department') {
                departments.push(values[i])
            }
        }
    }
    if (result != null) {
        var apps = []
        var cls = []
        var deps = []
        if (result.applications) {
            for (var i = 0; i < applications.length; i++) {
                if (result.applications.indexOf(applications[i]._id) > -1) {
                    apps.push(applications[i])
                }
            }
        }
        if (result.clients) {
            for (var i = 0; i < clients.length; i++) {
                if (result.clients.indexOf(clients[i]._id) > -1) {
                    cls.push(clients[i])
                }
            }
        }
        if (result.departments) {
            for (var i = 0; i < departments.length; i++) {
                if (result.departments.indexOf(departments[i]._id) > -1) {
                    deps.push(departments[i])
                }
            }
        }
        result.applications = apps
        result.clients = cls
        result.departments = deps
        result.release = release
        return result
    }
}
}

To answer the question, what my code is doing, I have documents in my database, which contain ticket data. These look like this:

{
   "_id": "12345",
   "_rev": "33-bb1ad1ec8429b638f1cb8ec2c592c8f6",
   "priority": "200",
   "description": "Ja, ist was",
   "deadline": "",
   "minutesperweek": "125",
   "ordervolume": "125",
   "impactdescription": "Irgendwas geht nicht mehr",
   "applications": [
       "6817eeda32184183e792011df10013e6"
   ],
   "departments": [
       "966f9e33d4d0233d967e0e3d3a000a90"
   ],
   "type": "ticket",
   "assignee": {
       "_id": "theo",
       "firstname": "Theo",
       "lastname": "Test",
       "emailaddress": "[email protected]",
       "phonenumber": "123456"
   },
   "assigneehistory": [
       {
           "_id": "theo",
           "firstname": "Theo",
           "lastname": "Test",
           "emailaddress": "[email protected]",
           "phonenumber": "123456"
       }
   ],
   "comments": [
       {
           "commentvalue": "asd",
           "creator": {
               "_id": "theo",
               "firstname": "Theo",
               "lastname": "Test",
               "emailaddress": "[email protected]",
               "phonenumber": "123456"
           },
           "created": "2014-11-27T15:03:00.342Z"
       }
   ],
   "clients": [
       "1152b87a2c7650ba9f8a7992b10009a1",
       "1152b87a2c7650ba9f8a7992b1000c4e"
   ],
   "reviewcomment": [
       {
           "commentvalue": "fdg",
           "creator": {
               "firstname": "Theo",
               "_id": "test",
               "lastname": "Test",
               "emailaddress": "[email protected]",
               "phonenumber": "123456"
           },
           "created": "2014-12-02T13:50:47.218Z"
       }
   ],
   "reviewed": true
}

With the posted reduce function I would like to replace the arrays of clients, applications and departments with the items from the database. I think that the reduce is not very efficient.

\$\endgroup\$
2
  • \$\begingroup\$ I am not sure what this does, can you put a blurb in your question? \$\endgroup\$ Commented Dec 4, 2014 at 20:02
  • 1
    \$\begingroup\$ Sure, just give me a bit time :) \$\endgroup\$ Commented Dec 5, 2014 at 9:55

2 Answers 2

1
\$\begingroup\$

The mapping function can be a lot simpler, i.e.:

map: function (doc) {
    if (doc.type == 'ticket') {
        emit([doc._id, doc.applications, doc.clients, doc.release, doc.departments], doc);
        return;
    }

    var types = ['application', 'client', 'release', 'department'];
    var index = types.indexOf(doc.type);
    if (index != -1)
        emit([doc._id, index], doc);
},

The return isn't strictly necessary, but it's better to exit early anyway.

Now for the reducing function you can again make use of a bit of indirection to reduce the number of lines. I'd also recommend lodash or any other functional library to get rid of the loops, but I guess that is a matter of taste.

I've also always remove as many layers of nesting as possible to keep the code readable, so by using return, continue, or break at the earliest convenience.

So kind of like the following:

reduce: function (keys, values, rereduce) {
    var result = null;
    var release;

    var sequences = {
        application: [],
        client: [],
        department: []
    };

    for (var i = 0; i < values.length; i++) {
        var value = values[i];

        if (!value)
            continue;

        var type = value.type;

        if (type == 'ticket')
            result = value;
        else if (type == 'release')
            release = value;
        else {
            var types = ['application', 'client', 'department'];
            var index = types.indexOf(doc.type);
            if (index != -1)
                sequences[type].push(values[i]);
        }
    }

    if (!result)
        return;

    var apps = [];
    var cls = [];
    var deps = [];

    ...
}
\$\endgroup\$
3
  • \$\begingroup\$ I have a question to the part after the if (!result) what should go there? I am a little unclear :/ Btw. the map and reduce functions are great :) \$\endgroup\$ Commented Dec 5, 2014 at 14:12
  • \$\begingroup\$ Oh, just what you already had in the result block, didn't want to repeat that part. \$\endgroup\$ Commented Dec 5, 2014 at 14:12
  • \$\begingroup\$ I think I am to silly to use it properly, I get null for key and value in cradle... \$\endgroup\$ Commented Dec 5, 2014 at 14:29
1
\$\begingroup\$

Interesting question,

first off, you have a few jshint problems:

  • Finish your lines with semicolons
  • Declare i only once in your function
  • Avoid null, count on undefined
  • You are not using keys
  • You are not using rereduce

Beyond this,

  • You could generalize the pushing off applications, clients, releases, and departments.
  • Instead of using an array with objects with an _id, why not collect the _id values instead in a lookup object, this would be much faster (reduced lookup time) and use less memory.
  • You could generalize the lookups off applications, clients, releases, and departments in the 2nd part of the code.

My counter proposal looks like this:

function replaceKeysWithValues(keys, values) {
    for(var i = 0; i < keys.length; i++) {
        keys[i] = values[keys[i]];
    }
}

function reduce(keys, values, rereduce) {
    var lookups = {
            application: {},
            client: {},
            department: {},
            release: {},
        },
        lookupKeys = Object.keys(lookups),
        result, i;
    for(i = 0; i < values.length; i++) {
        values[i] = values[i] || {};
        if(values[i].type == 'ticket') {
            result = values[i];
        } else if(lookupKeys.indexOf(values[i].type) != -1) {
            lookups[values[i].type][values[i]._id] = values[i];
        }
    }
    if(!result) {
        return;
    }
    if(result.applications) {
        replaceKeysWithValues(result.applications, lookups.application);
    }
    if(result.clients) {
        replaceKeysWithValues(result.clients, lookups.client);
    }
    if(result.departments) {
        replaceKeysWithValues(result.departments, lookups.department);
    }
    if(result.releases) {
        replaceKeysWithValues(result.releases, lookups.release);
    }
    return result;
}
\$\endgroup\$
8
  • \$\begingroup\$ Thanks :) keys and rereduce I have to have. If I use it or not ;) The part with i I don't get why and the same goes for the semicolon, I mean JavaScript offers it and I like it :) With undefined instead of null I come from C# there you have no undefined thanks for that hint :) \$\endgroup\$ Commented Dec 5, 2014 at 14:24
  • \$\begingroup\$ Ah, you can write FORTRAN in any language, but if you write JavaScript, you should use semicolons ;) As for using several times var i, that does not do what you think it does and it's a waste of characters. Meaning var i = 3;var i;i; will evaluate to 3. \$\endgroup\$ Commented Dec 5, 2014 at 14:28
  • \$\begingroup\$ I think the part with semicolons, would go to far :D Because I like it way more without ;) With i I understood you wrong ^^ My mistake :) To your code, I could get it run, but it returns null for applications, clients and departments \$\endgroup\$ Commented Dec 5, 2014 at 14:31
  • 1
    \$\begingroup\$ Gah, I dont have a full test case, but I think I fixed it with lookups[values[i].type][values[i]._id] = values[i]; Still, do you understand what I tried to do here? \$\endgroup\$ Commented Dec 5, 2014 at 14:35
  • \$\begingroup\$ Yeah I do and I like your approach :) But it seems not to fix it. I gonna go behind it later today. I also checked the part with semicolons again and found that: jshint.com/docs/options/#asi \$\endgroup\$ Commented Dec 5, 2014 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.