在具有多个出现次数的数组中循环删除一个元素

23

我想用一个函数从一个包含多个重复元素的数组中删除一个元素。

var array=["hello","hello","world",1,"world"];

function removeItem(item){
    for(i in array){
        if(array[i]==item) array.splice(i,1);
    }
}
removeItem("world");
//Return hello,hello,1
removeItem("hello");
//Return hello,world,1,world

这个循环在连续重复两次时不会移除其中一个元素,只会移除其中一个。

为什么?


这个能用吗?变量i将不等于数组的索引,而只是变量数组的成员。 - Captain John
11个回答

38

你有一个内置函数叫做 filter,它可以根据谓词(即条件)过滤数组。

它不会改变原始的数组,但是返回一个新的已被过滤的数组。

var array=["hello","hello","world",1,"world"];
var filtered = array.filter(function(element) {
    return element !== "hello";
}); // filtered contains no occurrences of hello
你可以将它提取为一个函数:
function without(array, what){
    return array.filter(function(element){ 
        return element !== what;
    });
}

然而,原来的过滤器似乎已经足够表达了。

这里是它的文档链接

您原来的函数存在几个问题:

  • 它使用了 for... in 循环遍历数组,但该循环无法保证迭代顺序。此外,请勿使用该循环来遍历数组 - 应该选择普通的 for... 循环或者 .forEach
  • 您在遍历数组时出现了偏移一位的错误,因此会跳过下一个元素,因为您同时删除了该元素并前进到了下一个位置。

1
@EduenSarceno 是的,你总是可以使用 array = array.filter 并将其重新赋值 - 这是完全可以的。通常你需要一个副本。 - Benjamin Gruenbaum

7
那是因为 for 循环在删除元素后会跳过下一个元素,从而跳过直接在其后面的元素。

例如,假设需要从此数组中删除 item1(注意,<- 是循环的索引):

item1 (<-), item2, item3

删除后:
item2 (<-), item3

在索引更新后(因为循环已完成)

item2, item3 (<-)

所以你可以看到item2被跳过了,因此没有被检查!因此,您需要通过手动将索引减1来进行补偿,如下所示:
function removeItem(item){
    for(var i = 0; i < array.length; i++){
        if(array[i]==item) {
            array.splice(i,1);
            i--; // Prevent skipping an item
        }
    }
}

不必使用此 for 循环,你可以使用更“现代”的方法来过滤掉不需要的项,如Benjamin 的其他答案所示


感谢您的解释,使用循环递减不会跳过其他项目。谢谢! - Eduen Sarceno

2
这些答案都不是很理想。使用过滤器的被接受的答案会导致一个新的数组实例。得票第二多的那个使用splice并且每次都向后迭代的for循环是不必要复杂的。
如果你想使用for循环的方式,只需反向计数到0即可。
for (var i = array.length - 0; i >= 0; i--) {
  if (array[i] === item) {
    array.splice(i, 1);
  }
}

然而,我使用了一种出乎意料的快速方法,使用while循环和indexOf函数:
var itemIndex = 0;
while ((itemIndex = valuesArray.indexOf(findItem, itemIndex)) > -1) {
  valuesArray.splice(itemIndex, 1);
}

这种方法不重复的原因在于,在任何一个元素被删除后,下一次搜索将从已删除项目之后的下一个元素的索引开始。这是因为可以将起始索引作为第二个参数传递给indexOf
在一个比较上述两种方法和接受的过滤方法的jsPerf测试案例中,indexOf在Firefox和Chrome上通常最先完成,而在IE上排名第二。过滤方法总是慢得多。
结论:无论是反向循环还是使用indexOf的while循环,都是我能找到的从数组中删除多个相同元素的最佳方法。使用filter会创建一个新的数组,速度较慢,因此我会避免使用它。

1

在这种情况下,您可以使用loadash或underscore js。如果arr是一个数组,您可以通过以下方式删除重复项:

var arr = [2,3,4,4,5,5];

arr = _.uniq(arr);

0

我需要稍微改变一下这个,从数组中删除“n”个项目的能力,所以我修改了@Veger的答案如下:

function removeArrayItemNTimes(arr,toRemove,times){
    times = times || 10;
    for(var i = 0; i < arr.length; i++){
        if(arr[i]==toRemove) {
            arr.splice(i,1);
            i--; // Prevent skipping an item
            times--;
            if (times<=0) break;
        }
    }
    return arr;
}

0
您可以使用以下代码段来删除数组arr中多个出现的值val
while(arr.indexOf(val)!=-1){
  arr.splice(arr.indexOf(val), 1);
}

0
另一种方法是对数组进行排序,然后通过调整值的索引来实现。
function(arr) {
    var sortedArray = arr.sort();
    //In case of numbers, you can use arr.sort(function(a,b) {return a - b;})
    for (var i = 0; sortedArray.length; i++) {
        if (sortedArray.indexOf(sortedArray[i]) === sortedArray.lastIndexOf(sortedArray[i]))
            continue;
        else
            sortedArray.splice(sortedArray.indexOf(sortedArray[i]), (sortedArray.lastIndexOf(sortedArray[i]) - sortedArray.indexOf(sortedArray[i])));
    }
}

0

尝试手动运行您的代码 - "hello"紧随其后。如果您删除第一个,数组将缩小一个项目,现在您拥有的索引将跟随下一个项目。

删除“hello”

开始循环。i = 0,array = ["hello",“hello”,“world”,1,“world”],i指向“hello” 删除第一个项目,i = 0,array = ["hello",“world”,1,“world”] 下一个循环,i = 1,array = ["hello",“world”,1,“world”]。第二个“hello”将不会被删除。

让我们看看“world”= i = 2,指向“world”(删除)。在下一次循环中,数组为: ["hello",“hello”,1,“world”],i = 3。这里去了第二个“world”。

您希望发生什么?您想删除该项的所有实例吗?还是只有第一个?对于第一种情况,删除应该在

while (array[i] == item) array.splice(i,1);

对于第二种情况 - 一旦你移除了项目,就立即返回。


0
创建一个集合,给定一个数组,原始数组不会被修改。

演示在Fiddle上

    var array=["hello","hello","world",1,"world"];

    function removeDups(items) {
        var i,
            setObj = {},
            setArray = [];
        for (i = 0; i < items.length; i += 1) {
            if (!setObj.hasOwnProperty(items[i])) {
                setArray.push(items[i]);
                setObj[items[i]] = true;
            }
        }
        return setArray;
    }

    console.log(removeDups(array)); // ["hello", "world", 1]

0

我必须说,我的方法不使用splice功能,而且您需要另一个数组来解决这个问题。

首先,我猜你循环数组的方式不正确。您正在使用适用于对象而非数组的for in循环。如果您使用jQuery,则最好使用$.each,如果您使用vanila Javascript,则最好使用Array.prototype.forEach

其次,为什么不创建一个新的空数组,遍历它并仅将唯一元素添加到新数组中,像这样:

第一种方法(jQuery)

 var newArray = [];
 $.each(array, function(i, element) {
        if ($.inArray(element, newArray) === -1) {
            newArray.push(region);
        }
 });

第二种方法(纯JavaScript)

var newArray = [];
array.forEach(function(i, element) {
  if (newArray.indexOf(element) === -1) {
            newArray.push(region);
  }
});

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