将点作为C++ map容器key值时的踩坑记录

1.背景

空间点具有X,Y,Z坐标等数据,一些情况下我们需要将点作为map容器的key值,比如识别重复点或处理轮廓等情况。

2.问题

将点作为map的key值,需要自定义比较器或者重载实现点类的小于<操作运算符,判断规则是a < b 和 b < a都为false时,认为a和b相等。

一种比较器或小于运算符重载实现如下,即判断两点在一定误差范围内时,两点相等。

bool PointBorderData::operator< (const PointBorderData& other) const
{
    if (abs(point.X - other.point.X) > 1e-3)
        return point.X < other.point.X;

    if (abs(point.Y - other.point.Y) > 1e-3)
        return point.Y < other.point.Y;

    if (abs(point.Z - other.point.Z) > 1e-3)
        return point.Z < other.point.Z;

    //  相等就 return false
    return false;
}

然后这种实现可能会有问题,有些情况下“相同的点”(在误差范围内)却在map容器中都保留了,经过判断,“认为他们不同”,这是什么原因呢?

聪明的你可能现在也不会想到,直到自己遇到并处理此类问题,或继续看完本文。

3.原因

红框中选中的两个点在误差范围内,应当识别为相同点,在map容器中保留一份,但实际缺各自都被保留了。我们仔细观察其中间的点,嗯,是不是法线问题了?

C++ map容器底层是红黑树,容器元素正向是升序排列,即正序遍历时得到的元素是从最小到最大。

在这里我们姑且称上述3个点为第0、1、2点,第1个点比第0个点小,为什么反而排在第0个点后面?我们对照着前面的代码来分析,第0和1个点x坐标在误差范围内,所以比较y坐标,y值超过了误差范围,且第1点的y值小于第0点,即认为第1点小于第0点,所以它排在了第0点后面。到这里好像也没啥问题,我们继续看~

第3点与第2点比较后,第3点大于第2点,这样好像得到了结论,第0点 < 第1点 < 第2点,得到了3个“不同”的点,那为什么不让第2点和第0点直接比较呢?这样就能识别出它俩相同。

好问题,map底层是红黑树,看下图,

在某些情况下,比如“尝试插入”时的顺序为第1点,第0点,第2点时。在插入第0点时(第1点已插入),其经过一顿比较器执行后需要和第1点比较,因为比较后第0点 < 第1点,所以第0点放在了1点的左分支, 在插入第2点时,(同样经过一顿比较器执行后)到了要和第1点比较的时刻,比较后第1点 < 第2点,第2点放在了第1点的右分支上,整个过程,第2点根本没有机会和第0点比较!所以没有被识别为相同点!

好吧,终于知道问题了。改成下面这样行吗?答案时仍然可能会有问题,具体原因可以自行脑补。

bool PointBorderData::operator< (const PointBorderData& other) const
{
    if (abs(point.X - other.point.X) > 1e-3)
        return point.X < other.point.X;

    if (abs(point.Y - other.point.Y) > 1e-3)
        return point.X == other.point.X ? point.Y < other.point.Y : point.X < other.point.X;

    if (abs(point.Z - other.point.Z) > 1e-3)
        return point.X == other.point.X ? (point.Y == other.point.Y ? point.Z < other.point.Z : point.Y < other.point.Y) : point.X < other.point.X;

    //  相等就 return false
    return false;
}

4.总结

由于点在一定误差内判断为相同,比较好的处理方式是将点坐标保留一定的精度(比如乘1e3,再四舍五入取整)后进行严格数值的大小判断

在有些情况下需要“严格彻底”的识别一定误差内的相同点,比如路径搜寻,就需要有逻辑严格的代码实现,有些情况识别不彻底也不会出错(比如点合并),最多就是多一些“相同”点。最好还是用逻辑准确的代码实现,避免一些超出我们当前认知的“不明确行为”。

