如何基于对象值从两个数组创建一个新的对象数组?

3

我正在努力将两个对象数组(从区块链数据中提取)根据对象值合并为一个新数组。

目标是获取与用户的最新交互。

这个问题面临的数据结构是一个简化但接近的表示:

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,
        },
    },
];

期望的结果应该包含最新的 messageId,即被相应用户“发送到”或“接收到”的消息。类似这样:

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

作为解决方案,我考虑循环遍历这些数组,并在每次迭代中循环另一个数组来比较 senderIdreceiverId 值。如果 "senderId 等于其中一个循环到的 receiverId",则可以将其发送到一个 interaction 数组中,然后进行时间排序和过滤。不幸的是,我无法弄清楚如何使其正常工作。我的思路可能有限,在我的解决方案概念中可能有更有效的方法。

输出数组的顺序对您是否很重要?如果是,您希望它们按什么顺序排序? - jcalz
我发现了期望结果中时间戳的一个小错误。我进行了编辑以更好地表示目标。 输出数组应包含相应用户“发送到”或“接收自”的最新messageId。对于这种情况,据我所见,该数组内部的顺序并不重要。但是如果必须选择,那么应按时间戳排序。 - tenxsoydev
如果这是你的目标,那么只获取具有最高时间戳的单个对象就足够了,不是吗? - Foxcode
1
这种方法对你有用吗?如果有的话,我可以将其发布为答案并解释;如果没有,那么我错过了什么?(如果您想让我收到回复通知,请提及@jcalz) - jcalz
@Foxcode 给定的是发送给用户的sentObjects和来自用户的receivedObjects,它们都具有最高时间戳。基于正在进行交互的用户,我需要比较哪个时间戳更高 - 是发送的对象还是接收的对象。 - tenxsoydev
@jcalz 这看起来非常有前途,并且在第一次实现测试中运行良好。我将在完整的实现中进一步了解。但我已经认为这是解决方案! - tenxsoydev
4个回答

1

我会采用的方法是将你收到和发送的消息转换为一个包含你关心的信息的“互动”数组。 对于收到的消息,您需要查看senderId,而对于发送的消息,您需要查看receiverId(想法是您希望每个互动都有另一个用户,而不是当前用户)。 它可能看起来像这样:

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
    });
  }

请注意,如果收到的消息没有senderId,那么我们将跳过它。也许我们应该抛出一个错误?这取决于您。现在,我们有一个填充了所有交互的单个数组。我们想要为数组中的每个user收集一个这样的交互,并且如果我们有多个交互,我们应该只保留具有最大timestamp的交互。代码如下:
  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;
    }
  }

interactionMap现在是一个普通对象,其键为user字符串,值为每个用户的最新Interaction。这里有我们想要的所有信息,但是我们需要一个数组而不是一个对象。因此,我们可以使用Object.values()方法来获取值的数组:

  return Object.values(interactionMap);
}

这是一个按照某种顺序排列的数组;如果您关心的话,可以根据自己的需求使用sort对其进行排序。


让我们确保它能与您的示例一起使用:

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
}]  */

看起来不错!

代码演示链接


1
您可以使用哈希分组方法,这是基于原生JS的解决方案。
演示地址:

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 }

更新

或者typescript变体:

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

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

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

const grouped = ([...latestReceivedMsgs, ...latestSentMsgs] as MsgSlice[])
  .reduce((acc, { messageId, messageDataSlice }) => {
    const { timestamp, senderId, receiverId } = messageDataSlice;
    const user = senderId ?? receiverId ?? "unindefined";
    const msgItem = { user, messageId, timestamp };
    if ((acc[user]?.timestamp ?? 0) < timestamp) acc[user] = msgItem
    
    return acc;
  }, {} as { [key: Interaction['user']]: Interaction });

const result: Interaction[] = Object.values(grouped);

console.log(result);

这里需要问一个愚蠢的问题:测试这种方法并尝试满足 TypeScript。返回一个数组而不是一个对象将让我更容易实现这个目标。看起来似乎没有任何问题。它将以用户作为索引,这看起来有点不熟悉。是否有什么反对使用它作为数组的内容呢? - tenxsoydev
1
@tenxsoydev,我在这里制作了TypeScript解决方案链接 - A1exandr Belan

0
你可以将这两个数组合并成一个,然后按时间戳排序。例如:
let msgs: MsgSlice[] = [];
msgs.push(...latestReceivedMsgs);
msgs.push(...latestSentMsgs);

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

-1
也许你可以将它们合并成一个数组,然后按照时间戳进行排序?
const sortedMsgs = [...latestReceivedMsgs, ...latestSentMsgs]
sortedMsgs.sort((a,b)=>a.messageDataSlice.timestamp-b.messageDataSlice.timestamp)

我以为这是用于聊天室之类的东西,而你想按时间戳进行排序。 - Drew
OP没有提供足够的细节,使得此答案无效。请先使用评论来了解更多信息。 - emerson.marini

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接