JavaScript专题(二)数组去重,会就要会的理直气壮

JavaScript专题(二)数组去重,会就要会的理直气壮相信当部分开发同学都会遇到这个问题,它的答案有很多种,但是想要真的的回答好这个问题却比较难,我们今天来看看应该怎么回答这个问题。本文篇幅较长,不妨收藏下来慢慢阅读~

大家好,又见面了,我是你们的朋友全栈君。

JavaScript专题系列之数组去重

相信当部分开发同学都会遇到这个问题,它的答案有很多种,但是想要真的的回答好这个问题却比较难,我们今天来看看应该怎么回答这个问题。
本文篇幅较长,不妨收藏下来慢慢阅读~

目录

在这里插入图片描述

一、双层循环

const unique = (arr)=>{ 
   
    for(let i = 0; i < arr.length; i++){ 
   
        for(let j = i + 1; j < arr.length; j++){ 
   
            if(arr[i] === arr[j]){ 
   
                arr.splice(j, 1); // 移除重复元素
                j--; // 修正下标
            }
        }
    }
    return arr;
};

const unique = (arr)=>{ 
   
    var arr = [1, '1', '1', 1, 2, true, false, true, 3, 2, 2, 1];
    var newArr = [];
    for(let i = 0; i < arr.length; i++){ 
   
        for(var j = 0; j < newArr.length; j++){ 
    // 注意var声明的j
            if(arr[i] === newArr[j]) break; // 新数组已存在当前值
        }
        if(j === newArr.length){ 
    // 此时j和newArr长度相等,没有被中断
            newArr.push(arr[i]);
        }
    }
    return newArr;
}

unique([1, '1', '1', 1, 2, true, false, true, 3, 2, 2, 1]); // [ 1, '1', 2, true, false, 3 ]

核心点:

  • 时间复杂度:O(n^2)
  • 上面的两种方法都是两次循环遍历,处理方式略有不同
  • 上述实现方式确实不是最佳选择,但它胜在兼容性好啊~

二、indexOf和includes

2.1 indexOf简化一层循环判断

核心点:

  • 如果需要返回原数组,则可以在indexOf方法找到重复项时(不等于它首次出现的位置)时利用splice移除
  • indexOf:返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
  • indexOf(ele, fromIndex)
    • ele:要查找的元素
    • fromIndex:查找元素的起始位置,默认为0,允许负数,-2表示从倒数第二个元素开始查找
    • 返回一个下标(number)

代码:

const unique = (arr) => { 
   
    var res = [];
    for (let i = 0; i < arr.length; i++){ 
   
        if(res.indexOf(arr[i]) === -1 ){ 
   
            res.push(arr[i]);
        }
    }
    return res;
}
2.2 includes简化一层循环判断

核心点:

  • 具体的是要返回原数组还是新数组大家可以自行组合~
  • includes:用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false
  • includes(ele, fromIndex)
    • ele:要查找的元素
    • fromIndex:指定索引处开始查找。默认为 0,如果为负值,从末尾开始往前跳 fromIndex 的绝对值个索引。。
    • 返回结果(bool)

代码:

const unique = (arr) => { 
   
    var res = [];
    for (let i = 0; i < arr.length; i++){ 
   
        if(!res.includes(arr[i])){ 
   
            res.push(arr[i]);
        }
    }
    return res;
}
2.3 indexOf和includes对于当前场景的选择

在这里我们推荐使用includes来查找元素:

返回值可以直接作为if的条件语句,简洁

if(res.indexOf(arr[i]) !== -1 ){ 
    todo }

// or

if(res.includes(arr[i])){ 
    todo }

识别NaN

如果数组中有NaN,你又正好需要判断数组是否有存在NaN,这时你使用indexOf是无法判断的,你必须使用includes这个方法。

var arr = [NaN, NaN];
arr.indexOf(NaN); // -1
arr.includes(NaN); // true

识别undefined

如果数组中有undefined值,includes会认为空的值是undefined,而indexOf不会。

var arr = new Array(3);
console.log(arr.indexOf(undefined)); //-1
console.log(arr.includes(undefined)) //true

三、排序去重

核心点:

  • 数组排序后,相同的元素会相邻,所以如果当前元素与它的相邻元素不同,就存入到新数组中;
  • 相比于indexOf,只需要一层循环;
  • concat会拼接数组,并返回新数组;
  • sort()排序是通过按照转换为的字符串的各个字符的Unicode位点进行排序。所以很难保证它的准确性;

代码:

var arr = [1, 1, '1'];

