javascriptreasonrescript

How to create an efficient group by function without mutation?


Is there a way to efficiently implement a group by function without mutation?

Naive implementation:

var messages = [
  {insertedAt: "2021-01-10"},
  {insertedAt: "2021-01-12"},
  {insertedAt: "2021-01-13"},
  {insertedAt: "2021-01-13"},
  {insertedAt: "2021-01-13"},
  {insertedAt: "2021-01-14"},
  {insertedAt: "2021-01-15"},
  {insertedAt: "2021-01-15"},
  {insertedAt: "2021-01-16"},
  {insertedAt: "2021-01-17"},
  {insertedAt: "2021-01-17"},
  {insertedAt: "2021-01-17"},
  {insertedAt: "2021-01-18"},
  {insertedAt: "2021-01-18"},
]

var messagesGroupedByDate = messages.reduce(function (data, message) {
  if (
    data.some(function (point) {
      return point.date === message.insertedAt;
    })
  ) {
    return data.map(function (point) {
      if (point.date === message.insertedAt) {
        return {
          date: point.date,
          count: (point.count + 1) | 0,
        };
      } else {
        return point;
      }
    });
  } else {
    return data.concat([
      {
        date: message.insertedAt,
        count: 1,
      },
    ]);
  }
}, []);


console.log(messagesGroupedByDate);

For the sake of argument, there's no need to make this more generic. The problem I'm facing is that I'm looping three times:

If there's not really any good way to make this efficient in ReScript, then I can always use raw JavaScript for this function, but I'm curious if it's possible to do this efficiently without mutation.


Solution

  • Just add data to a Map() and then convert to array, then to object. It doesn't mutate anything as per your request.

    We may simplify this even more but it is 5:00 AM right now and my brain is asleep now.

    var messages = [
      {insertedAt: "2021-01-10"},
      {insertedAt: "2021-01-12"},
      {insertedAt: "2021-01-13"},
      {insertedAt: "2021-01-13"},
      {insertedAt: "2021-01-13"},
      {insertedAt: "2021-01-14"},
      {insertedAt: "2021-01-15"},
      {insertedAt: "2021-01-15"},
      {insertedAt: "2021-01-16"},
      {insertedAt: "2021-01-17"},
      {insertedAt: "2021-01-17"},
      {insertedAt: "2021-01-17"},
      {insertedAt: "2021-01-18"},
      {insertedAt: "2021-01-18"},
    ];
    
    const mapped = new Map();
    
    messages.forEach(message => {
        // if date already seen before, increment the count
        if (mapped.has(message.insertedAt)) {
            const count = mapped.get(message.insertedAt);
            mapped.set(message.insertedAt, count+1);
        } else {
            // date never seen before, add to map with initial count
            mapped.set(message.insertedAt, 1);
        }
    });
    
    const msgArr = Array.from(mapped);
    
    const final = msgArr.map(([date, count])=> ({date, count}));
    
    console.log(final);