JavaScript中splice方法的奇怪bug

6

我有一个包含“零”的数组,我想把所有的“零”移动到数组的最后。

期望的输出是:

[1,2,3,0,0,0,0]

但实际上我得到了:

[1,2,0,3,0,0,0]

let a = [0, 1, 2, 0, 0, 3, 0];
let count = 0;
let len = a.length;

for (i = 0; i < len; i++) {
  if (a[i] == 0) {
    count = count + 1;
    a.splice(i, 1);
  }
}

for (j = 0; j < count; j++) {
  a.push(0);
}

console.log(a);


1
这个标题似乎暗示你在一个本地JavaScript方法中发现了一个错误。压制这种想法。让自己相信有错误的不是那个方法,而是你的代码。 - trincot
7个回答

9

当你从数组中删除一个元素时,所有的元素都会向下移动一位。当你使索引前进(i++),你跳过了在数组中被移动的元素,它恰好是数组中连续的零。

解决方案:倒序遍历 for-next 循环即可。


3
因为splice改变了数组的长度,所以你可以从数组的末尾开始迭代,并将找到的值直接拼接到最后一个索引。
采用这种方法,只需要一次循环即可。

var a = [0, 1, 2, 0, 0, 3, 0],
    i = a.length;

while (i--) {
    if (a[i] === 0) {
        a.splice(a.length, 0, ...a.splice(i, 1));
    }
}

console.log(a);

一种更简短的方法,无需拼接,并从零开始。

var a = [0, 1, 2, 0, 0, 3, 0],
    i, j = 0;

for (i = 0; i < a.length; i++) {
    if (a[i] !== 0) {
        [a[j], a[i]] = [a[i], a[j]]; // swap
        j++;
    }        
}

console.log(a);


1
在for循环中,当你切割数组时,数组和它的长度会改变。因此,你必须通过减去1来修复for循环中的i。
  i++;

并通过减去1或重新获取长度来修复长度。

let a = [0, 1, 2, 0, 0, 3, 0];
let count = 0;
let len = a.length;

for (i = 0; i < len; i++) {
  if (a[i] == 0) {
    count = count + 1;
    a.splice(i, 1);
    len = a.length;
    i--;
  }
}

for (j = 0; j < count; j++) {
  a.push(0);
}

console.log(a);


1

你可以使用 Array.prototype.sort() 更简单地完成它:

const array = [0, 1, 2, 0, 0, 3, 0];
const sortedArray = array.sort((a, b) => {
  if (a === 0) {
    return 1;
  }
  if (b === 0) {
    return -1;
  }
  return a - b;
});

console.log(sortedArray);


1
为什么要用O(n log n)的方法做,我们可以轻松地用O(n)的方式实现呢? - גלעד ברקן
@גלעדברקן 我认为我们还需要对数组进行排序。 - sergdenisov

0
你可以在使用 splice 的时候每次添加 i--;len--;
let a = [0, 1, 2, 0, 0, 3, 0];
let count = 0;
let len = a.length;

for (i = 0; i < len; i++) {
  if (a[i] == 0) {
    count = count + 1;
    a.splice(i, 1);
    i--; len--;
  }
}

for (j = 0; j < count; j++) {
  a.push(0);
}

console.log(a);

这是因为当你切割一个元素时,数组的键会向下移动一个位置,所以你想要检查的下一个元素的键与你刚刚删除的元素相同。由于我们刚刚删除了一个元素,所以 len--; 也需要进行修正。
虽然这个答案是使用你原来的计划正确的方法,但它有点像一个补丁。你的问题是在循环数组时,数组会在循环过程中失去元素,通常在这种情况下的正确方法是反向循环。这样,那些可能在循环过程中键值发生变化的元素就是我们已经检查过的元素。

0
请注意,每次调用 splice 的复杂度通常为 O(n)。有很多方法可以通过单个 O(n) 迭代以更高效的方式实现您想要的结果。以下是其中一种方法:

let a = [0, 1, 2, 0, 0, 3, 0]

for (let i=0, j=0; j<a.length; j++)
  if (a[j] && i != j)
    [a[i++], a[j]] = [a[j], 0]

console.log(a)


0

不要一遍又一遍地拼接数组,这里有一个不同的方法:

let a = [0, 1, 2, 0, 0, 3, 0];
// create some more (random) data
for (let i = a.length; i < 30; ++i)
  a[i] = Math.floor(Math.random() * Math.random() * 10);
console.log(""+a);

let i = 0, j = 0, len = a.length;
// move non-0 values to the front
while (i < len) {
  if (a[i] !== 0) {
    a[j++] = a[i];
  }
  ++i;
}
// fill the end of the list with 0
while (j < len) a[j++] = 0;

console.log(""+a);


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