function unique(arr) { 
   
    var res = [];
    var sortedArr = arr.concat().sort();
    var last;
    for (var i = 0; i < sortedArr.length ; i++) { 
   
        // 如果是第一个元素或者相邻的元素不相同
        if (!i || last !== sortedArr[i]) { 
   
            res.push(sortedArr[i])
        }
        last = sortedArr[i]; // 记录上一个值
    }
    return res;
}

console.log(unique(array));

四、filter

核心点:

  • filter:方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素(返回测试函数成立的元素)
  • filter(callback, thisArg)
    • callback接受三个参数:element-当前正在处理的元素,index-当前元素索引,array-调用了filter的数组本身
    • thisArg:执行 callback 时,用于 this 的值。
      利用filter我们可以在代码层面简化一下外层循环:

代码:

var arr = [1, 2, 1, 1, '1'];

const unique = function (arr) { 
   
    var res = arr.filter(function(item, index, arr){ 
   
        return arr.indexOf(item) === index;
    })
    return res;
}
console.log(unique(arr)); // [ 1, 2, '1' ]

结合排序的思路:

var arr = [1, 2, 1, 1, '1'];

const unique = function (arr) { 
   
    return arr.concat().sort().filter(function(item, index, arr){ 
   
        return !index || item !== arr[index - 1]
    })
}

console.log(unique(arr));

五、键值对(key-value)

前面提到的几种方式总结下来大致分为

  1. 非排序数组,两次遍历判断(遍历、查询)
  2. 排序数组,相邻元素比较

我们再提出一种方式,利用Object对象 key-value的方式,来统计数组中元素出现的个数,初步判断逻辑也有两种

[1,1,1,2,2,3,'3']举例:

  1. 统计每个元素出现的次数,obj:{1: 3, 2: 2, 3: 3}, 返回这个objkey而不管他们的value
  2. 只元素首次出现,再次出现则证明他是重复元素
5.1 统计次数
var arr = [1, 2, 1, 1, '1', 3, 3];

const unique = function(arr) { 
   
    var obj = { 
   };
    var res = [];
    arr.forEach(item => { 
   
        if (!obj[item]) { 
   
            obj[item] = true;
            res.push(item);
        }
    });
    return res;
}
console.log(unique(arr)); // [1, 2, 3]
5.2 结合filter
var arr = [1, 2, 1, 1, '1'];

const unique = function(arr) { 
   
    var obj = { 
   };
    return arr.filter(function(item, index, arr){ 
   
        return obj.hasOwnProperty(item) ? false : (obj[item] = true)
    })
}
console.log(unique(arr)); // [1, 2]
5.3 key: value存在的问题

对象的属性是字符串类型的,即本身数字1字符串‘1’是不同的,但保存到对象中时会发生隐式类型转换,导致去重存在一定的隐患。

考虑到string和number的区别(typeof 1 === ‘number’, typeof ‘1’ === ‘string’),

所以我们可以使用 typeof item + item 拼成字符串作为 key 值来避免这个问题:

var arr = [1, 2, 1, 1, '1', 3, 3, '2'];

const unique = function(arr) { 
   
    var obj = { 
   };
    var res = [];
    arr.forEach(item => { 
   
        if (!obj[typeof item + item]) { 
   
            obj[typeof item + item] = true;
            res.push(item);
        }
    });
    return res;
}
console.log(unique(arr)); // [ 1, 2, '1', 3, '2' ]

六、ES6

随着 ES6 的到来,去重的方法又有了进展,比如我们可以使用 Set 和 Map 数据结构。

6.1 Set

Set:它允许你存储任何类型的唯一值,无论是原始值或者是对象引用

代码:

var arr = [1, 2, 1, '1', '2'];

const unique = function(arr) { 
   
   return Array.from(new Set(arr));
}

console.log(unique(arr)); // [ 1, 2, '1', '2' ]

简化1:

function unique(array) { 
   
    return [...new Set(array)];
}

简化2:

var unique = (a) => [...new Set(a)]
6.2 Map

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。

  • Map.prototype.has(key):返回一个布尔值,表示Map实例是否包含键对应的值。
  • Map.prototype.set(key, value):设置Map对象中键的值。返回该Map对象。
function unique (arr) { 
   
    const newMap = new Map()
    return arr.filter((a) => !newMap.has(a) && newMap.set(a, 1));
}

写到这里比较常规的数组去重方法就总结的差不多了,如果需要更强大的去重方法,我们需要对他们进行组合,而且因为场景的不同,我们所实现的方法并不一定能涵盖到

一些问题

上面几个去重的方法,在针对数字、字符串、特殊字符、undefined、NaN的表现各不相同。

