从 GeoFire 对象数组中删除重复项
Remove Duplicates from an Array of GeoFire Objects
我在 Angular 6 上使用 Geofire 和 Firebase 来存储位置,不幸的是它存储了很多重复项这是一个例子(控制台记录我的变量 currentHits ):
0: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
1: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
2: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
3: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
4: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
5: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
6: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
7: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
8: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
location基本上就是一个用来计算距离的经纬度数组,在id 0, 1, 2中是一样的坐标,3,4, 5也是一样的,...
这就是我想要得到的:
0: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
1: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
2: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
(可选)这是它存储这些位置的方式:
...
hits = new BehaviorSubject([])
...
queryHits(...){
....
let hit = {
location: location,
distance: distance.toFixed(2),
url:img
}
let currentHits = this.hits.value
currentHits.push(hit)
this.hits.next(currentHits)
....
}
的确,这个问题可能已经有人问过了,我一直在研究所有类似的问题并找到了这些函数:
1. RemoveDuplicates()
function removeDuplicates(arr){
let unique_array = []
for(let i = 0;i < arr.length; i++){
if(unique_array.indexOf(arr[i]) == -1){
unique_array.push(arr[i])
}
}
return unique_array
}
var newlist = removeDuplicates(list)
没用我得到了相同的列表,但有重复项。
2。 arrUnique:
function arrUnique(arr) {
var cleaned = [];
arr.forEach(function(itm) {
var unique = true;
cleaned.forEach(function(itm2) {
if (_.isEqual(itm, itm2)) unique = false;
});
if (unique) cleaned.push(itm);
});
return cleaned;
}
var newlist= arrUnique(list);
还有,没用..
3。只有唯一
onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
var newlist = list.filter(onlyUnique)
不幸的是它没有用...
这些是针对从数组中删除重复项的类似问题给出的一些答案,其中 none 有效。我不明白为什么它们不适用于我的阵列类型,如果有人有想法或知道为什么会很有帮助。
您可以使用集合来存储和检查重复值。
const removeDuplicates = arr => {
let matches = new Set();
return arr.filter(elem => {
const {distance} = elem;
if(matches.has(distance)){
return false;
} else {
matches.add(distance);
return true;
}
})
}
请记住,使用此方法可能会删除距离相同但坐标不同的结果。如果这对您造成问题,那么您还需要检查 lat/lng 对。
您始终可以在添加匹配项之前进行检查,以确保没有重复项。
edit: 你不能比较对象,除非它们有相同的参考对象。因此,您可以通过唯一 ID
比较对象
使用 rxjs 过滤器()
这将 return 一个数组
// store history of objs for comparison
addedObjs = [];
this.hits.pipe(filter(obj => {
// check if id is an index of the previous objs
if (addObjs.indexOf(obj.id) === -1) {
this.addedObjs.push(obj.id)
return obj
});
这里是工作stackblitz使用你的一些代码
uniqWith
https://lodash.com/docs/#uniqWith可以用来指定比较的方法:
var arr = [ { location: [1, 2], distance: "48.84", url: "assets/imgs/fix.png" },
{ location: [1, 2], distance: "48.84", url: "assets/imgs/fix.png" },
{ location: [1, 2], distance: "48.84", url: "assets/imgs/fix.png" },
{ location: [3, 4], distance: "48.85", url: "assets/imgs/free.png"},
{ location: [3, 4], distance: "48.85", url: "assets/imgs/free.png"},
{ location: [3, 4], distance: "48.85", url: "assets/imgs/free.png"},
{ location: [5, 6], distance: "48.87", url: "assets/imgs/low.png" },
{ location: [5, 6], distance: "48.87", url: "assets/imgs/low.png" },
{ location: [5, 6], distance: "48.87", url: "assets/imgs/low.png" } ]
var result = _.uniqWith(arr, (a, b) => _.isEqual(a.location, b.location));
console.log( JSON.stringify({...result}).replace(/},/g, '},\n ') );
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
这里的问题是比较对象。除非两个对象都引用同一个对象,否则两个对象永远不相等。
示例:
{} === {} // false
// Two objects are equal only if they are referencing to same object
var a = {};
a === a; // true
从你的问题可以看出你是第一种情况。在您测试的解决方案中,解决方案 1 和 解决方案 3 由于 indexOf
进行 ===
比较而失败。
但是解决方案 2 应该适用于您的示例,因为它进行了深度比较,如此处所述。 https://lodash.com/docs#isEqual.
PS: 这可能是我在 解决方案 2 cleaned.,push(itm);
中观察到的一个简单的错字,那里是一个额外的逗号。希望情况不是这样我继续前进
所以我猜问题出在您的位置数组中,如果您能提供位置数组的内容,我们应该能够提供更好的解决方案。或者正如其他人所建议的那样,您可以根据对象的单个键进行过滤,例如 id 或 distance,而不是比较整个对象
也许您会想要使用像 lodash 这样的库,它具有关于所有类型集合的广泛功能集。
let newArr = _.uniqWith(myArr, _.isEqual);
uniqWith借助isEqual可以得到你想要的。
这是fiddle那个解决方案
您可以使用以下方法:
想法:
- 您可以创建自己的数据结构并使用 hashMap 来保存值。
- 因为你有位置数据,你可以使用
longitude|latitude
作为你的键名,因为它是唯一的。
- 然后暴露一些函数说,
add
将检查值是否存在,覆盖其他添加。
- 同时创建一个 属性,比如
value
,它将 return 位置列表。
注意: 以上行为也可以使用 Set
实现。如果您不能使用 ES6 功能,那么这是一种可扩展且简单的方法。
function MyList() {
var locations = {};
this.add = function(value) {
var key = value.location.join('|');
locations[key] = value;
}
Object.defineProperty(this, 'value', {
get: function() {
return Object.keys(locations).map(function(key) {return locations[key] })
}
})
}
var locations = new MyList();
locations.add({location: [123.12, 456.23], name: 'test 1' });
locations.add({location: [123.16, 451.23], name: 'test 2' });
locations.add({location: [123.12, 456.23], name: 'test 1' });
locations.add({location: [100.12, 456.23], name: 'test 3' });
locations.add({location: [123.12, 456.23], name: 'test 1' });
locations.add({location: [123.12, 400.23], name: 'test 4' });
console.log(locations.value)
Typescript 版本更具可读性:
interface ILocation {
location: Array<number>
[key: string]: any;
}
interface IList {
[key: string]: ILocation
}
class MyList {
private locations: IList = {};
public add(value: ILocation) {
const key: string = value.location.join('|');
this.locations[key] = value;
}
public get value(): Array<ILocation> {
return Object.keys(locations).map(function(key) {return locations[key] })
}
}
我认为您的比较可能无法正常工作。你可以试试这个:
var uniqueHits = currentHits.reduce((acc, curr) => {
if (acc.length === 0) {
acc.push(curr);
} else if (!acc.some((item) =>
item.location[0] === curr.location[0]
&& item.location[1] === curr.location[1]
&& item.distance === curr.distance
&& item.url === curr.url)) {
acc.push(curr);
}
return accumulator;
}, []);;
我在 Angular 6 上使用 Geofire 和 Firebase 来存储位置,不幸的是它存储了很多重复项这是一个例子(控制台记录我的变量 currentHits ):
0: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
1: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
2: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
3: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
4: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
5: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
6: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
7: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
8: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
location基本上就是一个用来计算距离的经纬度数组,在id 0, 1, 2中是一样的坐标,3,4, 5也是一样的,...
这就是我想要得到的:
0: {location: Array(2), distance: "48.84", url: "assets/imgs/fix.png"}
1: {location: Array(2), distance: "48.85", url: "assets/imgs/free.png"}
2: {location: Array(2), distance: "48.87", url: "assets/imgs/low.png"}
(可选)这是它存储这些位置的方式:
...
hits = new BehaviorSubject([])
...
queryHits(...){
....
let hit = {
location: location,
distance: distance.toFixed(2),
url:img
}
let currentHits = this.hits.value
currentHits.push(hit)
this.hits.next(currentHits)
....
}
的确,这个问题可能已经有人问过了,我一直在研究所有类似的问题并找到了这些函数:
1. RemoveDuplicates()
function removeDuplicates(arr){
let unique_array = []
for(let i = 0;i < arr.length; i++){
if(unique_array.indexOf(arr[i]) == -1){
unique_array.push(arr[i])
}
}
return unique_array
}
var newlist = removeDuplicates(list)
没用我得到了相同的列表,但有重复项。
2。 arrUnique:
function arrUnique(arr) {
var cleaned = [];
arr.forEach(function(itm) {
var unique = true;
cleaned.forEach(function(itm2) {
if (_.isEqual(itm, itm2)) unique = false;
});
if (unique) cleaned.push(itm);
});
return cleaned;
}
var newlist= arrUnique(list);
还有,没用..
3。只有唯一
onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
var newlist = list.filter(onlyUnique)
不幸的是它没有用...
这些是针对从数组中删除重复项的类似问题给出的一些答案,其中 none 有效。我不明白为什么它们不适用于我的阵列类型,如果有人有想法或知道为什么会很有帮助。
您可以使用集合来存储和检查重复值。
const removeDuplicates = arr => {
let matches = new Set();
return arr.filter(elem => {
const {distance} = elem;
if(matches.has(distance)){
return false;
} else {
matches.add(distance);
return true;
}
})
}
请记住,使用此方法可能会删除距离相同但坐标不同的结果。如果这对您造成问题,那么您还需要检查 lat/lng 对。
您始终可以在添加匹配项之前进行检查,以确保没有重复项。
edit: 你不能比较对象,除非它们有相同的参考对象。因此,您可以通过唯一 ID
比较对象使用 rxjs 过滤器() 这将 return 一个数组
// store history of objs for comparison
addedObjs = [];
this.hits.pipe(filter(obj => {
// check if id is an index of the previous objs
if (addObjs.indexOf(obj.id) === -1) {
this.addedObjs.push(obj.id)
return obj
});
这里是工作stackblitz使用你的一些代码
uniqWith
https://lodash.com/docs/#uniqWith可以用来指定比较的方法:
var arr = [ { location: [1, 2], distance: "48.84", url: "assets/imgs/fix.png" },
{ location: [1, 2], distance: "48.84", url: "assets/imgs/fix.png" },
{ location: [1, 2], distance: "48.84", url: "assets/imgs/fix.png" },
{ location: [3, 4], distance: "48.85", url: "assets/imgs/free.png"},
{ location: [3, 4], distance: "48.85", url: "assets/imgs/free.png"},
{ location: [3, 4], distance: "48.85", url: "assets/imgs/free.png"},
{ location: [5, 6], distance: "48.87", url: "assets/imgs/low.png" },
{ location: [5, 6], distance: "48.87", url: "assets/imgs/low.png" },
{ location: [5, 6], distance: "48.87", url: "assets/imgs/low.png" } ]
var result = _.uniqWith(arr, (a, b) => _.isEqual(a.location, b.location));
console.log( JSON.stringify({...result}).replace(/},/g, '},\n ') );
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
这里的问题是比较对象。除非两个对象都引用同一个对象,否则两个对象永远不相等。
示例:
{} === {} // false
// Two objects are equal only if they are referencing to same object
var a = {};
a === a; // true
从你的问题可以看出你是第一种情况。在您测试的解决方案中,解决方案 1 和 解决方案 3 由于 indexOf
进行 ===
比较而失败。
但是解决方案 2 应该适用于您的示例,因为它进行了深度比较,如此处所述。 https://lodash.com/docs#isEqual.
PS: 这可能是我在 解决方案 2 cleaned.,push(itm);
中观察到的一个简单的错字,那里是一个额外的逗号。希望情况不是这样我继续前进
所以我猜问题出在您的位置数组中,如果您能提供位置数组的内容,我们应该能够提供更好的解决方案。或者正如其他人所建议的那样,您可以根据对象的单个键进行过滤,例如 id 或 distance,而不是比较整个对象
也许您会想要使用像 lodash 这样的库,它具有关于所有类型集合的广泛功能集。
let newArr = _.uniqWith(myArr, _.isEqual);
uniqWith借助isEqual可以得到你想要的。
这是fiddle那个解决方案
您可以使用以下方法:
想法:
- 您可以创建自己的数据结构并使用 hashMap 来保存值。
- 因为你有位置数据,你可以使用
longitude|latitude
作为你的键名,因为它是唯一的。 - 然后暴露一些函数说,
add
将检查值是否存在,覆盖其他添加。 - 同时创建一个 属性,比如
value
,它将 return 位置列表。
注意: 以上行为也可以使用 Set
实现。如果您不能使用 ES6 功能,那么这是一种可扩展且简单的方法。
function MyList() {
var locations = {};
this.add = function(value) {
var key = value.location.join('|');
locations[key] = value;
}
Object.defineProperty(this, 'value', {
get: function() {
return Object.keys(locations).map(function(key) {return locations[key] })
}
})
}
var locations = new MyList();
locations.add({location: [123.12, 456.23], name: 'test 1' });
locations.add({location: [123.16, 451.23], name: 'test 2' });
locations.add({location: [123.12, 456.23], name: 'test 1' });
locations.add({location: [100.12, 456.23], name: 'test 3' });
locations.add({location: [123.12, 456.23], name: 'test 1' });
locations.add({location: [123.12, 400.23], name: 'test 4' });
console.log(locations.value)
Typescript 版本更具可读性:
interface ILocation {
location: Array<number>
[key: string]: any;
}
interface IList {
[key: string]: ILocation
}
class MyList {
private locations: IList = {};
public add(value: ILocation) {
const key: string = value.location.join('|');
this.locations[key] = value;
}
public get value(): Array<ILocation> {
return Object.keys(locations).map(function(key) {return locations[key] })
}
}
我认为您的比较可能无法正常工作。你可以试试这个:
var uniqueHits = currentHits.reduce((acc, curr) => {
if (acc.length === 0) {
acc.push(curr);
} else if (!acc.some((item) =>
item.location[0] === curr.location[0]
&& item.location[1] === curr.location[1]
&& item.distance === curr.distance
&& item.url === curr.url)) {
acc.push(curr);
}
return accumulator;
}, []);;