2
\$\begingroup\$

I have an array of objects where each object has a datetime key/value pair:

var original = [
  {
    datetime: '2015-07-22 09:00:00'
  },
  {
    datetime: '2015-07-22 11:00:00'
  },
  {
    datetime: '2015-07-23 10:00:00'
  }
]

I need to transform them into a new array with objects where the first key/value pair of each object is the date associated with an array of datetimes from the original array of objects. The second key/value pair is an array of the original objects which have a datetime date that matches the new date.

var new = [
  {
    date: '2015-07-22',
    events: [
      {
        datetime: '2015-07-22 09:00:00'
      },
      {
        datetime: '2015-07-22 11:00:00'
      }
    ]
  },
  {
    date: '2015-07-23',
    events: [
      {
        datetime: '2015-07-23 10:00:00'
      }
    ]
  }
];

Below is my current method of making this transformation happen and it is this method that I am seeking to improve upon. It is clunky. It is convoluted. It is hacky.

I am currently selecting the substring from the first object's datetime as currentDate which only contains the date. Then I'm creating an empty array, new. as well as an object as datum which contains a date key with currentDate as it's value and an events key with an empty array as its value.

var currentDate = original[0].datetime.substring(0,10); // => '2015-07-22';
var new = [];
var datum = {
  date: currentDate,
  events: []
};

Then I'm using a forEach function on the original array in the following manner:

original.forEach(function(item){

  //if item's date matches currentDate add it to datum object
  if (item.datetime.substr(0,10) === currentDate){
    datum.events.push(item);
  }

  //if item's date does not match then it is the next date 
  else {

    //add the previous day's datum object to the new array 
    new.push(datum);

    //set new currentDate and set new datum object
    currentDate = item.datetime.substr(0,10);
    datum = {
      date: currentDate,
      events: []
    };

    //push the current item into the new datum array
    datum.events.push(item);
  }
});

//push final day's datum to new array
new.push(datum);

What is a more efficient way to transform this data?

\$\endgroup\$
1
\$\begingroup\$

The general idea of your attempt is not so bad.

Its main default is to be not generic enough so you repeat some code. This can be simply avoided using this (already antique) rule: never prepare a container before you know you need it.
So here, instead of immediately creating a first date item, wait for the time you see that it's required.

A fortunate side effect is that it responds to another lack in your code: if original contains a sequence of unsorted events, it will fail. With the new method, all events will be regularly processed.
(look at this fiddle, where I intentionally interweaved dates)

Modified code looks like this:

var original = [
  {
    datetime: '2015-07-22 09:00:00'
  },
  {
    datetime: '2015-07-22 11:00:00'
  },
  {
    datetime: '2015-07-23 10:00:00'
  }
];
var result = [], // (don't like "var new", which is a reserved word!)
    index = [];

for (var i in original) {
  var date = original[i].datetime,
      day = date.substr(0,10),
      j = index.indexOf(day);
  if (j == -1) {
    // first instance for the current day, create it
    result.push({
      date: day,
      event: [],
    });
    // register its index
    index.push(day);
    // get this new index value
    j = index.length - 1;
  }
  // in any case, populate the right day with the current event
  result[j].event.push(date);
}
\$\endgroup\$
  • \$\begingroup\$ Awesome answer! This is extremely generic and does exactly what I'd like! \$\endgroup\$ – Peter Smith Jul 23 '15 at 18:01
0
\$\begingroup\$
   var temp = original.map(function (obj) {
        return original.map(function (o) { return o.datetime.substring(0, 10) }).indexOf(obj.datetime.substring(0, 10));
    }).filter(function (item, pos, arr) {
        return arr.indexOf(item) == pos;
    })

    var modified = [];

    for (var i in temp) {
        var events = original.filter(function (o) {
            return o.datetime.substring(0, 10) == original[temp[i]].datetime.substring(0, 10)
        })

        modified.push({ date: original[temp[i]].datetime.substring(0, 10), events: events });
    }

Just a different way to get the result without if/else

\$\endgroup\$
  • 1
    \$\begingroup\$ Could you elaborate more on how you've improved the code? Code-dumps usually aren't very helpful. \$\endgroup\$ – Ethan Bierlein Jul 23 '15 at 16:32
0
\$\begingroup\$

Avoid using language keywords as variable names

Then I'm creating an empty array, new.

I recommend naming your array something other than new since new is also the operator for creating an instance of an object type. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new)

Consider a Different Data Structure

Since each object in your output has a unique identifier (the date) you might be better off with a different data structure; instead of an array, you can store the data in an object that uses date strings as property hash keys.

var datesAndTimes = {};
var length = original.length;
while (length--) {
  // get the date substring to use as a key value
  var currentDate = original[length].datetime.substring(0, 10);
  if (!datesAndTimes[currentDate]) {
    // create an empty array at that key value if one doesn't exist
    datesAndTimes[currentDate] = [];
  }
  // push the datetime to the array corresponding to our key
  datesAndTimes[currentDate].push(original[length].datetime);
}

This would leave you with an object that looks like this:

{
 "2015-07-23":
    ["2015-07-23 10:00:00"],
 "2015-07-22":
    ["2015-07-22 11:00:00","2015-07-22 09:00:00"]
}

That object can be used directly for most applications, with a big benefit being that you can now grab an array of datetimes directly by using the date string (which saves you from having to iterate through an array, checking property values with each iteration).

If accessing datetime arrays directly by date is not sufficient, and you also need to loop through the dates, you can loop through the keys using the for ... in syntax like so:

var outputArray = [];
for (var prop in datesAndTimes) {
 outputArray.push({date:prop,events:datesAndTimes[prop]});
}

If you need to have the output in an array format for other reasons, such as sorting or mapping, that might outweight the benefits of the hash key object.

Here's a snippet demonstrating using an object with hash keys instead of an array of objects:

var original = [{
  datetime: '2015-07-22 09:00:00'
}, {
  datetime: '2015-07-22 11:00:00'
}, {
  datetime: '2015-07-23 10:00:00'
}];
var length = original.length;
var datesAndTimes = {};
while (length--) {
  var currentDate = original[length].datetime.substring(0, 10);
  if (!datesAndTimes[currentDate]) {
    datesAndTimes[currentDate] = [];
  }
  datesAndTimes[currentDate].push(original[length].datetime);
}

document.getElementById("output").innerHTML = "<b>object: </b>" + JSON.stringify(datesAndTimes) + "<br/><hr/>";

for (var prop in datesAndTimes) {
  document.getElementById("output").insertAdjacentHTML("beforeend", "<b>" + prop + ":</b> " + JSON.stringify(datesAndTimes[prop]) + "<br/>");
}
<div id="output" />

\$\endgroup\$
  • \$\begingroup\$ I am using the array structure for mapping making your answer not entirely appropriate. It is helpful though in a general sense to see this idea and to have ti available for the future. (Also, I only used new in the example :) ). \$\endgroup\$ – Peter Smith Jul 23 '15 at 17:56
  • \$\begingroup\$ That makes sense! It's also worth noting that you can get an object's keys as an array using Object.keys(obj), so you could map the hash object to an array in your format using var arr = Object.keys(datesAndTimes).map(function(key){return {date:key,datetime:datesAndTimes[key]};}); \$\endgroup\$ – Thriggle Jul 23 '15 at 18:07
  • \$\begingroup\$ But yeah, keep this functionality in mind for the future... if you ever find yourself with a massive array of objects, you'll see a big performance impact when you need to loop through the array to find specific objects or check if they exist. Accessing properties by hash key instead is remarkably speedy. \$\endgroup\$ – Thriggle Jul 23 '15 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.