前端面试题:高效地随机选取数组中的元素
有前端题目大概是这样的:考虑到性能问题,如何快速从一个巨大的数组中随机获取部分元素。
比如有个数组有100K个元素,从中不重复随机选取10K个元素。
为了演示方便我们将数据简化,先给出方案最后再用大点的数据来测试性能的对比。
常规解法
常规做法倒也不难,生成一个0到数组长度减1的随机数,这个数也就是被选中元素在原数组中的下标,获得该元素后将值保存到另一个数组同时通过数组的splice方法将该元素从原数组中删除,以保证下次不会重复取到。
按以上思路,代码大概就是这样的:
//元素总数,为了方便演示这里取个小一点的数目比如5,表示总共5个元素 var TOTAL_NUM = 5,//要取得的个数,表示我们要从原数组中随机取3个元素COUNT = 3,//用随机字符串初始化原数组arr = new Array(TOTAL_NUM + 1).join('0').split('').map(function() {return Math.random().toString(36).substr(2);}),//保存结果的数组result = [];console.log('原数组:', arr);//开始我们的选取过程 for (var i = COUNT - 1; i >= 0; i--) {//从原数组中随机取一个元素出来var index = Math.floor(Math.random() * arr.length);//压入结果数组result.push(arr[index]);//将该元素从原数组中删除arr.splice(index, 1); }; console.log('结果数组:', result);
运行结果如下图:
当然上面例子中为了便于演示,将题目要求的100 000 大数目简化为总数为5,同时只取3个。
由测试结果看这种做法是完全可行的。
但存在一个问题:为了下次随机时不重复选取已经选择过的元素,我们将选择过的元素从原数组中通过splice方法进行删除,但这个splice方法操作的过程本身就是数组重新维护其元素索引的过程,这意味着被选择的元素之后的所有元素需要前移一个位置来重新生成一个紧凑的数组,可以想象如果我们取走了原数组中的第1个元素,那么之后的99 999个元素都需要发生变动来完成重组数组的操作,无疑有点耗时。
利用洗牌算法
另一个思路可以是这样的,既然要随机选取,那我可以先把数组的元素打乱先,然后要多少就从开始取多少就行了。一提到随机,自然想到洗牌算法,而关于洗牌算法已经有一个非常经典且高效的Fisher-Yates算法了,这个算法我之前有写过一篇博客介绍过。
这个想法较之前的方法有点逆行的感觉,前面着重点是随机,所以每次都产生一个随机下标到原数组去取,现在是先将数组元素随机打乱,再去正常取。由于洗牌算法非常高效且省去了数组的重组,较之前性能应该有所提升。
照这个思路最后实现的代码大概就是这个样子的:
//元素总数,为了方便演示这里取个小一点的数目比如5,表示总共5个元素 var TOTAL_NUM = 5,//要取得的个数,表示我们要从原数组中随机取3个元素COUNT = 3,//用随机字符串初始化原数组arr = new Array(TOTAL_NUM + 1).join('0').split('').map(function() {return Math.random().toString(36).substr(2);}),//保存结果的数组result = [];console.log('原数组:', arr); //随机化原数组 arr = shuffle(arr); //选取元素 result = arr.slice(0, COUNT); console.log('结果数组:', result);function shuffle(array) {var m = array.length,t, i;// 如果还剩有元素…while (m) {// 随机选取一个元素…i = Math.floor(Math.random() * m--);// 与当前元素进行交换t = array[m];array[m] = array[i];array[i] = t;}return array; }
上面代码中包含了经典的洗牌算法Fisher-Yates Shuffle算法,即shuffle函数。具体可参见我的另一篇博客。
运行结果:
从结果来看,此种方法也是可行的。
细想还是存在问题,对于一个比较大的数组来说,不管你的洗牌算法多么高效(即使上面Fisher-Yates算法时间复杂度为O(n)),要随机整个数组也还是很庞大的工程的吧。
所以对于这个题目的探索还没有完。当我在stackoverflow上面发问后,虽然没得到什么惊人的回答,但有个回答却提醒我可以将上面的方法再次改进。
只取所需
那就是我们没有必要随机掉整个数组,在我们取完需要数量的元素后,可以将Fisher-Yates乱序方法中止掉!
思路是非常明显的了, 这样可以省下不少无意义的操作。
所以最后的实现大概成了这样子:
//元素总数,为了方便演示这里取个小一点的数目比如5,表示总共5个元素 var TOTAL_NUM = 5,//要取得的个数,表示我们要从原数组中随机取3个元素COUNT = 3,//用随机字符串初始化原数组arr = new Array(TOTAL_NUM + 1).join('0').split('').map(function() {return Math.random().toString(36).substr(2);}),//保存结果的数组result = [];console.log('原数组:', arr);//此段代码由Fisher-Yates shuflle算法更改而来 var m = arr.length,t, i; while (m && result.length < COUNT) {// 随机选取一个元素…i = Math.floor(Math.random() * m--);t = arr[m];arr[m] = arr[i];arr[i] = t;result.push(arr[m]); }console.log('结果数组:', result);
上面代码将Fisher-Yates算法略做修改,在取得满足要求的元素之后便停止了,所以较前面的做法更加科学。
运行结果:
性能比较
最后给出上面三个方法耗时的比较,这里将需要操作的数组元素个数回归到题目中要求的100 000来。
下图是jsperf上运行测试的结果,详情可点测试页面重新运行。数值越大越好。由上到下依次是本文中介绍的三种方法。
总结
目前PO主只能想到这些,更优的做法还有待进一步探究。
REFERNCE
由乱序播放说开了去-数组的打乱算法Fisher–Yates Shuffle http://www.cnblogs.com/Wayou/p/fisher_yates_shuffle.html
转载于:https://www.cnblogs.com/Wayou/p/get_random_subset_from_an_array.html
前端面试题:高效地随机选取数组中的元素相关推荐
- 前端面试题 回顾与复习(更新中)
还没有完全整理好 希望大家见谅 后面逐步优化 原生DOM操作 如需替换 HTML DOM 中的元素,请使用replaceChild(newnode,oldnode)方法 从父元素中删除子元素 pare ...
- 2021-2022 最新最全的前端面试题集锦(2022 持续更新中...敬请关注)
写在最前 想起上一次的面试经历,不免细思极恐. 工作以来,感觉自己接触到的东西还是挺多的,但是当我面试的时候,就会发现各种问题,很多知识点都是模棱两可,答得不全面.究其原因,还是没有吃透这些内容.所以 ...
- 前端面试题(附答案)完善中……
前端面试笔记 前言 一.HTML篇 1.语义话的目的是什么?⭐ 2.HTML5新特征⭐⭐⭐ 3.cookie与sessionStorage和localStorage的区别⭐⭐⭐ 二.CSS篇 1.cs ...
- php 随机 数据元素,php随机从数组中抽取元素的方法
这篇文章主要介绍了php从数组中随机选择若干不重复元素的方法,涉及php数组操作的相关常用技巧,非常具有实用价值,需要的朋友可以参考下 本文实例讲述了php从数组中随机选择若干不重复元素的方法.具体实 ...
- 关于随机输出数组中所有元素的三种算法
算法一:比较常见,也比较容易想到.缺点:如果arrA中有重复元素,那么重复的元素只会输出一次. int[] arrA={1,2,3,4,5,6}; int[] arrB=new int[arrA.le ...
- array 前端面试题_一则关于js数组的前端面试题
假设,有一个数组为[1,2,3,4,5]:再给定一个数字如7,将数组间的值进行加法运算,怎么求出最后结果为[2,5],[3,4],[1,2,4]?const sumArray = arr => ...
- 前端面试题----Ajax(随机排序)
1.什么是ajax?ajax作用是什么? AJAX = 异步 JavaScript 和 XML. AJAX 是一种用于创建快速动态网页的技术. 通过在后台与服务器进行少量数据交换,AJAX 可以使网页 ...
- 前端面试题合集(持续更新中......)
HTML.CSS 1.元素水平垂直居中 • 定位偏移 top,left为50%,margin-left,margin-top自身的-50% • 定位平移 top,left,bottom,right为0 ...
- 前端面试题总结以及vue在工作中常见的错误
<!-- js部分:1.JavaScript this 指针.闭包.作用域 this:指向调用上下文 闭包:内层作用域可以访问外层作用域的变量闭包:内层作用域可以访问外层作用域的变量 作用域:定 ...
- 带权重的随机输出数组中的元素
$orignial_arr= array("一"=>10,"二"=>5,"三"=>3,"四"=> ...
最新文章
- 为何人工智能首推Python 初学者怎么学Python
- css3选择器的比较(二) -- 包含字符串
- Oracle9在Windows7下的安装
- 某省高职比赛试题(园区网互联)
- 枚举枚举和修改“最终静态”字段的方法
- windows mobile 编译(生成镜像)提速
- Python爬虫的经典多线程方式,生产者与消费者模型
- 【CCF】201903-2 二十四点
- 网络请求以及网络请求下载图片的工具类 android开发java工具类
- CPU实时人脸检测,各种朝向、侧脸都检出来
- Windows引导及安装
- 用 JNI 进行 Java 编程(1)
- [转]coolfire黑客入门教程系列之(三)
- 一文熟练使用spring data jpa
- 注册中心原理和选型:Zookeeper、Eureka、Nacos、Consul和Etcd
- 如何在Visual Studio中自动格式化代码?
- 聊一聊世界杯里的数学知识
- 要报销的发票如何批量下载打印
- PCIe设备驱动demo
- Centos7 Gitlab版本升级过程
热门文章
- 卷积神经网络第三周作业 Autonomous driving application - Car detection - v1
- 训练和测试自己的图像集
- python四种方法实现去除列表中的重复元素
- Mat 创建图像的理解
- python验证身份证最后一位数字代表什么_身份证尾数带X的人,是有什么特殊身份吗?看完涨知识了...
- latex 引用硕士论文、博士论文 bibtex格式
- 以太坊 ERC-20 ERC-721 ERC-1155区别对比
- lock free(无锁并发)是什么
- 以太坊 智能合约 代码 数据空间 存储在哪儿
- Java中递归复制多级文件夹(IO流)