最近碰到这样一个问题:我们从文件里读入了一组三维空间的点,其中有些点的X,Y,Z座标只存在微小的差别,远小于我们后续数据处理的精度,可以认为它们是重复的。所以我们要把这些重复的点去掉。因为数据量不大,这里不准备使用划分包围盒或者建立k-d tree这样的重型武器,我们简单的把点按照其三维坐标进行排序就好。

我们准备使用STL的std::sort来做这个排序。它要求提供一个符合如下签名的比较函数:

bool cmp(const Type1 &a, const Type2 &b)

怎么样写这个比较函数呢?基本的思路是首先按照点的X座标排序,X座标在误差范围内相等就比较Y,再相等就比较Z。

于是我很快写出第一版:

 bool compare(const Point& p1, const Point& p2)
 {
     if (fabs(p1.x - p2.x) < numeric_tol)
     {
         if (fabs(p1.y - p2.y) < numeric_tol)
         {
             if (fabs(p1.z - p2.z) < numeric_tol)
                 return true;
             else if (p1.z < p2.z)
                 return true;
             else
                 return false;
         }
         else if (p1.y < p2.y)
             return true;
         else
             return false;
     }
     else if (p1.x < p2.x)
         return true;
     else
         return false;
 }

运行时居然发现有弹出一个"invalid operator <"的对话框。

于是Google了一下,找到了std::sort的比较函数要满足的三个条件:

  • For all a, comp(a,a)==false
  • If comp(a,b)==true then comp(b,a)==false
  • if comp(a,b)==true and comp(b,c)==true then comp(a,c)==true

详细信息见这里: http://en.cppreference.com/w/cpp/concept/Compare

明显我们的比较函数不符合第一个条件。于是把第8行改成return false就可以工作了。

后来又想,可不可以在比较函数里面不使用tolerance,而只在对排序好的数组进行扫描时使用呢?又尝试了一些思路,发现这样是不行的。完整的测试代码如下:

 // consle_app_sort_points.cpp : Defines the entry point for the console application.
 //

 #include "stdafx.h"
 #include <vector>
 #include <algorithm>
 #include "stdio.h"

 using namespace std;

 struct Point {
     double x;
     double y;
     double z;
 };

 ;

 // this compare function results in run time error "invalid operator <" because for below cases it will produce both p1 < p2 and p2 < p1
 // p1 = (1.1, 1.2, 0)
 // p2 = (1.2, 1.1, 0)
 bool compare1(const Point& p1, const Point& p2)
 {
     if (p1.x < p2.x)
         return true;
     else {
         if (p1.y < p2.y)
             return true;
         else
         {
             if(p1.z < p2.z)
                 return true;
             else
                 return false;
         }
     }
 }

 // compare function with tolerance
 //
 bool compare2(const Point& p1, const Point& p2)
 {
     if (fabs(p1.x - p2.x) < numeric_tol)
     {
         if (fabs(p1.y - p2.y) < numeric_tol)
         {
             if (fabs(p1.z - p2.z) < numeric_tol)
                 return false;
             else if (p1.z < p2.z)
                 return true;
             else
                 return false;
         }
         else if (p1.y < p2.y)
             return true;
         else
             return false;
     }
     else if (p1.x < p2.x)
         return true;
     else
         return false;
 }

 // compare function without tolerance
 //
 bool compare3(const Point& p1, const Point& p2)
 {
     if (p1.x == p2.x)
     {
         if (p1.y == p2.y)
         {
             if (p1.z == p2.z)
                 return false;
             else if (p1.z < p2.z)
                 return true;
             else
                 return false;
         }
         else if (p1.y < p2.y)
             return true;
         else
             return false;
     }
     else if (p1.x < p2.x)
         return true;
     else
         return false;
 }

 // compare function without tolerance
 // essentially the same with compare3
 bool compare4(const Point& p1, const Point& p2)
 {
     if (p1.x < p2.x)
         return true;
     else if (p1.x > p2.x)
         return false;
     else {
         if (p1.y < p2.y)
             return true;
         else if (p1.y > p2.y)
             return false;
         else {
             if (p1.z < p2.z)
                 return true;
             else if (p1.z > p2.z)
                 return false;
             else
                 return false;
         }
     }
 }

 void print(const std::vector<Point>& v)
 {
     std::vector<Point>::const_iterator it = v.begin();
     for (; it != v.end(); ++it)
         printf("{%1.7f, %1.7f, %1.7f}\n", it->x, it->y, it->z);
 }

 int _tmain(int argc, _TCHAR* argv[])
 {
     Point p1 = {1.0000001, 1.2000003, 0.0};
     Point p2 = {1.0000002, 1.0,       0.0};
     Point p3 = {1.0000002, 1.1,       0.0};
     Point p4 = {1.0000002, 1.2000003, 0.0};

     std::vector<Point> v1;
     v1.push_back(p1);
     v1.push_back(p2);
     v1.push_back(p3);
     v1.push_back(p4);

     std::vector<Point> v2(v1);

     printf("vector before sort:\n");
     print(v1);

     std::sort(v1.begin(), v1.end(), compare2);

     printf("sort using compare function with tolerance:\n");
     print(v1);

     printf("\n\n");

     printf("vector before sort:\n");
     print(v2);

     std::sort(v2.begin(), v2.end(), compare4);

     printf("sort using compare function without tolerance:\n");
     print(v2);

     ;
 }

