Array.prototype.filter() 的就地替代方案是什么

问题描述 投票:0回答:7

我有一个数组,我想从中删除一些元素。我不能使用

Array.prototype.filter()
,因为我想就地修改数组(因为它节省了内存分配,而且对我来说更重要的是,使代码在我的用例中更简单)。我可以使用
filter
的就地替代方案吗,也许类似于如何将
Array.prototype.forEach()
用作
Array.prototype.map()
的就地变体?

编辑:根据要求提供的最小示例:

function someCallback(array) {
  // do some stuff
  array.filterInPlace(function(elem) {
    var result = /* some logic */
    return result;
  })
  // do some more stuff
}
javascript functional-programming
7个回答
40
投票

您可以使用以下内容:

array.splice(0, array.length,...array.filter(/*YOUR FUNCTION HERE*/))

说明:

  • 拼接动作到位
  • 第一个参数意味着我们从数组的开头开始
  • 第二个意味着我们删除整个数组
  • 第三种意味着我们用过滤后的副本替换它
  • ... 是扩展运算符(仅限 ES6),并将数组的每个成员更改为单独的参数

39
投票

是否有过滤器的就地替代方案

不,但是自己编写并不难。这是一种排除所有不符合条件的值的方法。

function filterInPlace(a, condition) {
  let i = 0, j = 0;

  while (i < a.length) {
    const val = a[i];
    if (condition(val, i, a)) a[j++] = val;
    i++;
  }

  a.length = j;
  return a;
}

condition
被设计为与传递给
Array#filter
的回调具有相同的签名,即
(value, index, array)
。为了与
Array#filter
完全兼容,您还可以接受第四个
thisArg
参数。

使用
forEach

使用

forEach
的一个小优点是它会跳过空槽。这个版本:

  • 压缩具有空槽的数组
  • 实施
    thisArg
  • 如果我们还没有遇到失败的元素,则跳过分配

function filterInPlace(a, condition, thisArg) {
  let j = 0;

  a.forEach((e, i) => { 
    if (condition.call(thisArg, e, i, a)) {
      if (i!==j) a[j] = e; 
      j++;
    }
  });

  a.length = j;
  return a;
}

a = [ 1,, 3 ];
document.write('<br>[',a,']');

filterInPlace(a, x=>true);
document.write('<br>[',a,'] compaction when nothing changed');

b = [ 1,,3,,5 ];
document.write('<br>[',b,']');

filterInPlace(b, x=>x!==5);
document.write('<br>[',b,'] with 5 removed');


7
投票

你可以使用什么

  • Array#filter
    返回一个具有相同元素的数组,但不一定是全部。
  • Array#map
    为每个循环返回一些内容,结果是一个与源数组长度相同的数组。
  • Array#forEach
    不返回任何内容,但每个元素都会被处理,如上所示。
  • Array#reduce
    返回您想要的任何内容。
  • Array#some
    /
    Array#every
    返回布尔值。

但是上面的任何内容都不会在原位改变长度问题的原始数组。

我建议使用 while 循环,从最后一个元素开始并将 splice 应用于要删除的元素。

这可以保持索引有效,并允许在每个循环中递减。

示例:

var array = [0, 1, 2, 3, 4, 5],
    i = array.length;

while (i--) {
    if (array[i] % 2) {
        array.splice(i, 1);
    }
}
console.log(array);


5
投票

如果您能够添加第三方库,请查看lodash.remove

predicate = function(element) {
  return element == "to remove"
}
lodash.remove(array, predicate)

2
投票

当前选择的答案效果很好。然而,我希望这个函数成为数组原型的一部分。

Array.prototype.filterInPlace = function(condition, thisArg) {
    let j = 0;

    this.forEach((el, index) => {
        if (condition.call(thisArg, el, index, this)) {
            if (index !== j) {
                this[j] = el;
            }
            j++;
        }
    })

    this.length = j;
    return this;
}

这样我就可以像这样调用函数了:

const arr = [1, 2, 3, 4];
arr.filterInPlace(x => x > 2);
// [1, 2]

我只是将其保存在名为 Array.js 的文件中,并在需要时使用它。


2
投票

user663031 答案的稍微简化的 TypeScript 变体:

function filter_in_place<T>(array: Array<T>, condition: (value: T) => boolean)
{
    let next_place = 0;

    for (let value of array)
    {
        if (condition(value))
            array[next_place++] = value;
    }

    array.splice(next_place);
}

使用

splice()
而不是设置
length
会导致 Chrome 76 上
1400000
迭代速度提高 1.2 倍。


0
投票

这是一种干净的方法,并且会向后迭代以提高性能。

function filterInPlace<T> (array: T[], predicate: (item: T, index: number, arr: T[]) => unknown) {
  for (let i = array.length; i--; i >= 0) {
    if (!predicate(array[i]!, i, array)) {
      array.splice(i, 1)
    }
  }
  return array
}
© www.soinside.com 2019 - 2024. All rights reserved.