Abzal Serekov
Abzal Serekov

Reputation: 127

How to sort collection using complex function in Meteor (MongoDB)

I have the complex formula p / a^5 I want to sort a collection with, where p and a are attributes of the collection Items.

Is there any possible way to do it in Meteor (MongoDB)?

Upvotes: 1

Views: 675

Answers (3)

chridam
chridam

Reputation: 103455

You can use the aggregation framework for this. You can structure your aggregation pipeline so that it performs the exponential function by using the arithmetic operator $multiply, calling it n number of times where n is the exponent.

For example, the following aggregation pipeline will add an extra field r which is the result of the operation p / a^5.

For a start, in mongo shell create some sample documents in the exponents collection:

for(x=0; x < 10; x++){ db.exponents.insert({p: 5*x, a: x }); }

Generate the pipeline:

var pipeline = [
    {
        "$match" : {
            "a" : {
                "$gt" : 0
            }
        }
    },
    {
        "$project" : {
            "p" : 1,
            "a" : 1,
            "r" : {
                "$divide" : [
                    "$p",
                    {
                        "$multiply" : [
                            "$a",
                            "$a",
                            "$a",
                            "$a",
                            "$a"
                        ]
                    }
                ]
            }
        }
    },
    {
        "$sort" : {
            "r" : 1
        }
    }
];

Running the pipeline with the sample documents above :

db.exponents.aggregate(pipeline);

Output:

/* 0 */
{
    "result" : [ 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d393"),
            "p" : 45,
            "a" : 9,
            "r" : 0.0007620789513793629
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d392"),
            "p" : 40,
            "a" : 8,
            "r" : 0.001220703125
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d391"),
            "p" : 35,
            "a" : 7,
            "r" : 0.002082465639316951
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d390"),
            "p" : 30,
            "a" : 6,
            "r" : 0.003858024691358025
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d38f"),
            "p" : 25,
            "a" : 5,
            "r" : 0.008
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d38e"),
            "p" : 20,
            "a" : 4,
            "r" : 0.01953125
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d38d"),
            "p" : 15,
            "a" : 3,
            "r" : 0.06172839506172839
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d38c"),
            "p" : 10,
            "a" : 2,
            "r" : 0.3125
        }, 
        {
            "_id" : ObjectId("561d1ced3d8f561c1548d38b"),
            "p" : 5,
            "a" : 1,
            "r" : 5
        }
    ],
    "ok" : 1
}

You can set up a generic helper function for generating the pipeline code to extract $project operators with the arithmetic operators from the document representation of a power function. As aggregate isn't supported yet in Meteor, use the meteorhacks:aggregate package that adds proper aggregation support for Meteor. This package exposes .aggregate method on Mongo.Collection instances.

Add to your app with

meteor add meteorhacks:aggregate

Then simply use .aggregate() function together with the generic function to generate the pipeline to use as the .aggregate() argument below:

function power(exponent) {
    var multiply = [];
    for (var count = 0; count < exponent; count++){
        multiply.push("$a");
    }    
    return [
        { $match: { a: { $gt: 0} } },
        {
            $project: {
                p: 1, a: 1, r : { $divide: [ "$p", { $multiply: multiply } ] } 
            }
        },
        {
            "$sort": { r: 1 }
        }
    ];
}

var pipeline = power(5),
    exponents = new Mongo.Collection('exponents'),
    result = exponents.aggregate(pipeline);

Upvotes: 0

Oliver
Oliver

Reputation: 4091

No, you can't do this. I'd suggest adding another field that stores the value of p / a^5. E.g. let's say you called this field pResult, then you could just do:

collection.find(selector, {sort: {pResult: 1}});

Upvotes: 1

user3374348
user3374348

Reputation: 4101

You can just fetch() the collection, then do the sort using the regular Array.prototype.sort or _.sortBy. However, this destroys fine-grained reactivity, so if your collection is large or the elements being rendered are complex enough, and the collection changes a lot over time, then you might have performance problems.

Template.whatever.helpers({
    items: function () {
        return _.sortBy(Items.find().fetch(), function (item) {
            return item.p / Math.pow(item.a, 5);
        });
    }
});
<template name="whatever">
    {{#each items}}
        <!-- ... -->
    {{/each}}
</template>

If you need fine-grained reactivity, you could look at my package collection-view, although I don't consider it production ready.

Upvotes: 0

Related Questions