运行结果如下。可以发现在比较函数里不使用tolerance确实是不行的。

points vector before sort:
{1.0000001, 1.2000003, 0.0000000}
{1.0000002, 1.0000000, 0.0000000}
{1.0000002, 1.1000000, 0.0000000}
{1.0000002, 1.2000003, 0.0000000}

sort using compare function with tolerance:
{1.0000002, 1.0000000, 0.0000000}
{1.0000002, 1.1000000, 0.0000000}
{1.0000001, 1.2000003, 0.0000000}                          // 这两个点被放在一起,是对的
{1.0000002, 1.2000003, 0.0000000}

sort using compare function without tolerance:
{1.0000001, 1.2000003, 0.0000000}                          // 第一个点和第四个点不在一起,不行
{1.0000002, 1.0000000, 0.0000000}
{1.0000002, 1.1000000, 0.0000000}
{1.0000002, 1.2000003, 0.0000000}                          // 第一个点和第四个点不在一起,不行

2015/05/04 后记

做重复点去除时,我们发现如果排序的tolerance值选择得太大是不行的,例如取tolerance = 1.0e-4,我们对某一组点调用std::sort可能得到如下的排序结果:

1  94.129105 285.615356 170.371399
2  94.129051 287.139343 172.583496
3  94.129051 287.139343 172.583496
4  94.129196 279.484589 161.337463
5  94.129196 279.484589 161.337463
6  94.129105 285.615356 170.371399
7  94.129051 287.139343 172.583496

我们发现点1和点6实际上是一样,可是它们的排序位置相差了好多,中间隔了一些明显不相等的点,看上去似乎排序的结果是不对的。可是如果我们依次比较每相邻的两个点的坐标,发现他们都满足前述的“先比较X坐标,再比较Y坐标,最好比较Z坐标”的排序原则。例如,2,3 排在1后面是因为它们的X坐标在tolerance范围内相等,但2,3的Y坐标大于1的Y坐标。4,5排在2,3的后面是因为它们的X坐标比2,3大且超出了Tolerance范围。点6的坐标实际上和点1一样,但和点4,5相比,其X坐标在tolerance范围内是相等的。于是根据Y坐标它排在4,5的后面。

如果我们根据相邻点X,Y,Z坐标的差值是否在1.0e-4的范围内来判断是否有重复点,则点1和6不会被判断为重复点!产生如此误判的根本原因是,在tolerance范围内的具有相同坐标值的点实际上是乱序排列的,这样就产生了类似误差累积的错误。

解决方法是:排序的时候取一个很小的tolerance如1.0e-10,但去除重复点时采取一个符合应用场景要求的较大值如1.0e-4,这样就可以保证具有相似坐标的点被排在一起,并且能够被按照tolerance要求去除。

最新文章

  1. extJs学习基础4 Ext.each的用法
  2. 【BZOJ】2697: 特技飞行
  3. php + Bootstrap-v3-Typeahead 自动完成组件的使用
  4. 架设laravel
  5. python 字符串相加
  6. eBay Notification介绍
  7. leetcode143- Reorder List问题
  8. 《将博客搬至CSDN》的文章,
  9. HBase(七): HBase体系结构剖析(下)
  10. FullScreenDownloader
  11. DFS入门之二---DFS求连通块
  12. vmware tools安装程序无法继续,Microsoft Runtime DLL安装程序未能完成安装。的解决方法
  13. zoj 3878 Convert QWERTY to Dvorak【好坑的模拟】
  14. poj3641:伪素数检测
  15. 实现BUG自动检测 - ASP.NET Core依赖注入
  16. 第4章2节《MonkeyRunner源码剖析》ADB协议及服务: ADB服务SERVICES.TXT翻译参考(原创)
  17. XTU 1250 Super Fast Fourier Transform
  18. HTML表格标记
  19. 不常见的for循环命名以及with(document)
  20. emWin录音机,含uCOS-III和FreeRTOS两个版本

热门文章

  1. [转]:C#的ToString如何格式化字符串
  2. leetcode 206
  3. Encrypting bootloader (程序BIN文件加密及在线升级)
  4. C语言编程技巧-signal(信号)[转]
  5. svn本地客户端和eclipse插件对应不上解决
  6. Ubuntu 14.04下搭建 Android 开发环境(1) -JDK安装
  7. Centos安装jdk
  8. SEO优化小技巧
  9. 安装Nexus
  10. 用原生js写碰撞变色效果