如何在JavaScript中过滤数组而不使用另一个数组

7

到目前为止,我尝试了这个方法,但它返回未经过滤的数组:

function filterRangeInPlace(array, min, max) {
  array = array.filter(item => (item >= min && item <= max));
  console.log(array);
}

let arr = [5, 3, 8, 1];

filterRangeInPlace(arr, 1, 4);

console.log(arr);


1
.filter()总是会创建一个新的数组。你的代码不起作用,因为函数中对array的赋值是对参数的赋值,而JavaScript是一种按值传递的语言。 - Pointy
要实现问题标题所请求的功能,您需要编写一个函数来“压缩”数组以消除被过滤掉的条目。 - Pointy
1
在Javascript中通过引用传递变量 - adiga
函数.filter()不像for循环那样,它实际上是在数组实体上应用过滤器并返回一个新的过滤后的数组。它不像for循环那样对实际数组进行更改。因此,当您需要过滤任何数组时,必须使用一个变量来保存过滤后的数组结果,或者只需使用for循环并从数组中删除不满足条件的条目,这会影响原始数组元素。 - Neel Rathod
@NeelRathod 公平地说,一个for循环完全可以创建一个新的过滤数组。在底层,我猜测.filter()使用了某种形式的循环。 - undefined
这段代码运行正常。 我不明白这个问题。 - undefined
4个回答

10

如果实际上很重要在不创建另一个数组的情况下进行过滤,你必须有点老派,在数组中使用两个索引进行迭代,并沿途复制值。每次遇到未通过过滤测试的元素时,您将增加一个索引,但不会增加另一个索引。在完成后,将数组 .length 重置为尾随索引:

function filterInPlace(array, fn) {
  let from = 0, to = 0;
  while (from < array.length) {
    if (fn(array[from])) {
      array[to] = array[from];
      to++;
    }
    from++;
  }
  array.length = to;
}

这种方法的优点是O(n),只需要对数组进行一次遍历,而包含.splice()的解决方案是O(n2)。

要进行“范围检查”,您可以编写另一个函数,根据最小值和最大值创建一个过滤谓词:

function rangePredicate(min, max) {
  return n => n >= min && n <= max;
}

接着,您可以将该返回值传递给过滤器函数:

var arr = [1, 2, 3, ... ];
filterInPlace(arr, rangePredicate(0, 10));

谢谢您的精彩回答,这正是我所寻找的 - 一种快速且廉价(对于内存而言)的过滤数组的方法。 - Robert Hovhannisyan
请问您能否添加您的 fn 版本以完善答案?谢谢。 - Robert Hovhannisyan
1
@RobertHovhannisyan 好的,fn 可以是任何函数,就像你将传递给 .filter() 的函数一样。如果它返回 true,则该值包含在最终数组中,否则不包含。 - Pointy
1
@RobertHovhannisyan的回答得到了扩展。 - Pointy
哦,太好了。现在一切都清楚了。再次感谢您! - Robert Hovhannisyan

4
你需要返回新的过滤后的数组并将其分配给一个变量(例如arr本身):

function filterRangeInPlace(array, min, max){
  return array.filter(item => (item >= min && item <= max));
}

let arr = [5, 3, 8, 1];

arr = filterRangeInPlace(arr, 1, 4);

console.log(arr);


4
当然,这个方法可以工作,但它不能满足问题中“不使用另一个数组”的部分要求。 - Pointy
那是一种相当聪明的过滤数组的方法,但在这种情况下我同意@Pointy的看法。不过无论如何,非常感谢您的好回答! - Robert Hovhannisyan

3
你不能使用 .filter() 来实现此功能,因为它返回一个新的数组,而不是修改原始数组。你需要自己遍历数组,删除不符合条件的元素。
你需要按照索引降序进行循环,因为删除一个元素会使所有剩余元素的索引向下移动,如果按照递增的顺序操作将导致跳过下一个元素。这也意味着你不能使用像 .forEach() 这样的内置函数。

function filterRangeInPlace(array, min, max) {
  for (let i = array.length-1; i >= 0; i--) {
    if (array[i] < min || array[i] > max) {
      array.splice(i, 1);
    }
  }
}

let arr = [5, 3, 8, 1];
filterRangeInPlace(arr, 1, 4);
console.log(arr);


如果您使用 array.splice(),那么您需要这样做。我猜您可以继续前进并递减 i - Barmar
但是你不能使用 forEach,它没有提供备份的方式。 - Barmar
2
我会按照我在答案中写的做,因为在我的脑海里,我仍然是一名在pdp-11上编写C程序的程序员。 - Pointy
那是一个非常棒的解决方案。非常感谢您的代码! - Robert Hovhannisyan

3

返回该值并将其设置为自身。

function filterRangeInPlace(array, min, max) {
  return array.filter(item => (item >= min && item <= max));
}

let arr = [5, 3, 8, 1];

arr = filterRangeInPlace(arr, 1, 4);

console.log(arr);

当然,您也可以完全省略该函数,使用:

let arr = [5, 3, 8, 1];

arr = arr.filter(item => (item >= min && item <= max));

console.log(arr);

那是一个有趣且简短的回答。谢谢您的努力! - Robert Hovhannisyan
@RobertHovhannisyan 哎呀?这难道不是你在问题中尝试但失败了的事情吗? - undefined

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