JavaScript代码的无限循环

3
我正在编写一个函数,它接受一个值的数组并返回只包含唯一值的数组。例如:
var strings = ["audi", "audi", "bmw", "bmw","bmw","bmw","audi","audi", "8-()"];

结果应该是:

alert( unique(strings) ); // audi, bmw, 8-()

我不明白为什么我的函数进入了无限循环,有人能帮忙吗? 这是函数:

function unique(arr) {
   var result = [];
   result.push(arr[0]);

   for (var i = 1; i < arr.length; i++) {
       for (var j = 0; j < result.length; j++) {
           if  (result[j] != arr[i]) {
              result.push(arr[i]); 
           }
       }
   }

   return result;
} 

1
result.push(arr[i]) 会使 result.length 增加1。 - Pointy
你尝试过使用调试器逐步执行代码,或在循环中添加一些 console.log() 语句吗? - nnnnnn
@Pointy 不会自动更新它推送到的数组长度吗? - qazerty23
1
当我运行上述代码时,并没有出现无限循环,但逻辑是有问题的。如果当前的arr[i]项与result中的第一个值不相等,则将其添加到result中,即使它可能等于result中后面的某个值。如果它不等于任何现有的result值,则会添加多次... - nnnnnn
1
尽管有很多答案只是展示代码,但一定要分析自己的操作,以便理解出错的原因以及如何在原始方法的基础上进行修复。理解这些问题非常重要。 - user1106925
显示剩余5条评论
9个回答

7

您可以使用ES6中的Set扩展操作符来简化它。

var unique = src => [...new Set(src)]

顺便说一下,你的逻辑是错误的。它不会进入无限循环。但它会给你一个不可取的结果。


6

无需嵌套循环。你可以使用Array.prototype.indexOfArray.prototype.includes方法检查可用值的结果。

var strings = ["audi", "audi", "bmw", "bmw", "bmw", "bmw", "audi", "audi", "8-()"];

function unique(arr) {
  var result = [];
  result.push(arr[0]);

  for (var i = 1; i < arr.length; i++) {
    if (result.indexOf(arr[i]) === -1)
      result.push(arr[i]);


  }
  return result;
}
console.log(unique(strings))

顺便提一下,厨师建议是:

var strings = ["audi", "audi", "bmw", "bmw", "bmw", "bmw", "audi", "audi", "8-()"];
console.clear();

var result = strings.filter(function(s){ return this[s] ? false : (this[s] = true); }, Object.create(null))

console.log(result);


1
箭头函数中没有 this - Nina Scholz
1
这仍然看起来像一个嵌套循环,假设indexOf()是使用内部循环构建的,这意味着速度为O(N)^2(for循环乘以indexOf循环)。 - specimen
@specimen 你说的第一个是正确的,它可以用来提高OP实现的性能。 - Morteza Tourani

4
你可以简化它:使用Array.reduce

var values = [1,2,3,4,5,5,5,5,5,6,6,6,62,2,2]

function getUniq(array) {
  return array.reduce(function(u, value) {
    if (u.indexOf(value) < 0) u.push(value)
    return u;
  }, [])
}

console.log(getUniq(values))


4
您可以减少此操作的时间复杂度。
var strings = ["audi", "audi", "bmw", "bmw","bmw","bmw","audi","audi", "8-()"];

var unique = [];

strings.forEach(function(str){
  if (unique.indexOf(str) < 0) {
     unique.push(str)
  }
});

return unique;

1
.indexOf() 操作仍然是线性 (O(n)) 操作。 - Pointy
我们可以在这里使用对象。 - Piyush.kapoor
正确的,一个对象或者(使用ES2015)一个Set实例。 - Pointy

3
您可以使用 Set 作为一种工具。
var strings = ["audi", "audi", "bmw", "bmw","bmw","bmw","audi","audi", "8-()"];
var uniq = new Set(strings);

// if you need in array 
uniq = Array.from(uniq);

