从一个GeoFire对象的数组中去除重复项

10

我正在使用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"}

位置基本上是由纬度和经度组成的数组,用于计算距离,在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

  onlyUnique(value, index, self) { 
    return self.indexOf(value) === index;
  }

var newlist = list.filter(onlyUnique)

很不幸,它没有起作用...

这些是针对从数组中删除重复项的类似问题给出的一些答案,但它们都没有奏效。我不明白为什么它们不能适用于我的类型的数组,如果有人有想法或知道原因,那将非常有帮助。


你能分享一下JSON的样例吗?这对我们会更有帮助。 - Rajesh
你是否也可以考虑不使用递增的ID(0、1、2、3等)作为属性名称,而使用对象相关的ID作为属性名称?这将确保您没有重复记录。 - ylerjen
1
你不能用这种方式比较对象:{} === {},它会返回 false - Martial
这不是重复的,你是对的 @RobG 我改成了 Geofire 对象。 - Programmer Man
当然可以,但你想要检测重复的纬度/经度、重复的距离,还是所有属性和值都相同的情况? - RobG
显示剩余4条评论
7个回答

3
你可以使用一个集合来存储并检查重复的值。
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对。

位置是一个独特的元素,所以我们可以改用位置而不是距离吗? - Programmer Man

3

问题在于比较对象。除非两个对象引用同一个对象,否则它们永远不相等。

示例:

{} === {} // false

// Two objects are equal only if they are referencing to same object
var a = {};
a === a; // true

从你的问题中可以清楚地看出,你遇到了第一种情况。在你测试的解决方案中,Solution 1Solution 3由于indexOf执行的是===比较,因此失败了。
但是,Solution 2应该在你的示例中起作用,因为它执行了深度比较,如此处所述:https://lodash.com/docs#isEqualPS:我注意到在Solution 2中有一个简单的拼写错误cleaned.,push(itm);,多了一个逗号。希望这不是问题所在,我继续进行。
所以我猜问题在你的位置数组内部,如果你能提供位置数组的内容,我们应该能够提供更好的解决方案。或者像其他人建议的那样,可以基于对象的单个键(如iddistance)进行过滤,而不是比较整个对象。

1
是的,虽然解决方案2可以使用 if (!_.isEqual(itm, itm2)) cleaned.push(itm); 更简单地编写。;-) - RobG

1
你可以在添加点击量之前检查是否有重复。
编辑:除非它们有相同的引用对象,否则无法比较对象。因此,您可以通过唯一ID比较对象。
使用rxjs filter(),这将返回一个数组。
// 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链接


这种方法很好,但我遇到了错误ts]类型为'void'的'this'上下文无法分配给类型为'Observable <{}>'的方法的'this'。 - Programmer Man
我在问题中展示的列表实际上可以通过console.log(this.hits.value)或console.log(currentHits)记录。 - Programmer Man
@ProgrammerMan 抱歉,实际上您不需要在运算符中传递值,我会更新答案。 - FussinHussin
仍然出现错误,问题在于this.hits是BehaviorSubject([])对象。 - Programmer Man
嗯,你需要发布你正在做什么以获取更多帮助,或者将我添加到聊天中。因为你可以在BehaviorSubject上使用distinctUntilChanged函数。https://dev59.com/JlkS5IYBdhLWcg3wkHp2 - FussinHussin
显示剩余3条评论

1

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>


这不会起作用,因为它没有考虑到每个对象具有的唯一标识符。 - Programmer Man
1
此外,您无法保证对象属性的顺序,因此无法保证JSON.stringify将是有用的比较。 - RobG

1
你可以采用以下方法:

思路:

  • 你可以创建自己的数据结构,并使用哈希映射来保存值。
  • 由于你有位置数据,可以使用longitude|latitude作为键名,因为它是唯一的。
  • 然后暴露一些函数,例如add,该函数将检查值是否存在,如果存在则覆盖,否则添加。
  • 还可以创建一个属性,例如value,该属性将返回位置列表。

注意:以上行为也可以使用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] })
  }
}

这看起来很好,你能不能将它翻译成 TypeScript?因为当你说 this.add 时,你在哪里声明 add? - Programmer Man
@ProgrammerMan 我添加了一个 TypeScript 代码示例,虽然它不能运行,但希望能对你有所帮助! - Rajesh

0

我认为可能是您的比较操作没有正确执行。您可以尝试以下代码:

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;
}, []);;

0

也许你想使用像 lodash 这样的库,它具有关于所有类型集合的广泛函数集。

let newArr = _.uniqWith(myArr, _.isEqual);

使用 isEqual 的 uniqWith 可以得到你想要的结果。

这里是 fiddle 的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接