0
\$\begingroup\$

I have an array of log objects with log_types and dates, like this:

[
  {
    log_type: 'eventOne',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventOne',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventTwo',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventTwo',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventTwo',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventOne',
    date: '2020-08-27T00:00:00+00:00'
  },
]

For charting purposes, I need group all these logs by their log_types. Then I need to take all the logs for each log type, and group them by date. These will be formatted where t is the date and y is the count of that date occurring. Like this:

{
  eventOne: [
    { x: 2020-08-21T04:00:00.000Z, y: 0 },
    { x: 2020-08-22T04:00:00.000Z, y: 6 },
    { x: 2020-08-23T04:00:00.000Z, y: 0 },
    { x: 2020-08-24T04:00:00.000Z, y: 16 },
    { x: 2020-08-25T04:00:00.000Z, y: 0 },
    { x: 2020-08-26T04:00:00.000Z, y: 0 },
    { x: 2020-08-27T04:00:00.000Z, y: 22 }
  ],
  eventTwo: [
    { x: 2020-08-21T04:00:00.000Z, y: 0 },
    { x: 2020-08-22T04:00:00.000Z, y: 0 },
    { x: 2020-08-23T04:00:00.000Z, y: 1 },
    { x: 2020-08-24T04:00:00.000Z, y: 0 },
    { x: 2020-08-25T04:00:00.000Z, y: 0 },
    { x: 2020-08-26T04:00:00.000Z, y: 0 },
    { x: 2020-08-27T04:00:00.000Z, y: 17 }
  ]
}

So in plain english the goal is to get the number of events that were logged for each day, for each log type.

I've accomplished this, but it is a series of mangled lodash functions to where any sense of readability or efficiency has gone out the window.

// First group by log_type  
const logTypes = _.groupBy(logs, 'log_type');

const mappedLogs = {};
// Iterate through each log type
_.forOwn(logTypes, (value, key) => {
  const logDates = _.chain(value)
     // For each log type, group by date
     .groupBy('date')
     // Then map each log to the correct format
     .map((logValue, logKey) => ({ t: logKey, y: logValue.length }))
     .value();
  // Once we get the final mapped date values, assign them to the final object
  mappedLogs[key] = logDates;
});

I'm really hoping to find a way to do this more efficiently, I'm having trouble determining the efficiency of this current algorithm but I know the initial _groupBy touches each log O(n), the second _forOwn plus _groupBy I believe is another O(n), then the final mapping to correct format would own another O(n).

This is an application where there could be many thousands of logs so that is not an ideal efficiency.

\$\endgroup\$
3
  • \$\begingroup\$ I’m not sure I really understand. What you do is indeed O(n). You can’t hope to do better than this as clearly you need to go through every element of your array. But maybe you are concerned that you are making multiple large objects and are looping over the array effectively three times. Which is fair enough, and well yes you can do it all in one loop and just making one object. Just go through the array and at each element add the appropriate thing and you have already had something of that type and date increment the counter. I don’t think lodash has a neat function which generalises this. \$\endgroup\$ Commented Aug 28, 2020 at 1:46
  • \$\begingroup\$ Why is your example output not derived from your example input? That's going to confuse people trying to help you. It also puts the burden of coming up with mock data on others. \$\endgroup\$
    – GirkovArpa
    Commented Aug 29, 2020 at 20:42
  • \$\begingroup\$ It's also confusing how your example output could have objects with a y (count) value of zero. \$\endgroup\$
    – GirkovArpa
    Commented Aug 29, 2020 at 20:58

1 Answer 1

1
\$\begingroup\$

I don't see what lodash has to offer here. With modern JavaScript it can be written quite tersely thus:

const events = data.reduce((events, { log_type, date }) => {
  const event = (events[log_type] || (events[log_type] = []));
  const match = event.find(({ x }) => x == date);
  match ? match.y++ : event.push({ x: date, y: 1 });
  return events;
}, { });

The first line inside the .reduce method creates the event array if it doesn't exist.

The second line finds the element with a matching date.

The third line, if a matching date was found, increments its y property. Else, it adds a new element with the date.

For example:

const data = [
  {
    log_type: 'eventOne',
    date: '2020-08-27T20:00:00+00:00'
  },
  {
    log_type: 'eventOne',
    date: '2020-08-27T20:00:00+00:00'
  },
  {
    log_type: 'eventTwo',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventTwo',
    date: '2020-08-27T00:00:00+00:00'
  },
  {
    log_type: 'eventTwo',
    date: '2020-08-27T40:00:00+00:00'
  },
  {
    log_type: 'eventOne',
    date: '2020-08-27T00:00:00+00:00'
  },
];

const events = data.reduce((events, { log_type, date }) => {
  const event = (events[log_type] || (events[log_type] = []));
  const match = event.find(({ x }) => x == date);
  match ? match.y++ : event.push({ x: date, y: 1 });
  return events;
}, { });

console.log(events);

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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