在实际项目开发中,无论是后端仍是前端,最基本的操做就是数据的CRUD。换句话说,后端是根据某些条件组装数据,前端是拿着后端提供的数据,进行数据展现。可是无论在进行数据封装仍是展现,其中在特定的场景下,须要根据某些条件,对数据进行排序。而在既定的现有框架下,都有现成的方法对数据进行排序处理。javascript
可是,在开发中,有没有想过,这些排序底层是如何实现的,还有就是针对不一样的数据,不一样的排序是否在性能方面有不一样的体现。前端
后端的数据排序不在本文的讨论中,本文只针对前端的排序的一些思路和实践.(该文只在前端范围内讨论排序的实现)java
如今有一组数据以下:算法
0: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
1: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
2: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
3: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 1}
4: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 1}
5: {layoutId: 1269, priceCodeId: 1000410, activityId: 2, roomNum: 1}
复制代码
如今有一个需求就是,须要根据layoutId,priceCodeId,activityId
这个三个组合主键来对roomNum
进行求和。后端
处理结果以下:数组
0: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 3}
1: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 2}
2: {layoutId: 1069, priceCodeId: 1000410, activityId: 2, roomNum: 1}
复制代码
不要惊讶,这不是后台处理逻辑,这是一个真真切切的前端数据处理逻辑。有的前端可能会说,数据处理是后台的事。这个前端很差处理,我想说,若是是这个思惟方式,感受你被其余语言开发工程师鄙视是理所固然的。bash
因此,我信奉一个道理:框架
菜并不可怕,不敢正视本身的弱点才是最可怕的。性能
其实相似上述的问题,可能用JS的一些现有的库,可能很好实现,可是这个文章没有选择这些现有库,而是以一种最原始的方式来实现。或许还能有更好的方式来实现,欢迎你们批评指导。大数据
须要指出的是,这个案例和本文讲的排序没有很大的关系,可是在文末的实现的时候,用到了一些排序的思路方法和方式。
在开始以前,须要明确几个概念:原地排序,稳定性。
冒泡排序只会操做相邻的两个数据。每次冒泡操做都会对相邻的两个元素进行比较,看是否知足大小关系要求。若是不知足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工做。
如今咱们假设有29,10,14,37,14
这些数据,须要按升序排序
arr 为待排序数组,n为数组个数
function BubbleSort1(arr,n){
if(n<=1) return ;
for(let i=0;i<n;i++){
for(let j = i+1;j<n;j++){
if(arr[i]>arr[j]){
let tempValue = arr[i];
arr[i] = arr[j];
arr[j] = tempValue;
}
}
}
return arr;
}
复制代码
function bubbleSort2(arr, n) {
if (n <= 1) return;
for (let i = 0; i < n; ++i) {
// 提早退出冒泡循环的标志位
let flag = false;
for (let j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j+1]) { // 交换
let tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = true; // 表示有数据交换
}
}
if (!flag) break; // 没有数据交换,提早退出
}
}
复制代码
插入排序具体的实现思路就是:找到已存数据列表中插入位置,将数据插入对应位置。是一个找茬算法。
将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
咱们仍是以29,10,14,37,14
为例。
arr 为待排序数组,n为数组个数
function insertionSort(arr, n) {
if (n <= 1) return;
for (let i = 1; i < n; ++i) {
let value = arr[i];
//肯定已排序区间,这里的j是一个"哨兵",守卫者"边界"
let j = i - 1;
// 查找插入的位置(这里的) --j也就是从已排中找对应合适的位置
for (; j >= 0; --j) {
if (arr[j] > value) {
arr[j+1] = arr[j]; // 数据移动
} else {
break;
}
}
arr[j+1] = value; // 插入数据
}
}
复制代码
选择排序算法的实现思路有点相似插入排序,也分已排序区间和未排序区间。可是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
咱们仍是以29,10,14,37,14
为例。 屏幕录制 2019-09-27 下午2.30.52_52.gif
function selectionSort(arr) {
let len = arr.length;
let minIndex, temp;
for (let i = 0; i < len - 1; i++) {
minIndex = i;
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
复制代码
5,8,5,2,9
这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,因此就不稳定了。针对这三种时间复杂度都为O(n²)排序统一作一次对比:
排序方式 | 是否原地排序 | 是否稳定排序 | 最好 最坏 平均 |
---|---|---|---|
冒泡排序 | ✅ | ✅ | O(n) O(n²) O(n²) |
插入排序 | ✅ | ✅ | O(n) O(n²) O(n²) |
选择排序 | ✅ | × | O(n²) O(n²) O(n²) |
在刚开始的时候,有一个纯前端的数据聚合问题。话很少说,来段代码尽尽兴。嗨皮一下
来咯,来咯。
版本1:
function polymerizationData(sourceArr, flagKeyArr, targetKeyArr) {
let tempSourceArr = [...sourceArr];
//肯定已排区间,默认是数组第一个
let targetArr = tempSourceArr.splice(0, 1);
while (tempSourceArr.length) {
for (let i = 0; i < targetArr.length; i++) {
//这里的i,其实也是一个哨兵,可是他的起始位置是从已排区间的开始位置算
let outerItem = targetArr[i];
let j = 0;
for (; j < tempSourceArr.length; j++) {
//从未排区间,取值,进行数据比对
let innerItem = tempSourceArr[j];
let isAllEqual = true;
for (let flagindex = 0; flagindex < flagKeyArr.length; flagindex++) {
if (innerItem[flagKeyArr[flagindex]] != outerItem[flagKeyArr[flagindex]]) {
isAllEqual = false;
break;
}
}
if (isAllEqual) {
for (let targetKeyIndex = 0; targetKeyIndex < targetKeyArr.length; targetKeyIndex++) {
outerItem[targetKeyArr[targetKeyIndex]] += innerItem[targetKeyArr[targetKeyIndex]]
}
tempSourceArr.splice(j, 1);
j = -1;
} else {
targetArr.push(tempSourceArr.splice(j, 1)[0]);
break;
}
}
}
}
return targetArr;
}
复制代码
版本2(版本1的升级版)
function AdvancePolymerizationData(sourceArr, flagKeyArr, targetKeyArr) {
let storeDesignationKey = {};
let tempSourceArr = [...sourceArr];
let finalArr = tempSourceArr.splice(0, 1);
flagKeyArr.map(item => {
storeDesignationKey[item] = finalArr[0][item];
})
let i = 0, j = 0;
for (; i < tempSourceArr.length; i++) {
let isEqual = flagKeyArr.every(item => {
return tempSourceArr[i][item] == storeDesignationKey[item]
})
if (isEqual) {
targetKeyArr.map(item => {
finalArr[j][item] += tempSourceArr[i][item]
})
tempSourceArr.splice(i, 1);
i = -1;
} else {
let requirePushItem = tempSourceArr.splice(i, 1)[0];
flagKeyArr.map(item => {
storeDesignationKey[item] = requirePushItem[item];
})
finalArr.push(requirePushItem);
j++;
i = -1;
}
}
return finalArr;
}
复制代码
上面的代码调用方式
AdvancePolymerizationData(sourceArray, ["layoutId", "priceCodeId", "activityId"], ["roomNum"]);
复制代码
其实,相似这种的数据处理,有一个共同的点,就是须要有一个相似插入排序和选择排序中已排序区,来做为一个targetArray/finalArr
。在进行isAllEqual
的判断处理就相似于在排序中的数据判断。在知足状况下,进行数据拼接或者数据移动。