13

I'm using JavaScript. I have an array that contains data in this format:

[
        {"USER_NAME":"User1","LAST_SUCCESSFUL_CONNECT":"1373978337642"},
        {"USER_NAME":"User2","LAST_SUCCESSFUL_CONNECT":"1374515704026"},
        {"USER_NAME":"User3","LAST_SUCCESSFUL_CONNECT":"1374749782479"}
]

(the numbers above represent UTC date/time in milliseconds.

I would like to group (count) the data by month. Something like this:

[
    {"Month":"January, 2014","User_Count": 2},
    {"Month":"February, 2014","User_Count": 1},
]

I could use jQuery if it simplifies matters.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Perspectivus
  • 972
  • 1
  • 9
  • 22
  • 3
    Is this object being returned from a server? If so I would recommend returning the data in the correct format to the client, instead of adding parsing logic in java script. It's typically easier and more unit testable to run this type of logic on the server. – TGH Jul 25 '13 at 14:11
  • Yes, I'm getting the data from a server in [oData](http://www.odata.org/). This means I might not even have to change server code, but rather use oData's `$count` option (I'm not sure this would give me what I want). However, I already have the raw data in the client from a previous query, and I would like to save the round-trip. – Perspectivus Jul 26 '13 at 05:17
  • Please mark this question as answered if my answer below is indeed satisfactory. Otherwise, elaborate. – pygeek Aug 06 '13 at 17:58

4 Answers4

17

This looks like a map reduce problem. The high-level solution is as follows:

  1. Re-organize the members of the list.
  2. Count them.

Here is a step-by-step how-to for achieving this:

Map

  1. Iterate through the list of dictionaries
  2. Convert datetime string to javascript datetime object.
  3. Use month-year as key and list of dictionaries as value.

These are now grouped by month-year.

Example:

var l = [...];
var o = {};
var f = function(x){
    var dt_object = Date(x["LAST_SUCCESSFUL_CONNECT"]); // convert to datetime object
    var key = dt_object.year + '-' + dt_object.month;

    if (o[key] === undefined) {
        var o[key] = [];
    };

    o[key].push(x)
}

_.map(l, f(x)) //apply f to each member of l

Reduce

  1. Iterate through the new object containing dictionaries of lists.
  2. Calculate length of each dictionary's list.
  3. Use count as key and length of list as its value.

Example:

var g = function(member_count){
    //extra logic may go here
    return member_count
}

for member in o {
    count = _.reduce(l, g(member))
    member['count'] = count
}

Resulting API

o['month-year'] //for list of dictionaries in that month
o['month-year']['count'] //for the count of list of dictionaries in that month.

References:

For map and reduce functions in javascript see underscore.js:
http://underscorejs.org/#map
http://underscorejs.org/#reduce

Javascript Date object:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date

For more information on Date and DateTime objects:
https://en.wikipedia.org/wiki/ISO_8601

For more information on map reduce:
https://en.wikipedia.org/wiki/MapReduce

pygeek
  • 7,356
  • 1
  • 20
  • 41
  • Thanks @pygeek for your elaborate answer. I can't use open source or 3rd party sources other than jQuery, so underscorejs is out for me. I'll use jQuery's map and JavaScript's Array reduce. – Perspectivus Aug 07 '13 at 06:35
2

Use Map-reduce. Here is one example using underscore.js. It is very simple though it is a bit verbose.

var data = [{
    "USER_NAME": "User1",
        "LAST_SUCCESSFUL_CONNECT": "1373978337642"
}, {
    "USER_NAME": "User2",
        "LAST_SUCCESSFUL_CONNECT": "1374515704026"
}, {
    "USER_NAME": "User3",
        "LAST_SUCCESSFUL_CONNECT": "1374749782479"
}, {
    "USER_NAME": "User4",
        "LAST_SUCCESSFUL_CONNECT": "1274749702479"
}];

var monthNames = ["January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"];

var map_result = _.map(data, function (item) {
    var d = new Date(new Number(item.LAST_SUCCESSFUL_CONNECT));
    var month = monthNames[d.getMonth()] + ", " + d.getFullYear();
    return {
        "Month": month,
        "User_Count": 1
    };
});

var result_temp = _.reduce(map_result, function (memo, item) {
    if (memo[item.Month] === undefined) {
        memo[item.Month] = item.User_Count;
    }else{
        memo[item.Month] += item.User_Count;
    }
    return memo;
},{});

//then wrap the result to the format you expected.
var result = _.map(result_temp, function(value, key){
    return {
        "Month": key,
        "User_Count": value
    };
});

console.log(result); 
zs2020
  • 53,766
  • 29
  • 154
  • 219
-1

maybe this will be useful

LINQ for JavaScript
http://linqjs.codeplex.com/

svillamayor
  • 546
  • 3
  • 7
-1

optimized solution

var map_month= {}  
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];

for (const x of o) {
          
   GroupBy(x["LAST_SUCCESSFUL_CONNECT"])
}

const GroupBy = (elem) => {
       
       dateObj = new Date(elem) 

       monthObj = monthNames[dateObj.getMonth()] + " " + dateObj.getFullYear()
       if(map_month[monthObj]===undefined)
          map_month[monthObj+""]  = 1;
       else map_month[monthObj] += 1;

  } 
  • How it is an optimized solution, explain it and at least your code should be runnable. It has multiple errors. – DecPK May 30 '21 at 22:48