1

I have a large set of data that looks similar to this:

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];

I need to rearrange this data so that it looks more like this:

var newData = [
  { date : '2014-10-29', us : 45.3, africa : 60.5, south_america : 0 },
  { date : '2014-10-30', us : 30, africa : 0, south_america : 10 }
]

I'm new to dealing with datasets like this and I'm struggling to find any efficient way to handle this... The only thing I can come up with uses multiple for loops and just looks gross. Does anyone have any ideas or suggestions?

4
  • See the properties flip. Commented Aug 3, 2015 at 0:40
  • Where is the data coming from? It might be easier to change it on the server side. Commented Aug 3, 2015 at 0:40
  • is the output array 1 record per date? Commented Aug 3, 2015 at 0:50
  • Yes @JaromandaX. There should only be one record per date in the new data. Commented Aug 3, 2015 at 0:53

4 Answers 4

2

First thought is to do this

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];
var t = {};
var result = [];
original.forEach(function(entry) {
    var n = t[entry.date] = t[entry.date] || {};
    n.date = entry.date;
    n[entry.country] = entry.cost;
});
for(var k in t) {
    if (t.hasOwnProperty(k)) {
        result.push(t[k]);
    }
}
console.log(result);

there's probably a better way without the temporary object, but you want efficient, I think this is the most efficient way I can think of

Sign up to request clarification or add additional context in comments.

Comments

1

Unfortunately gross looking multiple loops is the way to solve this. You can make your self feel better about horrible code by putting it in a function then hiding that function away at the bottom of your code.

Jaromanda's answer is more efficient it terms of both memory and time, but if you really have a large quantity of data you should think about reorganising it on the server because the output data is smaller than the original it will be less the transfer. Although you might like the idea of farming out the work load to the client.

If you still think reorganising the data client side is the way to go you might like this function which can easily be used if you change the property names.

function groupAndMerge(original,groupBy,merge){

    var grouped={},list=[];

    original.forEach(function(o){
        var k=o[groupBy];
        if(!grouped[k]) grouped[ k ]=[];
        grouped[k].push(o);
    });

    for(var i in grouped){
        var d={};
        list.push(d);
        grouped[i].forEach(function(o){
            d[groupBy]=i;
            for(var k in merge)     d[o[k]]=o[merge[k]];
        });

    }

    return list;

}

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];

var result=groupAndMerge(original,'date',{'country':'cost'});

2 Comments

Great explanation @Bob. I'll probably use a combination of your answer and Jaramanda's answer. Thanks :)
And I kinda want to farm it out to the client, because I'll need the more 'robust' original dataset for a seperate table already. So it will save me a request.
1

If the data is already sorted, there is a faster way using only one loop:

var output = document.querySelector('#output');

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];

function mapData(data) {
  var newData = [];  
  var newMap = {};
    newMap.date = data[0].date;
  
    for (var i = 0; i < data.length; i++) {
        if (data[i].date === newMap.date) {
            newMap[data[i].country] = data[i].cost;
        }
        else {
            newData.push(newMap);
            newMap = { date: data[i].date, [data[i].country]: data[i].cost };
        }
    }

    newData.push(newMap);
    return newData;
}

output.innerHTML = JSON.stringify(mapData(original));
<html>
    <body>
        <div id="output"></div>
    </body>
</html>

This is faster than Jaromanda's solution. However, again, it depends on the data already in a sorted order. If the data must be sorted first, it quickly falls behind: jsPerf .

Both of these solutions, however, ignore the fact that the OP wants every country to have an entry for each date, even if the cost doesn't exist (is zero) for the specific date. This requirement will also mean that multiple loops will have to be used and will hurt performance.

Comments

0

You can limit the creation of intermediary collections by using a dictionary to store a pointer to the index of the entry associated with a given date. Example with a reduce function (getting away without any for loop).

var original = [
    { country : 'us', date : '2014-10-29', cost : 45.3 },
    { country : 'africa', date : '2014-10-29', cost : 60.5 },
    { country : 'south_america', date : '2014-10-30', cost : 10 },
    { country : 'us', date : '2014-10-30', cost : 30 }
];


var newData = original.reduce(function(acc, d) {
  var date = d.date;
  var idx = acc.dict.indexOf(date);
  var entry;
  if (idx === -1) {
    entry = { date: date };
    acc.dict.push(date);
    acc.list.push(entry);
  } else {
    entry = acc.list[idx];
  }
  entry[d.country] = d.cost;
  return acc
}, {
  list: [],
  dict: []
}).list

document.querySelector('#result').innerHTML = JSON.stringify(newData)
<div id='result'></div>

You can make the code more reusable, but that will create a slight performance cost.

var original = [
    { country : 'us', date : '2014-10-29', cost : 45.3 },
    { country : 'africa', date : '2014-10-29', cost : 60.5 },
    { country : 'south_america', date : '2014-10-30', cost : 10 },
    { country : 'us', date : '2014-10-30', cost : 30 }
];

var groupByDateAndAggregateCostByCountry = {
getGroup : function(d) { return {k: 'date', v: d.date}; },
assignData: function(d) { return  {[d.country]:  d.cost}; }
}

var newData = original.reduce(function(acc, d) { 
var gp  = acc.fn.getGroup(d);
var idx = acc.dict.indexOf(gp.v); 
var entry;
if(idx === -1) { 
	entry = {[gp.k] : gp.v};
	acc.dict.push(gp.v); 
	acc.list.push(entry);
} else {
	entry = acc.list[idx];
}
Object.assign(entry, acc.fn.assignData(d));
return acc;  
}, {list: [], dict: [], fn: groupByDateAndAggregateCostByCountry}).list;

newData.map(function(d) {console.log(d)});

document.querySelector("#result2").innerHTML = JSON.stringify(newData);
<div id="result2"></div>

Comments

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.