【问题标题】:Why are some of the objects not getting removed in this .splice() and some are?为什么有些对象在这个 .splice() 中没有被删除,而有些是?
【发布时间】:2020-11-20 18:19:54
【问题描述】:

我正在使用 .splice() 从包含时间戳的对象数组中删除对象,具体取决于 userDates 是否包含匹配的时间戳或对象时间戳之前或之后 45 分钟范围内的时间戳.本质上是使用 userDates 数组日期值删除所有具有重叠日期值的对象。

当您运行此代码时,您会注意到一些对象被删除,但其他对象没有。

JSFiddle:https://jsfiddle.net/jb2t3Lr9/1/ 和重现问题的代码:

let userDates = ["2020-11-20T22:00:00.000Z","2020-11-20T23:00:00.000Z","2020-11-21T00:00:00.000Z","2020-11-21T01:00:00.000Z","2020-11-22T02:15:00.000Z","2020-11-22T03:15:00.000Z","2020-11-22T01:00:00.000Z","2020-11-22T00:00:00.000Z","2020-11-21T23:00:00.000Z","2020-12-13T22:00:00.000Z","2020-12-14T22:00:00.000Z","2020-12-15T22:00:00.000Z","2020-12-16T22:00:00.000Z","2020-12-13T23:00:00.000Z","2020-12-14T23:00:00.000Z","2020-11-21T20:00:00.000Z","2020-11-22T20:00:00.000Z","2020-11-22T19:00:00.000Z","2020-11-21T19:00:00.000Z"];

let datesToUpdate = [
  { sessionInterval: 50, dateTime: '2020-11-22T20:00:00.000Z' },
  { sessionInterval: 50, dateTime: '2020-11-21T20:00:00.000Z' },
  { sessionInterval: 50, dateTime: '2020-11-21T19:00:00.000Z' },
  { sessionInterval: 50, dateTime: '2020-11-22T19:00:00.000Z' },
  { sessionInterval: 50, dateTime: '2020-11-22T17:30:00.000Z' },
  { sessionInterval: 50, dateTime: '2020-11-21T17:00:00.000Z' }
];


function removeOverlappingDates(userDates, datesToUpdate) {
  const FIFTEEN_MINUTES = 15 * 60 * 1000; // milliseconds
  datesToUpdate.forEach((toUpdate, index) => {
    userDates.forEach((date) => {
      let dateInMS = new Date("" + date).valueOf();
      const fifteenBefore = dateInMS - FIFTEEN_MINUTES;
      const thirtyBefore = dateInMS - FIFTEEN_MINUTES * 2;
      const fortyFiveBefore = dateInMS - FIFTEEN_MINUTES * 3;
      const fifteenAfter = dateInMS + FIFTEEN_MINUTES;
      const thirtyAfter = dateInMS + FIFTEEN_MINUTES * 2;
      const fortyFiveAfter = dateInMS + FIFTEEN_MINUTES * 3;
      let toUpdateInMS = new Date("" + toUpdate.dateTime).valueOf();
      if (
        toUpdateInMS == fifteenBefore ||
        toUpdateInMS == thirtyBefore ||
        toUpdateInMS == fortyFiveBefore ||
        toUpdateInMS == fifteenAfter ||
        toUpdateInMS == thirtyAfter ||
        toUpdateInMS == fortyFiveAfter ||
        toUpdateInMS == dateInMS
      ) {
        datesToUpdate.splice(index, 1);
      }
    });
  });

  return datesToUpdate;
}

console.log("datesToUpdate 1", datesToUpdate);
datesToUpdate = removeOverlappingDates(userDates, datesToUpdate);
console.log("datesToUpdate 2", datesToUpdate);

对我来说更奇怪的是,如果我只是将日期时间值数组相互比较(与对象数组包含的日期时间值相同),那么所有内容都会被正确删除。小提琴:https://jsfiddle.net/rc1mvzLq/

