业务场景
当地图往下拖的时候要更新地图上的房源标签数据,上图绿框表示不变的标签,而黄框表示新加的房源。 后端每次都会把当前地图可见区域的房源返回给我,当用户拖动的时候需要知道哪些是原先已经有的房源,哪些是新加的。把新加的房源画上,而把超出区域的房源删掉,已有的房源保持不动。因此需要对比当前房源和新的结果哪些是重复的。因为如果不这样做的话,改成每次都是全部删掉再重新画,已有的房源标签就会闪一下。因此为了避免闪动做一个增量更新。 把这个问题抽象一下就变成:给两个数组,需要找出第一个数组里面的重复值和非重复值。即有一个数组保存上一次状态的房源,而另一个数组是当前状态的新房源数据。找到的重复值是需要保留,找到非重复值是要删掉的。 用set实现如下。
实现算法
var lastHouses = new Set();
function filterHouse(houses){
var remainsHouses = [],
newHouses = [];
for(var i = houses.length - 1; i >= 0; i--){
if(lastHouses.has(houses[i].id)){
remainsHouses.push(houses[i]);
} else {
newHouses.push(houses[i]);
}
}
for(var i = 0; i < newHouses.length; i++){
lastHouses.add(newHouses[i].id);
}
return {remainsHouses: remainsHouses,
newHouses: newHouses};
}
老数据的存储lastHouses从数组改成set,但如果一开始就是数组呢,就像问题抽象里面说的给两个数组?那就用这个数组的数据初始化一个Set. 使用Set和使用Array的区别在于可以减少一重循环,调用Set.prototype.has的函数。Set一般是使用红黑树实现的,红黑树是一种平衡查找二叉树,它的查找时间复杂度为O(logN)。所以时间上进行了改进,从O(N)变成O(logN),而总体时间从O(N2)变成O(NlogN)。实际上,Chrome V8的Set是用哈希实现的,它是一个哈希Set,查找时间复杂度为O(1),所以总体的时间复杂度是O(N). 不管是O(NlogN)还是O(N),表面上看它们的时间要比O(N2)的少。但实际上需要注意的是它们前面还有一个系数。使用Set在后面更新lastHouses的时候也是需要时间的:
for(var i = 0; i < newHouses.length; i++){
lastHouses.add(newHouses[i].id);
}
如果Set是用树的实现,这段代码是时间复杂度为O(NlogN),所以总的时间为O(2NlogN),但是由于大O是不考虑系数的,O(2NlogN) 还是等于O(NlogN),当数据量比较小的时侯,这个系数会起到很大的作用,而数据量比较大的时候,指数级增长的O(N2)将会远远超过这个系数,哈希的实现也是同样道理。所以当数据量比较小时,如只有一两百可直接使用双重循环处理即可。 上面的代码有点冗长,我们可以用ES6的新特性改写一下,变得更加的简洁:
function filterHouse(houses){
var remainsHouses = [],
newHouses = [];
houses.map(house => lastHouses.has(house.id) ? remainsHouses.push(house)
: newHouses.push(house));
newHouses.map(house => lastHouses.add(house.id));
return {remainsHouses, newHouses};
}
希望本文能对读者开发相关业务有所启发。