js数据结构与算法——数组
(一)小编有话说
这段时间没有更新文章先给读者们道个歉,因为个人心态等原因没有把自己所学的写博客,这段时间自学了vue全家桶还有node.js,后续我会重新的去整理出它们的学习笔记
(二)重要性
数据结构和算法对于不管是什么方向的程序员来讲都是非常重要的,不仅仅是为了过面试,还有我们不可能在整个职业生涯中仅仅做一个crud boy,所以我想开拓一个新的模块和大家一起学习js版数据结构与算法。
(三)我们为什么用数组
举个例子:
现在有这样一个需求:保存所在城市每个月的平均温度。可以这么写:
const averageTempJan = 31.9;
const averageTempFeb = 35.3;
const averageTempMar = 42.4;
const averageTempApr = 52;
const averageTempMay = 60.8;
当然,这不是最好的方案,这样只能存一年的数据,要是想多存几年的平均温度呢?我们可以用数组来解决,更简洁的呈现同样的信息
const averageTemp = [];
averageTemp[0] = 31.9;
averageTemp[1] = 35.3;
averageTemp[2] = 42.4;
averageTemp[3] = 52;
averageTemp[4] = 60.8;
(四)创建和初始化数组
在js中创建数组,用[ ]的形式就可以了
例如:
let daysOfWeek = [];
也可以使用一些元素初始化数组
let daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday'];
我们可以使用数组的length属性查看存了多少个元素
console.log(daysOfWeek.length);
运行结果为7
(五)访问数组和迭代数组
如果要访问数组里特定位置的元素,可以直接使用[ ]传递数值位置,如果我们想要输出daysOfWeek的所有元素,可以通过迭代数组、打印元素
for (let i = 0; i < daysOfWeek.length; i++) {
console.log(daysOfWeek[i]);
}
我们再看另一个例子,求斐波那契数列的前 20 个数。已知斐波那契数列中的前两项是 1,从第三项开始,每一项都等于前两项之和。
const fibonacci = [];
fibonacci[0] = 1;
fibonacci[1] = 1;
for (let i = 2; i < 20; i++) {
fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2];
}
for (let i = 0; i < fibonacci.length; i++) {
console.log(fibonacci[i]);
}
输出结果
(六)添加元素
假如我们有一个数组 numbers,初始化成了 0 到 9。
let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
我们可以直接给数组最后一位空位赋值
numbers[numbers.length] = 10;
使用push方法,能把元素添加到数组末尾,通过push方法,我们可以添加任意个元素
numbers.push(11);
numbers.push(12, 13);
下面我们来迭代输出number数组查看结果
for(let i = 0; i < numbers.length; i++){
console.log(numbers[i])
}
在数组开头插入元素,使用unshift方法可以直接把数值插入数组的开头
numbers.unshift(-1);
numbers.unshift(-3, -2);
我们再来输出数组
(七)删除元素
从数组末尾删除元素,使用pop方法
numbers.pop();
输出数组
删除数组的第一个元素,使用shift方法
numbers.shift();
输出数组
(八)在任意位置添加和删除元素
使用splice方法,通过指定位置,删除指定数量的元素
numbers.splice(5,3);
输出数组发现,删除了从数组索引 5 开始的 3 个元素,这就意味着 numbers[5]、numbers[6]和numbers[7]从数组中删除了。(3,4,5已经移除)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210503101933384.png)现在,我们想把数 3、4、5 插入数组里,放到之前删除元素的位置上,可以再次使用 splice方法。
numbers.splice(5, 0, 3, 4, 5);
输出数组
最后我们可以执行以下代码
numbers.splice(5, 3, 3, 4, 5);
输出的值是从-2 到 12。原因在于,我们从索引 5 开始删除了 3 个元素,但也从索引 5 开始添加了元素 3、4、5。
(九)二维数组和多维数组
我们把开头的例子改成数天内每小时的温度,要保存两天内的每小时温度数据可以这样写
let averageTemp = [];
averageTemp[0] = [72, 75, 79, 79, 81, 81];
averageTemp[1] = [81, 79, 75, 75, 73, 73];
也可以这样写
// day 1
averageTemp[0] = [];
averageTemp[0][0] = 72;
averageTemp[0][1] = 75;
averageTemp[0][2] = 79;
averageTemp[0][3] = 79;
averageTemp[0][4] = 81;
averageTemp[0][5] = 81;
// day 2
averageTemp[1] = [];
averageTemp[1][0] = 81;
averageTemp[1][1] = 79;
averageTemp[1][2] = 75;
averageTemp[1][3] = 75;
averageTemp[1][4] = 73;
averageTemp[1][5] = 73;
创建一个通用函数,看二维数组的输出
function printMatrix(myMatrix) {
for (let i = 0; i < myMatrix.length; i++) {
for (let j = 0; j < myMatrix[i].length; j++) {
console.log(myMatrix[i][j]);
}
}
}
printMatrix(averageTemp);
使用 console.table(averageTemp) 语句,数组结构更清晰
多维数组
假设我们要创建一个3*3*3的矩阵,每一个格子包含矩阵的i(行)、j(列)及 z(深度)之和
const matrix3x3x3 = [];
for (let i = 0; i < 3; i++) {
matrix3x3x3[i] = []; // 我们需要初始化每个数组
for (let j = 0; j < 3; j++) {
matrix3x3x3[i][j] = [];
for (let z = 0; z < 3; z++) {
matrix3x3x3[i][j][z] = i + j + z;
}
}
}
输出矩阵内容
for (let i = 0; i < matrix3x3x3.length; i++) {
for (let j = 0; j < matrix3x3x3[i].length; j++) {
for (let z = 0; z < matrix3x3x3[i][j].length; z++) {
console.log(matrix3x3x3[i][j][z]);
}
}
}
console.table(matrix3x3x3);
(十)数组合并
考虑以下场景,有多个数组,需要合并起来成为一个数组。
const zero = 0;
const positiveNumbers = [1, 2, 3];
const negativeNumbers = [-3, -2, -1];
concat 方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序连接指定数组。
let numbers = negativeNumbers.concat(zero, positiveNumbers);
在这个例子里,zero 将被合并到 nagativeNumbers 中,然后positiveNumbers 继续被合并。
输出数组
for (let i = 0; i < numbers.length; i++){
console.log(numbers[i])
}
(十一)迭代器函数
我们需要一个数组和一个函数:假设数组中的值是从 1 到 15;如果数组里的元素可以被 2 整除(偶数),函数就返回 true,否则返回 false。
function isEven(x) {
// 如果 x 是 2 的倍数,就返回 true
console.log(x);
return x % 2 === 0 ? true : false;
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
其中 return x % 2 === 0 ? true : false; 可以写成 return (x % 2 === 0)
ES6语法可以使用箭头函数来改写isEven函数 const isEven = x => x % 2 === 0;
const isEven = x => x % 2 === 0;
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1. 用every迭代
every 方法会迭代数组中的每个元素,直到返回 false。
numbers.every(isEven);
输出结果为1,因为1不是2的倍数,函数返回false,every执行结束
2. 用some迭代
它和 every 的行为相反,会迭代数组的每个元素,直到函数返回 true。
numbers.some(isEven);
输出结果为1,2,1不是2的倍数返回false。2是2的倍数返回true,some执行结束
3. 用foreach迭代
numbers.forEach(x => console.log(x % 2 === 0));
4. 使用map和filter方法
map方法:它保存了传入 map 方法的 isEven函数的运行结果。这样就很容易知道一个元素是否是偶数。
const myMap = numbers.map(isEven);
filter方法:它返回的新数组由使函数返回 true 的元素组成。
const evenNumbers = numbers.filter(isEven);
5. 使用reduce方法
reduce 方法接收一个有如下四个参数的函数:previousValue、 currentValue、index 和 array。因为 index 和 array 是可选的参数,所以如果用不到它们的话,可以不传。这个函数会返回一个将被叠加到累加器的值,reduce 方法停止执行后会返回这个累加器。如果要对一个数组中的所有元素求和,这就很有用。
numbers.reduce((previous, current) => previous + current);
(十二)ES6和数组新功能
1. 使用 for…of 循环迭代
for (const n of numbers) {
console.log(n % 2 === 0 ? 'even' : 'odd');
}
2. 使用@@iterator 对象
ES6 还为 Array 类增加了一个@@iterator 属性,需要通过 Symbol.iterator 来访问
iterator = numbers[Symbol.iterator]();
for (const n of iterator) {
console.log(n);
}
3. 数组的 entries、keys 和 values 方法
entries 方法返回包含键值对的@@iterator
aEntries = numbers.entries();
for (const n of aEntries) {
console.log(n);
}
keys 方法返回包含数组索引的@@iterator
const aKeys = numbers.keys(); // 得到数组索引的迭代器
console.log(aKeys.next());
console.log(aKeys.next());
console.log(aKeys.next());
keys 方法会返回 numbers 数组的索引。一旦没有可迭代的值,aKeys.next()就会返回一个 value 属性为 undefined、done 属性为 true 的对象。如果 done 属性的值为 false,就意味着还有可迭代的值。
values 方法返回的@@iterator 则包含数组的值。
const aValues = numbers.values();
console.log(aValues.next());
console.log(aValues.next());
console.log(aValues.next());
4. 使用 from 方法
Array.from 方法根据已有的数组创建一个新数组。
例如,要复制 numbers 数组,可以这样做。
let numbers2 = Array.from(numbers);
还可以传入一个用来过滤值的函数,例如:
let evens = Array.from(numbers, x => (x % 2 == 0));
5. 使用 Array.of 方法
Array.of 方法根据传入的参数创建一个新数组。
let numbers3 = Array.of(1);
let numbers4 = Array.of(1, 2, 3, 4, 5, 6);
和下面的代码的效果是一样的
let numbers3 = [1];
let numbers4 = [1, 2, 3, 4, 5, 6];
我们也可以用这个方法来复制已有的数组
let numbersCopy = Array.of(...numbers4);
6. 使用fill方法
fill 方法用静态值填充数组。
例如:
let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);
numbersCopy.fill(0);
输出结果来看数组所有值都会变为0
再比如:
numbersCopy.fill(2, 1);
输出结果来看数组中从 1 开始的所有位置上的值都是 2
再比如:
numbersCopy.fill(1, 3, 5);
输出结果来看数组中我们会把 1 填充到数组索引 3 到 5 的位置(不包括 3 和 5)
fill方法在创建数组并且初始化数值的时候非常好用,例如:
let ones = Array(6).fill(1);
我们创建了一个长度为6,都是1的数组
7. 使用 copyWithin 方法
copyWithin 方法复制数组中的一系列元素到同一数组指定的起始位置。
例如:
let copyArray = [1, 2, 3, 4, 5, 6];
假如我们想把 4、5、6 三个值复制到数组前三个位置,得到[4, 5, 6, 4, 5, 6]这个数组,可以这样做。
copyArray.copyWithin(0, 3);
从索引3复制后,到索引0
假如我们想把 4、5 两个值(在位置 3 和 4 上)复制到位置 1 和 2,可以这样做:
copyArray.copyWithin(1, 3, 5);
这种情况下,会把从位置 3 开始到位置 5 结束(不包括 3 和 5)的元素复制到位置 1,结果是得到数组[1, 4, 5, 4, 5, 6]。
(十三)排序元素
我们想要方向输出之前的数组numbers可以用reverse方法
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
numbers.reverse();
我们再使用sort方法
numbers.sort();
这样的输出结果因为 sort 方法在对数组做排序时,把元素默认成字符串进行相互比较。
我们可以传入自己写的比较函数。因为数组里都是数,所以可以像下面这样写。
numbers.sort((a, b) => a - b);
意思是: b 大于 a 时,这段代码会返回负数,反之则返回正数。如果相等的话,就会返回 0。也就是说返回的是负数,就说明 a 比 b 小,这样 sort 就能根据返回值的情况对数组进行排序。
也可以写成这样,更清晰
function compare(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
// a 必须等于 b
return 0;
}
numbers.sort(compare);
1. 自定义排序
例如:
const friends = [{
name: 'John',
age: 30
},
{
name: 'Ana',
age: 20
},
{
name: 'Chris',
age: 25
}
];
function comparePerson(a, b) {
if (a.age < b.age) {
return -1;
}
if (a.age > b.age) {
return 1;
}
return 0;
}
console.log(friends.sort(comparePerson));
2. 字符串排序
例如:
let names = ['Ana', 'ana', 'john', 'John'];
console.log(names.sort());
输出根据首字母ASCII 值来比较排序
(十四)搜索
搜索有两个方法:indexOf 方法返回与参数匹配的第一个元素的索引;lastIndexOf 返回与参数匹配的最后一个元素的索引。
我们用到之前的numbers数组
console.log(numbers.indexOf(10));
console.log(numbers.indexOf(100));
在这个示例中,第一行的输出是 9,第二行的输出是-1(因为 100 不在数组里)。
numbers.push(10);
console.log(numbers.lastIndexOf(10));
console.log(numbers.lastIndexOf(100));
我们往数组里加入了一个新的元素 10,因此第二行会输出 15(数组中的元素是 1 到 15,还有 10),第三行会输出-1(因为 100 不在数组里)。
1. ES6的find和findIndex方法
let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
function multipleOf13(element, index, array) {
return (element % 13 == 0);
}
console.log(numbers.find(multipleOf13));
console.log(numbers.findIndex(multipleOf13));
find 和 findIndex 方法接收一个回调函数,搜索一个满足回调函数条件的值。上面的例子里,我们要从数组里找一个 13 的倍数。find 和 findIndex 的不同之处在于,find 方法返回第一个满足条件的值,findIndex方法则返回这个值在数组里的索引。如果没有满足条件的值,find 会返回 undefined,而findIndex 返回-1。
2. ES7 使用 includes方法
如果数组里存在某个元素,includes 方法会返回 true,否则返回 false。
console.log(numbers.includes(15));
console.log(numbers.includes(20));
如果给 includes 方法传入一个起始索引,搜索会从索引指定的位置开始。
let numbers2 = [7,6,5,4,3,2,1];
console.log(numbers2.includes(4,5));
上面的例子输出为 false,因为数组索引 5 之后的元素不包含 4。
(十五)输出数组为字符串
toString 和 join方法
例如:
let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
const numbersString = numbers.join('-');
console.log(numbersString);
如果要把数组内容发送到服务器,或进行编码(知道了分隔符,解码也很容易),这会很有用。