【问题讨论】:

  • 不要修改你当前正在迭代的集合(或者至少只有当你真的知道你在做什么时)-> datesToUpate.forEach((toUpdate, index) => { /*...*/ datesToUpdate.splice(index, 1) });如果删除索引1 处的元素,索引2 处的元素会发生什么情况?
  • 在最后一个元素的开始处使用Array.prototype.filter()for 循环并转到第一个
  • @Andreas,哦,索引值改变了!所以这就是为什么我什至尝试将索引一起收集到另一个数组中并在循环之后运行另一个循环来拼接 datesToUpdate,它也不起作用......啊。好的,我再试一次。
  • 您不是在检查代码中的范围,而是检查提到的范围内的确切值。这是有目的的还是应该实际上是if (toUpdateInMS >= fortyFiveBefore || toUpdateInMS <= fortyFiveAfter)?同样如前所述,您应该复制数组并遍历数组的副本,因为一旦进行第一次拼接,数组索引就会移动,但如果它已经在索引 2 上,则循环将永远不会回到索引 1。但是那仍然不会工作,因为您依赖于旧索引,但需要找到新元素的索引。
  • *更正:if (toUpdateInMS >= fortyFiveBefore && toUpdateInMS <= fortyFiveAfter)

标签: javascript node.js datetime


【解决方案1】:

我将尝试使用最少的新方法来编写此代码,但正如 cmets 中所述,filter 非常适合此用例。

首先,永远不要更改您正在迭代的数组。这会导致难以调试的行为。但是,在这种情况下,开头的数组副本对您没有帮助,因为您正在使用 splice 删除数组中间的一个元素,这会导致一堆元素重新索引。

例如,如果您有[A,B,C,D,E] 并且您删除了索引1 处的元素,那么您现在有[A,C,D,E,F] 并且(因为该索引的迭代已经完成)您的索引增加并且现在是2 并且你永远不会在你的逻辑中测试值C。像这种情况还有一些情况,您可以看到多个元素是如何不受检查的。唯一可行的方法是,两个需要删除的元素永远不会彼此相邻。

为了保持这个简单并尽可能接近您的原始逻辑,我建议您计算已删除的元素数量并将索引偏移该数量。然而,也复制数组,这样你就不会改变你正在迭代的那个(注意:浅拷贝就足够了,你可以简单地使用spread syntax 创建一个具有 same 的新数组强> 对象)。

function removeOverlappingDates(userDates, datesToUpdate) {
  const FIFTEEN_MINUTES = 15 * 60 * 1000; // milliseconds
  const newDatesToUpdate = [...datesToUpdate];
  let deletedElementsCount = 0;
  datesToUpdate.forEach((toUpdate, index) => {
    // same code as before
      if (
        // check if this condition should maybe be toUpdateInMS >= fortyFiveBefore && toUpdateInMS <= fortyFiveAfter
      ) {
        const newIndex = index - deletedElementsCount;
        newDatesToUpdate.splice(newIndex, 1);
        deletedElementsCount = deletedElementsCount + 1;
      } 
    });
  });

  return newDatesToUpdate;
}

附带说明,我要更改副本是有原因的,这是因为 datesToUpdate 作为函数参数传递。如果datesToUpdate 数组在函数内部发生变化,它之后将保持变化。您不应该更改函数内部的参数对象(包括数组),除非您希望它保持这种状态。执行此操作的函数被认为具有side effects,如果不谨慎使用,也可能难以调试。在您的情况下,不需要做副作用,因为您正在返回结果。

【讨论】:

  • 将成功的元素push 到一个新数组中要比复制数组并拼接失败要简单得多。所以变化只是datesToUpdate.splice(index, 1)newArray.push(datesToUpdate[index]),它有效地模拟了filter
  • 是的,这要简单得多,但我想保持逻辑和结构与以前一样,推送将涉及反转一个我一开始不确定是否正确的条件.但我绝对同意 push 更好更简单。
  • 一种更简单的方法,它不使用过滤器并且也适用于改变集合,是从后面迭代的普通旧for。这不需要任何额外的更改(例如重新计算索引,...)
  • @robertfoenix 是的。如果你现在移除一个元素,你只会改变你已经访问过的元素的索引。
  • @robertfoenix “它更快,内存效率略高......” - 在你发现代码中的实际瓶颈之前不要考虑这些事情使用分析器
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
  • 2021-02-27
  • 2012-11-16
  • 2013-08-18
  • 1970-01-01
  • 2016-12-25
相关资源
最近更新 更多