将三维空间的点按照座标排序(兼谈为std::sort写compare function的注意事项)
最近碰到这样一个问题:我们从文件里读入了一组三维空间的点,其中有些点的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要求去除。
将三维空间的点按照座标排序(兼谈为std::sort写compare function的注意事项)的更多相关文章
- zw版·Halcon与delphi(兼谈opencv)
zw版·Halcon与delphi(兼谈opencv) QQ群 247994767(delphi与halcon) <Halcon与delphi>系列,早两年就想写,不过一方面,因为Halc ...
- [转] Portable Trac 简单介绍 - 兼谈为什么不选择 Redmine
Portable Trac 简单介绍 - 兼谈为什么不选择 Redmine Trac是一个轻量级的软件项目管理环境,如果在工作中涉及一个开发团队的管理并且关心项目管理工具的话,相信都在 Trac. ...
- 11.4.2 排序或合并文件(sort命令) - 51CTO.COM
11.4.2 排序或合并文件(sort命令) - 51CTO.COM 11.4.2 排序或合并文件(sort命令) 2010-03-12 14:37 陆松年 电子工业出版社 我要评论(0) 字号:T ...
- 垃圾回收机制GC知识再总结兼谈如何用好GC(转)
作者:Jeff Wong 出处:http://jeffwongishandsome.cnblogs.com/ 本文版权归作者和博客园共有,欢迎围观转载.转载时请您务必在文章明显位置给出原文链接,谢谢您 ...
- java排序算法之冒泡排序(Bubble Sort)
java排序算法之冒泡排序(Bubble Sort) 原理:比较两个相邻的元素,将值大的元素交换至右端. 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面.即在第一趟:首先比较第1个和第2个数 ...
- 一个std::sort 自定义比较排序函数 crash的分析过程
两年未写总结博客,今天先来练练手,总结最近遇到的一个crash case. 注意:以下的分析都基于GCC4.4.6 一.解决crash 我们有一个复杂的排序,涉及到很多个因子,使用自定义排序函数的st ...
- Qt使用std::sort进行排序
参考: https://blog.csdn.net/u013346007/article/details/81877755 https://www.linuxidc.com/Linux/2017-01 ...
- TCP的状态兼谈Close_Wait和Time_Wait的状态
原文链接: http://www.2cto.com/net/201208/147485.html TCP的状态兼谈Close_Wait和Time_Wait的状态 一 TCP的状态: 1).LIST ...
- 漫谈 Google 的 Native Client(NaCl) 技术(二)---- 技术篇(兼谈 LLVM)
转自:http://hzx5.blog.163.com/blog/static/40744388201172531637729/ 漫谈 Google 的 Native Client(NaCl) 技术( ...
随机推荐
- linux(Debian) 中的cron计划任务配置方法
cron服务每分钟不仅要读一次/var/spool/cron内的所有文件,还需要读一次/etc/crontab,因此我们配置这个文件也能运用cron服务做一些事情.用crontab配置是针对某个用户的 ...
- 关于int类型的赋值语句正确的有
A.char a=65; 对 B.int a=12.0; C.int a=12.0f; D.int a=(int)12.0 对
- ASP.NET 表单验证实现浅析
首先,自然是配置 Web.config,在 <system.web> 下设定: <authentication mode="Forms"> <form ...
- LB负载均衡层次结构(摘抄)
作为后端应用的开发者,我们经常开发.调试.测试完我们的应用并发布到生产环境,用户就可以直接访问到我们的应用了.但对于互联网应用,在你的应用和用户之间还隔着一层低调的或厚或薄的负载均衡层软件,它们不显山 ...
- 解决String TestContext下使用junit4抛出异常(java.lang.NoClassDefFoundError)的问题
Spring版本2.5.5,JUnit 版本 4.8.1,使用了 Spring TestContext 的 SpringJUnit4ClassRunner.一直使用这个版本的JUnit,在写简单的测试 ...
- JS splice() 定义和用法
定义和用法 splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目. 注释:该方法会改变原始数组. 语法 arrayObject.splice(index,howmany,item1, ...
- 关于 Dev中的GridControl 中 GridView 的 PopulateColumns() 方法
最近使用Dev控件,Gridview绑定数据源后不能显示数据,于是在网上查询,说是使用PopulateColumns()方法,可以显示数据.试了一下,管用. 于是在所有更新数据源数据后,都用上了这句话 ...
- 深入理解javascript原型和闭包
目录: 深入理解javascript原型和闭包(1)——一切都是对象 深入理解javascript原型和闭包(2)——函数和对象的关系 深入理解javascript原型和闭包(3)——prototyp ...
- 设计winform自带动态加载工具按钮和实现热键响应
1.初衷 主要是想设计一个自带添加工具按钮和按钮的快捷键的基窗体.这样以后所设计的窗体只要继承自这个窗体就可以实现热键响应和动态加工具按钮的功能了 写这边文章主要是为了以后使用的时候有个参考,因为这只 ...
- 协同开发中SVN的使用建议
协同开发中SVN的使用建议 1. 注意个人账户密码安全 各员工需牢记各自的账户和密码,不得向他人透漏,严禁使用他人账户进行SVN各项操作(主要考虑每个SVN账号的使用者的权限范围问题).如有忘记,请 ...