前言
删除数组中指定的元素是编写程序算法中常用的操作之一。
本文章以力扣第27算法题移除元素为例详细解释了JavaScript数组删除指定元素的方法的原理和应用。
27移除元素
题目如图所示
本题友情客串这篇文章,也可直接跳过看后面的内容。
splice()方法
基础用法
splice() 方法用于添加或删除数组中的元素。
注意:这种方法会改变原始数组。
返回值
如果删除一个元素,则返回一个元素的数组。 如果未删除任何元素,则返回空数组
记录试错过程
下面通过力扣的27题移除元素具体查看splice方法的功能和用法
题目要求原地修改数组,而splice可以改变原始数组,下面尝试用splice方法做题
最简单的一个思路,遍历数组中的所有值,如果当前值等于val,就把当前值splice(i,1)
先用示例一的数据测试一下
代码如下
function removeElement() {
let nums = [3, 2, 2, 3];
let val = 3;
for (let i = 0; i < nums.length; i++) {
if (nums[i] == val) {
nums.splice(i, 1)
}
}
console.log("处理之后", nums)
return nums.length
};
运行截图
代码执行之后删除了原数组中所有的3,到这没有发现任何问题
然后测试第二组数据,就是数组为[2,2]
修改上面代码中的数据
let nums = [2, 2];
let val = 2;
运行结果截图
代码执行之后返回了一个2,删除了部分元素。
如果是
let nums = [2, 2, 3, 2];
let val = 2;
很疑惑……
let nums = [2, 2, 2, 3, 2, 1, 3, 2];
let val = 2;
有一个三没有去掉。。原因是由于splice方法改变原数组的长度,但循环中i仍然会读取原始数组长度,造成跳过或者只删除数组中符合条件的部分元素。解决这个办法有三种办法(下面的代码是移除元素的题解)
- 可以在循环中使用倒序遍历
var removeElement = function (nums, val) {
// splice方法会改变原数组长度,但是循环中仍会读取原始数组长度,会造成跳过或者只删除数组中符合条件的部分元素。
// 所以这里遍历数组使用倒序遍历
for (let i = nums.length - 1; i >= 0; i--) {
if (nums[i] == val) {
nums.splice(i, 1)
}
}
return nums.length
};
- 同步索引,对符合条件的元素执行splice方法之后进行i–操作,使读取的索引跟splice后的数组同步
var removeElement = function (nums, val) {
// splice方法会改变原数组长度,但是循环中仍会读取原始数组长度,会造成跳过或者只删除数组中符合条件的部分元素。
// 索引同步
for (let i = 0; i < nums.length; i++) {
if (nums[i] == val) {
nums.splice(i, 1)
i--;//使读取的索引和splice之后数组同步
}
}
return nums.length
};
- 使用其他方法替代,比如filter方法
filter()方法
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
移除元素的题目要求操作原始数组,而filter方法不会改变原始数组,所以下面用filter方法实现移除元素返回一个新的数组
function removeElement() {
let nums = [2, 2, 2, 3, 2, 1, 3, 2];
let val = 2;
for (const num of nums) {
let res = nums.filter((num) => {
return num != val
})
}
};
filter方法不会改变原数组,这里使用res接收filter之后的数组元素,,那么如果用nums接收是不是就可以就算是没有用别的数组空间了。
pop()方法
pop() 方法用于删除数组的最后一个元素并返回删除的元素。
注意:此方法改变数组的长度!
如果说要用pop实现移除元素的题,那实在是有点拽,最直接的思路就是,将数组的元素排序,之后将数组拆分 为两组,一组包含排序后等于val元素的最后一个元素,另一组包含原始数组中等于val的元素之后的剩余元素。使用pop方法删除第一组元素的后面等于val的值。之后再将两组数组合并,这样做法不能保证数组元素的顺序,也不保证在原数组中操作,需要借助额外的数组空间。
将数组中的元素分组原则是pop方法删除数组的最后一个元素,所以需要将要删除的元素放在数组的后面。
分组图示
function removeElement() {
let nums = [2, 2, 2, 3, 2, 1, 3, 2];
let val = 2;
nums.sort((a, b) => a - b); // 将数组元素排序
let idx = nums.lastIndexOf(val); // 找到排序后等于val的元素的最后一个元素的索引
let part1 = nums.slice(0, idx + 1); // 包含排序后等于val元素的最后一个元素及之前的部分
let part2 = nums.slice(idx + 1); // 原始数组中等于val的元素之后的剩余部分
let count = part1.filter(item => item === val).length;
for (let i = 0; i < count; i++) {
part1.pop(); // 删除第一部分数组中后面等于val的值
}
nums = part1.concat(part2); // 将两部分数组合并
console.log(nums); // 输出移除元素后的数组
};
运行结果截图
shift()方法
shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
注意: 此方法改变数组的长度!
使用shift方法移除元素的思路就是,将数组的元素排序 nums.sort((a, b) => a - b),排序之后数组中的元素从小到大升序排列,之后将数组拆分为两组,一组包含排序后等于val元素的第一个元素之前的所有元素,另一组包含原始数组中等于val的元素及其之后的剩余元素。使用shift方法删除第二组数组前面等于val值的元素。之后再将两组数组合并,这样做法不能保证数组元素的顺序,也不保证在原数组中操作,需要借助额外的数组空间。
将数组中的元素分组原则是shift方法删除数组的第一个元素,所以需要将要删除的元素放在数组的前面。
分组图示
function removeElement() {
let nums = [2, 2, 2, 3, 2, 1, 3, 2];
let val = 2;
// 对数组进行升序排序
nums.sort((a, b) => a - b);
// 找到等于 val 的第一个元素的索引
let index = nums.indexOf(val);
// 拆分数组为两组
let firstGroup = nums.slice(0, index);
let secondGroup = nums.slice(index);
// 使用 shift 方法删除第二组数组前面等于 val 值的元素
while (secondGroup.length > 0 && secondGroup[0] === val) {
secondGroup.shift();
}
// 合并两组数组
let result = firstGroup.concat(secondGroup);
console.log(result);
};
代码运行截图
slice()方法
slice() 方法可从已有的数组中返回选定的元素。
slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
注意: slice() 方法不会改变原始数组。
关于slice方法的参数
只有一个正整数参数就是截取当前参数索引到最后一个元素
只有一个负整数参数就是截取当前数组从右往左的参数索引到最后一个元素
slice(start,end)会提取从start到end的元素,包含start不包含end。
在编程中常见的slice方法的应用
返回现有数组的一部分
使用下面的数组测试不同的参数对应的返回值
const wishes = ['peaceful', 'happy', 'wealth', 'love', 'work'];
对slice不同的参数进行测试,下面是控制台输出截图
在类数组对象中调用的方式
slice() 方法会读取 this 对象的 length 属性,然后从 start 到 end 读取整数键属性,并将它们定义在一个新创建的数组中。
const LeiArray = {
length: 3,
0: 2,
1: 3,
2: 4,
};
console.log(Array.prototype.slice.call(LeiArray, 1, 3));
使用 Array.prototype.slice.call() 方法,将类数组对象转换为数组。
将类数组对象转换成数组
slice() 方法经常与 bind() 和 call() 一起使用,创建一个实用方法,将类数组对象转换为数组。
// 调用 slice() 方法时,会将 this 对象作为第一个参数传入
const slice = Function.prototype.call.bind(Array.prototype.slice);
function list() {
return slice(arguments);
}
const list1 = list(1, 2, 3); // [1, 2, 3]
总结
在上述删除数组指定元素的方法中,不会改变数组长度的有filter(),slice();会改变数组长度的有splice(),pop(),shift()。
在循环中使用会改变数组元素的方法要注意
由于方法会改变数组的长度,数组长度发生改变,数组循环使用的是原始数组的i,会造成循环中跳过或者只删除部分符合条件的数组元素,造成最后输出数据有误。可以使用合理的倒序遍历或者注意索引的同步避免此问题的发生。