2
你可以稍微改变内部循环,并使用一个名为found的变量来检查该值是否在结果数组中。同时,需要使用这个变量来检查是否将该值推送到结果集中。此外,如果找到重复的值,你可以使用break退出内部循环。

function unique(arr) {
    var result = [],
        found;

    result.push(arr[0]);
    for (var i = 1; i < arr.length; i++) {
        found = false;                            // initial set to false
        for (var j = 0; j < result.length; j++) {
            if (result[j] === arr[i]) {
                found = true;                     // set to true to skip push
                break;                            // exit loop
            }
        }
        found || result.push(arr[i]);             // push only, if not found
    }
    return result;
}

var strings = ["audi", "audi", "bmw", "bmw", "bmw", "bmw", "audi", "audi", "8-()"];

console.log(unique(strings)); // audi, bmw, 8-()

一个更简短的ES6提案,使用Array#filter和哈希表作为闭包。

function unique(arr) {
    return arr.filter((temp => a => !temp[a] && (temp[a] = true))(Object.create(null)));
}

var strings = ["audi", "audi", "bmw", "bmw", "bmw", "bmw", "audi", "audi", "8-()"];

console.log(unique(strings)); // audi, bmw, 8-()


我没有看到你的回答。有时候检查一下自己的答案是很好的习惯。 - Morteza Tourani
哪个更好?是中断循环还是将找到的元素添加到循环的第二个语句中?或者为了简单起见,使用以下代码:for( var j = 0; j < result.length && ( found = result[j] === arr[i] ) ; j++); - Morteza Tourani
1
@mortezaT,这取决于情况,但使用break并不是错误的。我认为在有意义的地方使用所有语言特性是好的。 - Nina Scholz
1
没问题,这个没有任何问题。我看到的每个地方都说最好不要使用jumpbreak,因为这样更易读。 - Morteza Tourani

2
您也可以使用字典/哈希表来完成此操作。这样,循环遍历arr一次的总运行时间应为O(N):
function unique(arr) {
    unique = {}
    for(var i=0; i<arr.length; i++) {
        unique[arr[i]]=true
    }
    return Object.keys(unique)
}   

JSFiddle


0

另一个最快的方法是,只需2毫秒就可以检查性能

var dupEleArr = [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 20, 3, 3, 3, 32, 324, 5, 52];
var uniqueArr = dupEleArr.sort(function(a,b){
    return a-b;
}).filter(function(ele,i,arr){
   return arr.length > 3 ? arr[i] != arr[i+1] : (arr.length == 2) ? (arr[i] != arr[i+1]) : true;
});

检查 jsfiddle


使用字典至少快10倍。我尝试了你的jsfiddle,使用更大的数据集:https://jsfiddle.net/49srtxpp/1/。你的解决方案需要30毫秒,而使用字典只需要3毫秒。请看我的回答。顺便说一句:我之所以评论这个问题,是因为你把它呈现为“最快的方法”。 - specimen
嘿,谢谢@specimen,因为你,我学会了最快的技巧...在这里看 https://jsfiddle.net/2en1aejr/ 我用了三种方法,但非常感谢你。 - Nageshwar Reddy Pandem

0

push() 将一个新对象推到数组的开头,并将其他所有元素向后移动!

让我们在程序中查看您的数组状态:

arr = [A,B]    result = [A]    i = 1

j = 0 -> arr[i] = B and result[j] = A    
so we use push: result = [B,A] and j++

j = 1 -> arr[i] = B and result[j] = A
so we use push: result = [B,B,A] and j++

j = 2 -> arr[i] = B and result[j] = A
so we use push: result = [B,B,B,A] and j++

你的数组不断增加,检查永远不会失败,因为你将新元素添加到数组的开头而不是结尾。

但即使你将新项目附加到末尾,你也不会得到唯一的结果,因为你必须检查结果中的每个元素,并且只有在没有任何结果相同时,才应将新元素添加到结果数组中。

要获得更好的解决方案,请参见其他答案-> 使用contains()Set


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