DrakaSAN
DrakaSAN

Reputation: 7853

Count how many and which index of a array

I have an array of objects:

 result = [
 { _id: 53d0dfe3c42047c81386df9d, video_id: '1' },
 { _id: 53d0dfe3c42047c81386df9e, video_id: '1' },
 { _id: 53d0dfe3c42047c81386df9f, video_id: '1' },
 { _id: 53d0dfe3c42047c81386dfa0, video_id: '2' },
 { _id: 53d0dfe3c42047c81386dfa1, video_id: '2' },
 { _id: 53d0dfe3c42047c81386dfa2, video_id: '1' },
 { _id: 53d0dfe3c42047c81386dfa3, video_id: '2' },
 { _id: 53d0dfe3c42047c81386dfa4, video_id: '1' } 
 ]

I need to create another array, which takes video_id as the index, and contains how many times this video_id appears in the first array:

list = [
{'1' : 5},
{'2' : 4}
]

Currently, I use this code:

while (i < result.length)
{
    if(list[result[i].video_id] === undefined) {
        list[result[i].video_id] = 0;
    }
    list[result[i].video_id] = list[result[i].video_id] + 1;
    i = i + 1;
}

It works, but I wonder if there is any faster and cleaner way to do so? (the real result array has over 10k elements, and I doubt >10k conditional statements are optimal...).

I am using node.js, result is from a mongoose (mongoDB) query, and I didn't see any way to get this done by mongoose itself:

var now = new Date();
//M_logs is a mongoose model
query = M_logs.where('time').gt(new Date(now.getFullYear(), 0, 1).getTime() / 1000).lt(now.getTime() / 1000).select('video_id');

(PS: I wonder if this isn't more a Code Review question, please tell me if I am off-topic so I can migrate the question).

EDIT:

To answer to Juan Carlos Farah:

S_logs =  new mongoose.Schema({

    user_ip : String,
    user_id : String,
    user_agent : String,
    canal_id :  String,
    theme_id :  String,
    video_id :  String,
    osef : String,
    time : Number,
    action: String,
    is_newuser : String,
    operator : String,
    template : String,
    catalogue : String,
    referer : String,
    from : String,
    osef1 : String

});

M_logs = mongoose.model('logs', S_logs);

Upvotes: 1

Views: 116

Answers (3)

Juan Carlos Farah
Juan Carlos Farah

Reputation: 3879

You can do this using the aggregation framework. The idea is to do something as follows:

  1. Match the documents you are looking for. Based on your current query, I understand it would be documents where time is between new Date(now.getFullYear(), 0, 1).getTime() / 1000 and now.getTime() / 1000.
  2. Group the matched documents by video_id and keep track of their count.
  3. Optionally sort by _id, which would be equivalent to the original video_id.

The following is in mongo shell syntax:

var now = new Date();

db.M_logs.aggregate([
    {
        "$match" : {
            "time" : { 
                "$gt" : new Date(now.getFullYear(), 0, 1).getTime() / 1000,
                "$lt" : now.getTime() / 1000
            }
        }    
    },
    { 
        "$group" : {
            "_id" : "$video_id",
            "count" : { "$sum" : 1 }
        }
    },
    { 
        "$sort" : { "_id" : 1 }
    }
]);

If this works for you, you can easily implement it in Mongoose or Node.js driver syntax. Note that the aggregation framework returns a cursor, which you can iterate through to populate your array.

EDIT:

Using the Node.js driver, you can access the results from the aggregation query in the callback function. Something as follows:

...
, function(err, result) {
    console.dir(result);
    db.close();                          
}

Note that the Mongoose syntax for aggregation queries is slightly different.

Example:

Model.aggregate([ <QUERY> ]).exec( <CALLBACK> );

For more information, consult the documentation here.

Upvotes: 2

Christian P
Christian P

Reputation: 12240

I would suggest that you use aggregation framework to count number of documents. It will be significantly faster than iterating all your documents and counting them.

Using mongoose you can do it like this:

var now = new Date();
var startTime = new Date(now.getFullYear(), 0, 1).getTime() / 1000):
var endTime = now.getTime() / 1000;

M_logs.aggregate([
    // filter the documents you're looking for
    {"$match" : { "time" : {"$gt": startTime, "$lt": endTime}}},
    // group by to get the count for each video_id
    {"$group" : {"_id" : "$video_id", "count" : {"$sum" : 1}}},
    // make the output more explanatory; this part is optional
    {"$project" : { "video_id" : "$_id", "count" : "$count", _id : 0}}
]).exec(function(err, docs){
    if (err) console.err(err);
    console.log(docs);
});

The output of the docs will be:

[ { count: 4, video_id: '2' }, { count: 5, video_id: '1' } ]

Upvotes: 1

Ferdi265
Ferdi265

Reputation: 2969

use

var list = {};
result.forEach(function (el) {
    list[el.video_id] = (list[el.video_id] || 0) + 1;
});

the resuling list will look something like this:

var list = {
    '1': 5,
    '2': 4
};

Upvotes: 0

Related Questions