Home > OS >  How to create a new array of objects from two arrays based on object values?
How to create a new array of objects from two arrays based on object values?

Time:09-12

I'm struggling with merging two arrays of objects (fetched from blockchain data) into a new array based on object values.

The goal is to get the latest interaction with a user.

A simplified but close representation of the data structure this problem is faced in:

interface MsgSlice {
    messageId: string;
    messageDataSlice: {
        senderId?: string;
        receiverId: string;
        timestamp: number;
    };
};

const latestReceivedMsgs: MsgSlice[] = [
    {
        messageId: "messageId1",
        messageDataSlice: {
            senderId: "userId1",
            receiverId: "ownerId", // <- always same in that array
            timestamp: 101,
        },
    },
    {
        messageId: "messageId3",
        messageDataSlice: {
            senderId: "userId2",
            receiverId: "ownerId",
            timestamp: 103,
        },
    },
    {
        messageId: "messageId5",
        messageDataSlice: {
            senderId: "userId3",
            receiverId: "ownerId",
            timestamp: 105,
        },
    },
];

const latestSentMsgs: MsgSlice[] = [
    {
        messageId: "messageId2",
        messageDataSlice: {
            // senderId: "ownerId",
            receiverId: "userId1",
            timestamp: 102,
        },
    },
    {
        messageId: "messageId4",
        messageDataSlice: {
            receiverId: "userId3",
            timestamp: 104,
        },
    },
];

The desired result should contain the latest messageId either 'sent to' or 'received by' the corresponding user. Something like this:


const latestInteraction = [
    {
        user: "userId1",
        messageId: "messageId2",
        timestamp: 102,
    },
    {
        user: "userId2",
        messageId: "messageId3",
        timestamp: 103,
    },
    {
        user: "userId3",
        messageId: "messageId5",
        timestamp: 105,
    },
]   

As a solution I thought of looping over the arrays and per iteration also looping over the other array to compare the senderId and receiverId values. If "senderId is == one of the looped receiverIds", it could be sent into an interaction array and then time sorted and filtered. Unfortunately, I couldn't figure out how to get it working. Also, I thought that my thinking might be limited here and that there are probably more efficient ways to do it than in my solution concept.

CodePudding user response:

The approach I'd take is to convert your received and sent messages into a single array of "interactions" that contain only the information you care about. For a received message you want to look at the senderId, whereas for a sent message you want to look at the receiverId (the idea is that you want the other user for each interaction, not the current user). That could look like this:

interface Interaction {
  user: string
  messageId: string
  timestamp: number
}

function latestInteractions(
  receivedMsgs: MsgSlice[], 
  sentMsgs: MsgSlice[]
): Interaction[] {

  const allInteractions: Interaction[] = [];
  for (const m of receivedMsgs) {
    const sender = m.messageDataSlice.senderId;
    if (sender === undefined) continue;
    allInteractions.push({
      user: sender,
      messageId: m.messageId,
      timestamp: m.messageDataSlice.timestamp
    });
  }
  for (const m of sentMsgs) {
    allInteractions.push({
      user: m.messageDataSlice.receiverId,
      messageId: m.messageId,
      timestamp: m.messageDataSlice.timestamp
    });
  }

Note that if somehow a received message doesn't have a senderId then we just skip it. Maybe we should throw an error instead? That's up to you. Now we have a single array filled with all interactions. We want to collect just one such interaction for each user in the array, and if we ever have more than one we should keep just the one with the greatest timestamp. That could look like this:

  const interactionMap: { [k: string]: Interaction } = {};
  for (const i of allInteractions) {
    if (!(i.user in interactionMap) || interactionMap[i.user].timestamp < i.timestamp) {
      interactionMap[i.user] = i;
    }
  }

The interactionMap is now a plain object whose keys are the user strings and whose values are the latest Interaction for each user. This has all the information we want, but you want an array and not an object. So we can just use the Object.values() method to get an array of the values:

  return Object.values(interactionMap);
}

That's an array in some order; if you care you can sort it according to your needs.


Let's make sure it works with your example:

const latestInteraction = latestInteractions(latestReceivedMsgs, latestSentMsgs);
console.log(latestInteraction);
/* [{
  "user": "userId1",
  "messageId": "messageId2",
  "timestamp": 102
}, {
  "user": "userId2",
  "messageId": "messageId3",
  "timestamp": 103
}, {
  "user": "userId3",
  "messageId": "messageId5",
  "timestamp": 105
}]  */

Looks good!

Playground link to code

CodePudding user response:

You can use the hash grouping approach, the vanila JS solution

Live Demo:

const latestReceivedMsgs = [{messageId: "messageId1",messageDataSlice: {senderId: "userId1",receiverId: "ownerId", timestamp: 101,},},{messageId: "messageId3",messageDataSlice: {senderId: "userId2",receiverId: "ownerId",timestamp: 103,},},{messageId: "messageId5",messageDataSlice: {senderId: "userId3",receiverId: "ownerId",timestamp: 105,},},];
const latestSentMsgs = [{messageId: "messageId2",messageDataSlice: {receiverId: "userId1",timestamp: 102,},},{messageId: "messageId4",messageDataSlice: {receiverId: "userId3",timestamp: 104,},},];

const grouped = [...latestReceivedMsgs, ...latestSentMsgs]
  .reduce((acc, { messageId, messageDataSlice }) => {
    const { timestamp, senderId, receiverId } = messageDataSlice;
    const user = senderId ?? receiverId;
    const msgItem = { user, messageId, timestamp };
    if ((acc[user]?.timestamp ?? 0) < timestamp) acc[user] = msgItem;
    
    return acc;
  }, {});

const result = Object.values(grouped);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }

CodePudding user response:

You could simply flatten both arrays into one, then sort them per timestamp. Such as:

let msgs: MsgSlice[] = [];
msgs.push(...latestReceivedMsgs);
msgs.push(...latestSentMsgs);

msgs.sort((a, b) => {
    return a.timestamp - b.timestamp ;
});

CodePudding user response:

Perhaps you can join them into a single array and then sort them by their timestamp?

const sortedMsgs = [...latestReceivedMsgs, ...latestSentMsgs] sortedMsgs.sort((a,b)=>a.messageDataSlice.timestamp-b.messageDataSlice.timestamp)

  • Related