1 === 1 // true
undefined === undefined // true
'str' === 'str' // true
1 === '1' // false
NaN === NaN // false

这还不算数组保存多个对象时的去重,所以场景不同我们的选择不同,加油~

参考

写在最后

JavaScript系列:

  1. 《JavaScript内功进阶系列》(已完结)
  2. 《JavaScript专项系列》(持续更新)

关于我

  • 花名:余光(沉迷JS,虚心学习中)
  • WX:j565017805

其他沉淀

如果您看到了最后,对文章有任何建议,都可以在评论区留言~

如果真的对您有所帮助,也希望您能本系列的GitHub仓库,传送门点个star,这是对我最大的鼓励 !

JavaScript专题(二)数组去重,会就要会的理直气壮

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148661.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 基于VUE + Echarts 实现可视化数据大屏展示效果[通俗易懂]

    基于VUE + Echarts 实现可视化数据大屏展示效果[通俗易懂]中国(寿光)国际蔬菜科技博览会智慧农业系统—LED拼接屏展示前端开发文档上线后呈现效果:一、开发需求及方案制定1、确定现场led拼接屏的宽高比,按照1920px*1080px的分辨率,F11全屏后刚好占满整屏且无滚动条;2、与产品设计确定页面相关功能:第一屏相关功能:实时时间、当地天气、菜博会基本信息、图表数据统计(近三日人流量、…

  • 〖免杀〗.net程序一键免杀Win10 20H2 Defender「建议收藏」

    〖免杀〗.net程序一键免杀Win10 20H2 Defender「建议收藏」WIN10更新至最新版20H2发现,查杀能力比以前强了不少,特别是针对CS加载.NET程序集或NIM加载.NET的查杀,毕竟你要调用的函数微软很了解,它想拦截想杀还是比较容易的。但是不知道大家有没听说过一个故事“微软的编程工具的开发工程师,编写程序时,也需要查阅文档”,说明什么?微软系统的API很多,是微软写的没错,但又不是一个人自己写的,就算是一个人自己写的,功能那么多,他也根本记不起,用到自己的东西也要查阅文档,就像我用Ladon有时也要查阅文档,这很正常,因为我写过的工具或功能太多,有些久不用,甚至都

  • 【图像处理】详解 最近邻插值、线性插值、双线性插值、双三次插值「建议收藏」

    【图像处理】详解 最近邻插值、线性插值、双线性插值、双三次插值「建议收藏」插值(Interpolation),通常指内插,既是离散数学名词,也是图像处理术语,二者的联系十分密切。作为图像放缩(Scale)的手段,常见的传统插值方法有:最近邻插值(NearestNeighbourInterpolation)、线性插值(LinearInterpolation)、双线性插值(BilinearInterpolation)、双三次插值(Bicubicinterpolation)等乃至更高阶的线性插值方法。

  • es6模板字符串里用html标签,为ES6模板字符串计算标签函数[通俗易懂]

    es6模板字符串里用html标签,为ES6模板字符串计算标签函数[通俗易懂]Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。这篇博客描述了你可以通过函数为ES6模板字符串做些什么从而获取返回值。对于一篇针对模板字符串的介绍来说,标记的模板字符串和函数需要在《探索ES6》中查询模板字符串章节1.通过模板字符串获取返回值在JavaScript中获取一个值最普遍的方法就是在括号中加上参数。在ES6中,你可以通过模板字符串更多地获取返回…

  • linux下 opencv 和 ffmpeg安装「建议收藏」

    linux下 opencv 和 ffmpeg安装「建议收藏」1、要先安装ffpmeg下载地址http://www.ffmpeg.org/download.html这里选择版本3.4.8直接下载包上传或者linux直接下载wgethttp://www.ffmpeg.org/releases/ffmpeg-3.4.8.tar.gz解压当前包tar-zxvfffmpeg-3.4.8.tar.gzcdffmpeg-3.4.8/安装ffmpeg过程中,执行./configure时,报yasm/nasmnotfoundortooold.U

  • ov7725摄像头人脸识别_图像采集是干什么的

    ov7725摄像头人脸识别_图像采集是干什么的目前FPGA用于图像采集传输处理显示应用越来越多,主要原因是图像处理领域的火热以及FPGA强大的并行处理能力。本文以OV7725为例,对摄像头使用方面的基础知识做个小的总结,为后续做个铺垫。  XCLK:工作时钟输入,由主控器产生,频率为24MHz;    HREF:行参考信号输出;  PCLK:像素时钟输出,由XCLK产生,用于控制器采样图像数据(时钟速率&amp;lt;5n…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号