欢迎交流:公众号:geometrylib

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/666490.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Python发送企业微信消息

大家好&#xff0c;在本文中&#xff0c;我们将探讨如何使用 Python 发送企业微信消息。将详细说明如何通过 Python 脚本实现消息的发送。无论是希望自动化某些任务&#xff0c;还是想要快速地向团队发送实时通知&#xff0c;本文都将为您提供一站式的解决方案。 企业微信提供了…

二叉树—堆(C语言实现)

一、树的概念及结构 1.树的概念 树是一种非线性的数据结构&#xff0c;它是有n&#xff08;n > 0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一颗倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下。 ● 有一个特殊的结点…

【SQL学习进阶】从入门到高级应用(六)

文章目录 ✨数据处理函数✨数字相关✨rand()和rand(x)✨round(x)和round(x,y)四舍五入✨truncate(x, y)舍去✨ceil与floor ✨空处理✨日期和时间相关函数✨获取当前日期和时间✨获取当前日期✨获取当前时间✨获取单独的年、月、日、时、分、秒✨date_add函数✨date_format日期格…

Netty SSL双向验证

Netty SSL双向验证 1. 环境说明2. 生成证书2.1. 创建根证书 密钥证书2.2. 生成请求证书密钥2.3. 生成csr请求证书2.4. ca证书对server.csr、client.csr签发生成x509证书2.5. 请求证书PKCS#8编码2.6. 输出文件 3. Java代码3.1. Server端3.2. Client端3.3. 证书存放 4. 运行效果4…

C++ 多重继承的内存布局和指针偏移

在 C 程序里&#xff0c;在有多重继承的类里面。指向派生类对象的基类指针&#xff0c;其实是指向了派生类对象里面&#xff0c;该基类对象的起始位置&#xff0c;该位置相对于派生类对象可能有偏移。偏移的大小&#xff0c;等于派生类的继承顺序表里面&#xff0c;排在该类前面…

Python中Web开发-Django框架

大家好&#xff0c;本文将带领大家进入 Django 的世界&#xff0c;探索其强大的功能和灵活的开发模式。我们将从基础概念开始&#xff0c;逐步深入&#xff0c;了解 Django 如何帮助开发人员快速构建现代化的 Web 应用&#xff0c;并探讨一些最佳实践和高级技术。无论是初学者还…

SM2259XT2、SM2259XT3量产工具开启“调整不对称CH/CE组态”功能

自己摸索的SM2259XT2、SM2259XT3量产工具开启“调整不对称CH/CE组态”功能。在量产部落下载SM2259XT2量产工具后&#xff0c;解压量产工具压缩包&#xff0c;找到并打开量产工具文件夹中的“UFD_MP”文件夹&#xff0c;用记事本或者Notepad打开“Setting.set”文件&#xff0c;…

Vue3实战笔记(53)—奇怪+1,VUE3实战模拟股票大盘工作台

文章目录 前言一、实战模拟股票大盘工作台二、使用步骤总结 前言 实战模拟股票大盘工作台 一、实战模拟股票大盘工作台 接上文&#xff0c;这两天封装好的组件直接应用,上源码&#xff1a; <template><div class"smart_house pb-5"><v-row ><…

Docker管理工具Portainer忘记admin登录密码

停止Portainer容器 docker stop portainer找到portainer容器挂载信息 docker inspect portainer找到目录挂载信息 重置密码 docker run --rm -v /var/lib/docker/volumes/portainer_data/_data:/data portainer/helper-reset-password生成新的admin密码&#xff0c;使用新密…

若依分页问题排查

无限分页数据返回 一、问题排查1.1 代码排查1.2 sql排查1.3 原因分析 二、问题修复 项目使用了 若依的框架&#xff0c;前端反馈了一个问题&#xff0c;总记录条数只有 48条的情况下&#xff0c;传入的 页数时从6~~无穷大&#xff0c;每页大小为10, 此时还能返回数据&#xff0…

