又在不经意之间看到了一个面试题,应该算是比较久远的了
数组去重
为了培养 JavaScript 的语感,今天就把能想到的方法都梳理一遍
没有编程基础的人的思维
比如说现在有一个数组
var a = [1,2,2,3,3,4,4,5]
我猜按照正常的思维逻辑,肯定会这么想
- 将
a
中的第一项拿出来放到空数组b
里 - 将
a
中的第二项拿出来和数组b
的所有成员比一下,如果有重复的就跳过,如果没有重复的就把这一项加载到b
里去 - 将
a
中的第三项拿出来和数组b
的所有成员比一下,如果有重复的就跳过,如果没有重复的就把这一项加载到b
里去 - 。。。
- 一直到最后一项
好了,接下来我们来将思路码出来
var a = [1,2,2,3,3,4,4,5]
var b = [] // 声明一个空数组
// 以下为伪代码
if(a[0] !== b[0]) { // 一开始 b 并没有成员,所以这条语句的意思就是把 a 的第一项添加到 b 上
b.push(a[0])
}
if(a[1] !== b[0]) {
b.push(a[1])
}
if(a[2] !== b[0] && a[2] !== b[1]){
b.push(a[2])
}
....
能感受到一点规律么 if() {}
这一块可以用 for
循环搞定 而 if
后面的 ()
里的内容可以用另一个 for
循环搞定
赶紧把想法写出来
var a = [1,2,2,3,3,4,4,5]
var b = [] // 声明一个空数组
for(var i = 0; i < a.length; i++) {
var item = a[i]
for(var j = 0; j < b.length; j++) {
if(b[j] !== item) {
// 天哪,写不下去了,我们并不能够用循环模拟出一个 && 的作用啊
}
}
}
代码的思路断了,不过这并不能难倒我,我们可以计数嘛
先初始化一个 k
,只要 b[j] !== item
,k
就加 1
,最后统计一下每过一轮 for(var i...)
的循环后 k
的值就能得知到底有没有重复的了,好聪明
var a = [1,2,2,3,3,4,4,4,4,4,4,5]
var b = [] // 声明一个空数组
for(var i = 0; i < a.length; i++) {
var item = a[i]
var k = 0
for(var j = 0; j < b.length; j++) {
if(b[j] !== item) {
k = k + 1
}
}
if(k === b.length || i === 0) { // 加 || 运算符后面这句话是因为当最外层的循环 i = 0 的时候,内层的循环并不会执行
b.push(item)
}
}
console.log(b) // [1, 2, 3, 4, 5]
好了,这就是数组去重的方法,总结一下思路
- 初始化一个空数组
b
- 逐个将待处理数组
a
的成员与b
数组中的每一个成员比较 - 如果都不相等,才将这个
a
数组里的成员放到b
数组
以上就是符合没有学过编程的人但想要解答这道题的基本思路,不知道有没有认同的
但是我们是程序员啊,不能就这么算了,看看能不能把这段代码再优化优化
我觉得计数太麻烦了,能不能不计数,想一想
这时候,我看到了上面总结的思路的最后一句话
如果都不相等,才将这个
a
数组里的成员放到b
数组
换言之
只要有一个相等,数组
b
就不会被添加新成员
有了思路就马上实施
我们把
if(b[j] !== item) {...}
改成
if(b[j] === item) {...}
如果数组 b
的成员只要有一个和 item
相等,那么就不会 b.push(item)
var a = [1,2,2,3,3,4,4,4,4,4,4,5]
var b = [] // 声明一个空数组
for(var i = 0; i < a.length; i++) {
var item = a[i]
for(var j = 0; j < b.length; j++) {
if(b[j] == item) {
break; // 只要有相等的情况,就跳出这个 for 循环
}
}
if(j === b.length || i === 0) { // 加 || 运算符后面这句话是因为当最外层的循环 i = 0 的时候,内层的循环并不会执行
b.push(item)
}
}
console.log(b) // [1, 2, 3, 4, 5]
是的,比一开始的代码稍微简洁那么一点。。。
先排个序呢
我们知道,怎么给数组排序
var a = [2,2,1,4,4,4,3,3,5]
a.sort(function(a, b) {
return a - b
})
console.log(a) // [1, 2, 2, 3, 3, 4, 4, 4, 5]
对于一个还排序的数组去重的方法,我好像有了一个新思路
从数组的第二个成员开始,与其前一项做对比,如果相等,那么说明有重复,删除之
说干就干!
var a = [2,2,1,4,4,4,3,3,5]
a.sort(function(a, b) {
return a - b
})
for(var i = 1; i < a.length; i++) {
if(a[i] === a[i - 1]) {
a.splice(i, 1)
}
}
console.log(a) // [1, 2, 3, 4, 4, 5]
额,并没有成功,有两个重复的4
这一小段代码很好分析,因为 a.splice(i, 1)
删掉了一个成员,但是 for
循环里面的 i
还是沿用没有删掉成员的值,所以
var a = [2,2,1,4,4,4,3,3,5]
a.sort(function(a, b) {
return a - b
})
for(var i = 1; i < a.length; i++) {
if(a[i] === a[i - 1]) {
a.splice(i, 1)
i -= 1 // 因为删掉了一个成员,成员数就得减1
}
}
console.log(a) // [1, 2, 3, 4, 5]
这个方法有个副作用,就是数组的顺序变了
你听过对象的键名有重复的么
是啊,对象的键名是没有重复的,这给了我新思路
var a = [1, 2, 2, 3, 3, 4, 4, 4, 5]
var b = []
var o = {}
for(var i = 0; i < a.length; i++) {
var item = a[i]
if(!o[item]) {
o[item] = true
b.push(item)
}
}
console.log(b) // [1, 2, 3, 4, 5]
这个方法有个缺陷,他分不清 1
和 "1"
,因为键名会默认转换为字符串
var a = [1, '1', 2, 2, 3, 3, 4, 4, 4, 5]
var b = []
var o = {}
for(var i = 0; i < a.length; i++) {
var item = a[i]
if(!o[item) {
o[item] = true
b.push(item)
}
}
console.log(b) // [1, 2, 3, 4, 5] // 分不清 1 和 '1'
这也有解决的办法
- 如果键名不存在,毫无疑问,这个值是没有重复的
- 如果键名存在,那么利用
typeof
判断一下他的类型 - 如果这个类型之前出现过,不管
- 如果这个类型之前没出现过,就把它放到新数组
试试
var a = [1, '1', 2, 2, 3, 3, 4, 4, 4, 5]
var b = []
var o = {}
for(var i = 0; i < a.length; i++) {
var item = a[i]
var type = typeof a[i]
if(!o[item]) {
o[item] = [type]
b.push(item)
}else if(o[item].indexOf(type) === -1) {
o[item].push(type)
b.push(item)
}
}
console.log(b) // [1, "1", 2, 3, 4, 5] // 现在分得清 1 和 '1' 了
当然了,这方法还是有缺陷,因为
console.log(typeof []) // Object
console.log(typeof {}) // Object
也就是说如果数组里的成员是简单类型的那么好办,对于复杂类型的就无能为力了
先进的 ES5
ES5 新增了 indexOf()
方法,接受两个参数
- 要查找的项
- 查找位置起点的索引
返回查找到的项的位置,如果没找到返回 -1
看完这段介绍我似乎又有思路了
var a = [1, 2, 2, 3, 3, 4, 4, 4, 5]
var b = []
for(var i = 0; i < a.length; i++) {
if(b.indexOf(a[i]) === -1){
b.push(a[i])
}
}
console.log(b) // [1, 2, 3, 4, 5]
搞定
懒惰的 ES6
ES6 提供了新的数据结构 Set
他类似于数组,但是成员的值是唯一的,比如
const set = new Set([1,2,2,3,3,4,4,4,5])
console.log(set) // Set(5) {1, 2, 3, 4, 5}
这就简单了,再把这个数据结构转换成数组就行了么
ES6 又提供了一个新的运算符 ...
,他的其中一个功能就是吧任何类似数组的对象转为真正的数组
const set = new Set([1,2,2,3,3,4,4,4,5])
console.log([...set]) // [1, 2, 3, 4, 5]
好了
程序员的三大美德
Perl 语言的发明人 Larry Wall 说,好的程序员有3种美德:
- 懒惰
- 是这样一种品质,它使得你花大力气去避免消耗过多的精力。它敦促你写出节省体力的程序,同时别人也能利用它们。为此你会写出完善的文档,以免别人问你太多问题。
- 急躁
- 是这样一种愤怒----当你发现计算机懒洋洋地不给出结果。于是你写出更优秀的代码,能尽快真正的解决问题。至少看上去是这样。
- 傲慢
- 极度的自信,使你有信心写出(或维护)别人挑不出毛病的程序。
这篇文章就展现了程序员的其中一个美德
懒惰
(完)
文档信息
- 自由转载-非商用-非衍生-保持署名
- 发表日期:2017年5月17日