最近作一个项目中作一个竞猜游戏界面,游戏规则和彩票是同样的。在实现“机选一注”,“机选五注”的时候遇到数组的一些操做,例如产生['01', '02' ... '35']这样的数组,随机抽取不重复的元素从新组成数组等问题。回想这类问题在平时项目中遇到的机会很是多,何不概括一下JavaScript数组的一些知识点,以供平时工做参考。javascript
JavaScript提供的数组很是灵活,相关的api也很丰富,例如fill,map,filter,sort等等,极大地方便了程序编写。这里不介绍这些基本的api,而是经过工做中经常使用的使用场景来展现数组的强大。前端
在一个分页表格中好比订单表,要求根据订单金额展现这一页的订单总金额。不少时候后端开发偷懒,把这种计算推给前端,可使用reduce轻松实现个功能,代码以下。vue
var orders = [ { userName: 'Anna', books: 'Bible', money: 21.2 }, { userName: 'Bob', books: 'War and peace', money: 26.5 }, { userName: 'Alice', books: 'The Lord of the Rings', money: 18.4 } ]; let total = orders.reduce((acc, curr) => {return acc + curr.money}, 0); console.log(total);
在vue组件中,能够直接使用reduce表达式计算表格某一列的概括总和,很方便,示例代码以下:java
<tbody> <tr class="header-tr"> <th>品名</th> <th>批号</th> <th>规格</th> <th>等级</th> <th>生产入库(KG)</th> <th>退货入库(KG)</th> <th>返修入库(KG)</th> <th>返修投料(KG)</th> <th>出库(KG)</th> <th>库存结存(件)</th> <th>库存结存重量(KG)</th> <th>期初结存(件)</th> <th>期初结存重量(KG)</th> </tr> <template v-for="(item, key) in tableData"> <template v-for="obj in item"> <tr> <td>{{key}}</td> <td>{{obj.batchNo}}</td> <td>{{obj.spec}}</td> <td>{{obj.level}}</td> <td>{{obj.productionInbound}}</td> <td>{{obj.refundInbound}}</td> <td>{{obj.reworkInbound}}</td> <td>{{obj.reworkFeeding}}</td> <td>{{obj.outbound}}</td> <td>{{obj.monthlyBalanceCount}}</td> <td>{{obj.monthlyBalanceWeight}}</td> <td>{{obj.preMonthlyBalanceCount}}</td> <td>{{obj.preMonthlyBalanceWeight}}</td> </tr> </template> <tr> <th colspan="3">{{key}}小计</th> <th> </th> <th>{{ item.reduce((acc, curr) => acc + curr.productionInbound, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.refundInbound, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.reworkInbound, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.reworkFeeding, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.outbound, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.monthlyBalanceCount, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.monthlyBalanceWeight, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.preMonthlyBalanceCount, 0) }}</th> <th>{{ item.reduce((acc, curr) => acc + curr.preMonthlyBalanceWeight, 0) }}</th> </tr> </template>
工做中前端进度通常是先于后端的,前端画页面的时候后端服务通常尚未写好,这时前端要本身生成一些数据把页面先作起来。有人可能会说用mock,可是小项目引用mock就太麻烦了。这时就要本身先生成一些数据,最多见的就是生成一个对象列表。下面就来讨论生成数据的方式。es6
构造函数Array()有两个重载:算法
new Array(element0, element1[, ...[, elementN]]):根据给定元素生成一个javascript数组,这些元素是逗号分割的。
new Array(arrayLength):arrayLength是一个范围在0到232-1之间的整数,这时方法返回一个长度为arrayLength的数组对象,注意数组此时没有包含任何实际的元素,不是undefined,也不是null,使用console.log()打印出来是empty。若是传入的arrayLength不知足上面条件,抛出RangeError错误。后端
使用Array(arrayLength)获取到空数组以后,使用Array.fill()方法给数组填充初始值,再使用Array.map()方法给数组元素生成有意义的值。api
console.time("arr1"); let arr1 = Array(10).fill(0).map((value, index) => { return ++index; }); console.timeEnd("arr1"); console.log(arr1);
输出结果以下:数组
Array.from:方法从一个相似数组或可迭代对象建立一个新的,浅拷贝的数组实例。代码以下:安全
console.time("arr2"); let arr2 = Array.from(new Array(10), (value, index) => { return ++index; }); console.timeEnd("arr2"); console.log(arr2);
执行结果以下:
使用了递归和当即执行函数来生成数组。
console.time("arr3") let arr3 = (function wallace(i) { return (i < 1) ? [] : wallace(i - 1).concat(i); })(10); console.timeEnd("arr3");
执行结果以下:
相对递归来讲,尾递归效率更高。
console.time("arr4") let arr4 = (function mistake(i, acc) { return (i < 10) ? mistake(i + 1, acc.concat(i)) : acc; })(1, []); console.timeEnd("arr4") console.log(arr4);
执行结果以下:
console.time("arr5"); function* mistake(i) { yield i; if (i < 10) { yield* mistake(i + 1); } } let arr5 = Array.from(mistake(1)); console.timeEnd("arr5"); console.log(arr5);
执行结果以下:
console.time("arr6"); let arr6 = Array.apply(null, {length: 10}).map((value, index) => index + 1); console.timeEnd("arr6"); console.log(arr6);
结果以下:
使用对象属性不重名的特性。
var arr = ['qiang','ming','tao','li','liang','you','qiang','tao']; console.time("nonredundant1"); var nonredundant1 = Object.getOwnPropertyNames(arr.reduce(function(seed, item, index) { seed[item] = index; return seed; },{})); console.timeEnd("nonredundant1"); console.log(nonredundant1);
结果以下:
set是一种相似数组的结构,可是set成员中没有重复的值。set()函数能够接受一个数组或者类数组的参数,生成一个set对象。而Array.from方法用于将两类对象转为真正的数组:相似数组的对象(array-like object和可遍历iterable)的对象包括 ES6 新增的数据结构 Set 和 Map)。
var arr = ['qiang','ming','tao','li','liang','you','qiang','tao']; function unique (arr) { return Array.from(new Set(arr)) } console.time("nonredundant2"); var nonredundant2 = unique(arr); console.timeEnd("nonredundant2"); console.log(nonredundant2);
结果以下:
function unique(arr) { for (var i = 0; i < arr.length; i++) { for (var j = i + 1; j < arr.length; j++) { if (arr[i] == arr[j]) { //第一个等同于第二个,splice方法删除第二个 arr.splice(j, 1); j--; } } } return arr; } console.time("nonredundant3"); var arr = ['qiang', 'ming', 'tao', 'li', 'liang', 'you', 'qiang', 'tao']; var nonredundant3 = unique(arr); console.timeEnd("nonredundant3"); console.log(nonredundant3);
结果以下:
function unique(arr) { var array = []; for (var i = 0; i < arr.length; i++) { if (array .indexOf(arr[i]) === -1) { array .push(arr[i]) } } return array; } var arr = ['qiang', 'ming', 'tao', 'li', 'liang', 'you', 'qiang', 'tao']; console.time("nonredundant4"); var nonredundant4 = unique(arr); console.timeEnd("nonredundant4"); console.log(nonredundant4);
结果以下:
function unique(arr) { arr = arr.sort() var arrry = [arr[0]]; for (var i = 1; i < arr.length; i++) { if (arr[i] !== arr[i - 1]) { arrry.push(arr[i]); } } return arrry; } var arr = ['qiang', 'ming', 'tao', 'li', 'liang', 'you', 'qiang', 'tao']; console.time("nonredundant5"); var nonredundant5 = unique(arr); console.timeEnd("nonredundant5"); console.log(nonredundant5);
结果以下:
function unique(arr) { var obj = {}; return arr.filter(function(item, index, arr){ return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true) }) } var arr = ['qiang', 'ming', 'tao', 'li', 'liang', 'you', 'qiang', 'tao']; console.time("nonredundant6"); var nonredundant6 = unique(arr); console.timeEnd("nonredundant6"); console.log(nonredundant6);
结果以下:
function unique(arr) { let map = new Map(); let array = new Array(); // 数组用于返回结果 for (let i = 0; i < arr.length; i++) { if (map.has(arr[i])) { // 若是有该key值 map.set(arr[i], true); } else { map.set(arr[i], false); // 若是没有该key值 array.push(arr[i]); } } return array; } var arr = ['qiang', 'ming', 'tao', 'li', 'liang', 'you', 'qiang', 'tao']; console.time("nonredundant7"); var nonredundant7 = unique(arr); console.timeEnd("nonredundant7"); console.log(nonredundant7);
结果以下:
function unique(arr){ return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]); } var arr = ['qiang', 'ming', 'tao', 'li', 'liang', 'you', 'qiang', 'tao']; console.time("nonredundant8"); var nonredundant8 = unique(arr); console.timeEnd("nonredundant8"); console.log(nonredundant8);
结果以下:
这个需求在实际开发中也很常见,好比彩票随机一注,随机五注,机动车号牌随机选一个等等。
这种方式是使用Array.sort()和Math.random()结合的方法,Math.random()返回的是一个0-1之间(不包括1)的伪随机数,注意这不是真正的随机数。
var letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; function shuffle1(arr) { return arr.sort(() => 0.5 - Math.random()) } console.time("shuffle1"); letter = shuffle1(letter); console.timeEnd("shuffle1"); console.log(letter);
这种方式并非真正的随机,来看下面的例子。对这个10个字母数组排序1000次,假设这个排序是随机的话,字母a在排序后的数组中每一个位置出现的位置应该是1000/10=100,或者说接近100次。看下面的测试代码:
let n = 1000; let count = (new Array(10)).fill(0); for (let i = 0; i < n; i++) { let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; letter.sort(() => Math.random() - 0.5); count[letter.indexOf('a')]++ } console.log(count);
结果以下:
能够看出元素a的位置在0到9出现的次数并非接近100的。
缘由有两点:
这里sort(() => 0.5 - Math.random())没有输入,跟谈不上返回相同的结果,因此这个方法返回的结果不是真正的数组中的随机元素。
既然(a, b) => Math.random() - 0.5 的问题是不能保证针对同一组 a、b 每次返回的值相同,那么咱们不妨将数组元素改造一下,好比将元素'a'改造为{ value: 'a', range: Math.random() },数组变成[{ value: 'a', range: 0.10497314648454847 }, { value: 'b', range: 0.6497386423992171 }, ...],比较的时候用这个range值进行比较,这样就知足了Array.sort()的比较条件。代码以下:
let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; function shuffle2(arr) { let new_arr = arr.map(i => ({value: i, range: Math.random()})); new_arr.sort((a, b) => a.r - b.r); arr.splice(0, arr.length, ...new_arr.map(i => i.value)); } console.time("shuffle2"); letter = shuffle2(letter); console.timeEnd("shuffle2"); console.log(shuffle2);
输出结果以下:
咱们再使用上面的方式测试一下,看看元素a元素是否是随机分布的。
let n = 1000, count = (new Array(10)).fill(0); for (let i = 0; i < n; i++) { let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; letter = shuffle2(letter) count[letter.indexOf('a')]++ } console.log(count);
结果以下:
从这里能够看出,元素a在位置0到9出现的次数是接近100的,也就是说元素a是随机分布的,其余的元素也是,这时再从这个新数组中截取前几个元素就是想要的数组了。
上面的sort算法,虽然知足了随机性的需求,可是性能上并非很好,很明显为了达到随机目的把简单数组变成了对象数组,最后又从排序后的数组中获取这个随机数组,明显走了一些弯路。
洗牌算法能够解决随机性问题,洗牌算法的步骤以下:
洗牌算法是真的随机的吗,换言之洗牌算法真的能够随机获得n个元素中m个吗?下面拿一个只有5个元素的数组来讲明。
数组有5个元素,以下图。
从5个元素随机抽出一个元素和最后一个换位,假设抽到3,几率是1/5,以下图。注意其余任意4个元素未被抽到的几率是4/5。最终3出如今最后一位的几率是1/5。
将抽到的3和最后一位的5互换位置,最后一位3就肯定了,以下图:
再从前面不肯定的4个元素随机抽一个,这里注意要先考虑这4个元素在第一次未被抽到的几率是4/5,再考虑本次抽到的几率是1/4,而后乘一下获得1/5。注意其余任意3个未被抽到的几率是3/4。5出如今倒数第二位的几率是4/5*1/4=1/5以下图:
如今最后2个元素肯定了,从剩下的3个元素中任意抽取一个,几率是1/3,身下任意2个未被抽到的几率是2/3,可是要考虑上一次未被抽到的几率是3/4,以及上上一次未被抽到的几率是4/5,因而最终1出如今倒数第三位的几率是1/3*3/4*4/5=1/5。
如今倒数3个元素已经肯定,剩下的2个元素中任意取一个,几率是1/2,可是要考虑上一次未被抽到的几率是2/3,上上一次未被抽到的几率是3/4,上上上一次未被抽到的几率是4/5,最终4出如今倒数第4位的几率是1/2*2/3*3/4*4/5=1/5。
最后还剩下一个2,它出如今倒数第5位的几率确定也是1/5。不嫌啰嗦的话能够继续看下去。
如今倒数4个元素已经肯定,剩下1个元素中任意取一个,几率是1,但要考虑上一次未被抽中的几率是1/2,上上一次未被抽中的几率是2/3,上上上一次未被抽中的几率是3/4,上上上上一次未被抽中的几率是4/5,因而2出如今倒数5位置的几率是1*1/2*2/3*3/4*4/5=1/5。
有了算法,下面给出洗牌算法的代码:
let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; function shuffle3(arr) { let i = arr.length, t, j; while (i) { j = Math.floor(Math.random() * (i--)); // t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } console.time("shuffle3"); shuffle3(letter); console.timeEnd("shuffle3"); console.log(letter)
运行结果以下:
还有最后一个问题,咱们来验证一下,仍是和上面的方法同样,随机排序1000次,看看字母a出如今0-9个位置的几率是多少,理论上应该是1000/10=100。来看下面的代码:
let n = 1000; let count = (new Array(10)).fill(0); function shuffle3(arr) { let i = arr.length, t, j; while (i) { j = Math.floor(Math.random() * (i--)); // t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } for (let i = 0; i < n; i++) { let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; shuffle3(letter); count[letter.indexOf('a')]++ } console.log(count);
结果以下:
能够看到基本上都是接近100的,能够说明洗牌算法是随机的。
最近开发作了一个模拟彩票游戏的功能,彩票有机选,其实就是随机选取,下面以双色球为例来看看实现的效果是什么样的。双色球前区从1到33个小球,后区从1到16个小球,一注彩票中前区至少选6个,后区至少选1个。这里使用洗牌算法实现,以下图:
数组扁平化就是把一个多维数组转换成一维的。
es6已经实现了数组的flat方法,使用方法很简单,如[1, [2, 3]].flat()。flat方法能够传入一个参数表示最多处理多深的数组。
var arr1 = [1, 2, [3, 4]]; arr1 = arr1.flat(); console.log(arr1); // [1, 2, 3, 4] var arr2 = [1, 2, [3, 4, [5, 6]]]; arr2 = arr2.flat(); console.log(arr2); //[1, 2, 3, 4, [5, 6]] var arr3 = [1, 2, [3, 4, [5, 6]]]; arr3 = arr3.flat(2); console.log(arr3); // [1, 2, 3, 4, 5, 6] var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]; arr4 = arr4.flat(Infinity); console.log(arr4); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
上面介绍数组概括的时候讲到过reduce,它对数组中的每一个元素执行一个指定的函数,最终将结果汇总为单个返回值,而后配合concat方法合并两个数组,来实现扁平化。
let arr = [1, [2, 3, [4, 5]]]; function flatten1(arr) { return arr.reduce((prev, curr) => prev.concat(Array.isArray(curr) ? flatten1(curr) : curr), []); } console.time("flatten1"); console.log(flatten1(arr)); console.timeEnd("flatten1");
结果以下:
调用数组的toString方法,无论数组有几层,均可以将数组变为字符串并用逗号分隔,而后再使用split分隔,map还原为数组。
let arr = [1, [2, 3, [4, 5]]]; function flatten2(arr) { return arr.toString().split(",").map(i => Number(i)); } console.time("flatten2"); console.log(flatten2(arr)); console.timeEnd("flatten2");
结果以下:
调用对数组调用join方法,无论数组有几层,均可以将数组变成字符串并用逗号分隔,而后使用split分隔,map还原为数组。
let arr = [1, [2, 3, [4, 5]]]; function flatten3(arr) { return arr.join().split(",").map(i => parseInt(i)); } console.time("flatten3"); console.log(flatten3(arr)); console.timeEnd("flatten3");
结果以下:
这种方法和第一种相似,只不过是用map获得数组。
function flatten4(arr) { let res = []; arr.map(item => { if(Array.isArray(item)) { res = res.concat(flatten4(item)); } else { res.push(item); } }); return res; } let arr = [1, [2, 3, [4, 5]]]; console.time("flatten4"); console.log(flatten4(arr)); console.timeEnd("flatten4");
结果以下:
es6中的扩展运算符能够展开数组,将数组元素转换成逗号分隔的对象。
function flatten5(arr) { while (arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr; } let arr = [1, [2, 3, [4, 5]]]; console.time("flatten5"); console.log(flatten5(arr)); console.timeEnd("flatten5");
结果以下:
Generator函数也能够递归调用,这种方式比较新颖。
function* flatten6(array) { for (const item of array) { if (Array.isArray(item)) { yield* flatten6(item); } else { yield item; } } } let arr = [1, [2, 3, [4, 5]]]; console.time("flatten6"); console.log(flatten6(arr)); console.timeEnd("flatten6");
结果以下:
数组是平时开发时最经常使用到的数据结构,充分了解数组的技巧以及相关的api对开发工做大有帮助,首先要作到的是了解这些知识点,而后重要的是在开发中可以使用起来,这样才能让他们迸发出生命力。本文若有错误,请你们指正。