SpringMVC框架学习笔记(四):模型数据 以及 视图和视图解析器

1 模型数据处理-数据放入 request 说明&#xff1a;开发中, 控制器/处理器中获取的数据如何放入 request 域&#xff0c;然后在前端(VUE/JSP/...)取出显 示 1.1 方式 1: 通过 HttpServletRequest 放入 request 域 &#xff08;1&#xff09;前端发送请求 <h1>添加主人…

浅谈线性化

浅谈线性化 原文&#xff1a;浅谈线性化 - 知乎 (zhihu.com) All comments and opinions expressed on Zhihu are mine alone and do not necessarily reflect those of my employers, past or present. 本文内容所有内容仅代表本人观点&#xff0c;和Mathworks无关 (这里所说…

视频汇聚EasyCVR视频监控云平台对接GA/T 1400视图库对象和对象集合XMLSchema描述

GA/T 1400协议主要应用于公安系统的视频图像信息应用系统&#xff0c;如警务综合平台、治安防控系统、交通管理系统等。在城市的治安监控、交通管理、案件侦查等方面&#xff0c;GA/T 1400协议都发挥着重要作用。 以视频汇聚EasyCVR视频监控资源管理平台为例&#xff0c;该平台…

探索UWB模块的多功能应用——UWB技术赋能智慧生活

超宽带&#xff08;Ultra-Wideband, UWB&#xff09;技术&#xff0c;凭借其高精度、低功耗和强抗干扰能力&#xff0c;正在成为智能家居领域的一项关键技术。UWB模块的应用不仅提高了智能家居设备的性能&#xff0c;还为家庭安全、设备管理和用户体验带来了显著的改善。 UWB模…

java基础-chapter15(io流)

io流&#xff1a;存储和读取数据的解决方案 I:input O:output io流的作用&#xff1a;用于读写数据&#xff08;本地文件,网络&#xff09; io流按照流向可以分为&#xff1a; 输出流&#xff1a;程序->文件 输入流&#xff1a;文件->程序 io流按照操作文件…

Qt Creator(Qt 6.6)拷贝一行

Edit - Preference - Environment&#xff1a; 可看到&#xff0c;拷贝一行的快捷键是&#xff1a; ctrl Ins

使用KEPServer连接欧姆龙PLC获取对应标签数据(标签值类型改为字符串型)

1.创建通道&#xff08;通道&#xff09;&#xff0c;&#xff08;选择对应的驱动&#xff0c;跟当前型号PLC型号对应&#xff09;。 2.创建设备&#xff0c;&#xff08;填入IP地址以及欧姆龙的默认端口号&#xff1a;44818&#xff09; 3.创建对应的标签。这里关键讲诉下字…

医院该如何应对网络安全?

在线医生咨询受到很多人的关注&#xff0c;互联网医疗行业的未来发展空间巨大&#xff0c;但随着医院信息化建设高速发展 医院积累了大量的患者基本信息、化验结果、电子处方、生产数据和运营信息等数据 这些数据涉及公民隐私、医院运作和发展等多因素&#xff0c;医疗行业办…

[C/C++]_[初级]_[在Windows平台上导出DLL函数接口的一些疑问]

场景 最近看了《COM本质论》里关于如何设计基于抽象基类作为二进制接口,把编译器和链接器的实现隐藏在这个二进制接口中,从而使用该DLL时不需要重新编译。在编译出C接口时,发现接口名直接是函数名,比如BindNativePort,怎么不是_BindNativePort?说明 VC++导出的函数默认是使…

Notepad++ 常用

File Edit search view Encoding Language Settings Tools Macro Run Plugins Window 文件 编辑 搜索 视图 编码 语言 设置 工具 宏 运行 插件 窗口 快捷方式 定位行 &#xff1a;CTRL g查找&#xff1a; CTRL F替换&am…