是否有 STL 算法可以根据某些容差来查找容器中是否存在元素?

Posted

技术标签:

【中文标题】是否有 STL 算法可以根据某些容差来查找容器中是否存在元素?【英文标题】:Is there an STL algorithm to find if an element is present in a container based on some tolerance? 【发布时间】:2016-07-25 18:19:23 【问题描述】:

我要解决的问题如下:我有一个浮点容器(双向量向量):

std::vector<std::vector<double>> dv  0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 ;

然后,假设我有一个新点(双向量):

std::vector<double> v1 0.0001, 1.0;

我想根据一些容差检查 dv 容器中是否存在点 v1。两个向量之间的距离被计算为欧几里得距离。

我已经查看了相关的问题和答案:

How to find if an item is present in a std::vector? check if a std::vector contains a certain object?

并且还尝试使用std::find_if(),但没有成功,因为它只接受一元谓词

目前我想出了一个临时解决方案。首先,我创建了一个通用函数来查找两个向量之间的欧几里得距离:

template <typename InputIt1, typename InputIt2>
double EuclideanDistance(InputIt1 beg1, InputIt1 end1, InputIt2 beg2) 
  double val = 0.0;
  while (beg1 != end1) 
    double dist = (*beg1++) - (*beg2++);
    val += dist*dist;
  
  return val > 0.0? sqrt(val) : 0.0;

其次,我创建了check_if函数来根据容差(Epsilon)检查容器中是否存在元素:

template <typename Container, typename Element>
bool check_if(const Container& c, const Element& el,
              const double Epsilon = 0.001) 
  auto pos = c.begin();
  for (; pos != c.end(); ++pos) 
    if (EuclideanDistance(pos->begin(), pos->end(), el.begin()) < Epsilon) 
      return true;
    
  
  return false;

然后我可以在这样的上下文中使用我的代码:

// Check if container contains v1 using check_if()
if (check_if(dv, v1)) 
  std::cout << "Using check_if() - Container contains v1\n";

所以我的问题如下:

    是否有内部的 STL 算法来实现相同的目标?如果没有,我该如何改进我的代码?例如,我不确定如何在check_in() 中使用任何距离函数而不是EuclideanDistance()? 我想知道 AshleysBrain 建议 (https://***.com/a/3451045/3737891) 使用 std::set 而不是 std::vector 是否会对浮点容器产生影响?

【问题讨论】:

我会将点存储在 std::pair&lt;double&gt;std::array&lt;double,2&gt; - std::vector 对于固定大小的数据来说太过分了,而且您需要验证它是否始终有 2 个元素 使用捕获Epsilon的lambda并将该lambda传递给find_if std::find_if 再试一次,因为我看不出有任何原因它不起作用。 使用 lambda,类似于:std::find_if(dv.begin(), dv.end(), [&amp;](const auto&amp;inner) return EuclideanDistance(inner.begin(), inner.end(), v1.begin()) &lt; Epsilon; ) @Jarod42 它不能是 v1.begin() - OP 在该向量中存储点 xy 坐标 【参考方案1】:

并且还尝试使用 std::find_if() 没有任何成功,因为它只接受一元谓词。

这就是你想要的。单个参数将是您正在查看的向量的元素。

std::vector<std::vector<double>> dv
    0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0;

std::vector<double> v1 0.0001, 1.0;

auto find_result =
    std::find_if(begin(dv), end(dv), [&v1](std::vector<double> const &dve) 
      assert(dve.size() == v1.size());
      return EuclideanDistance(begin(dve), end(dve), begin(v1)) < 0.001;
    );

另一方面,如果您的点都是固定维度,那么使用vector&lt;double&gt; 可能不是正确的表示。 tuple&lt;double,double&gt;array&lt;double 2&gt; 或具有重载运算符的自定义类可能都是合适的替代品。

【讨论】:

在我的例子中,所有点都是 n 维向量。但是,只有在执行期间,我才会知道这些点的确切尺寸 (n),即,对于不同的运行,这些点的尺寸可能不同。然而,在同一次运行中,所有点都具有相同的维度。因此,我的目标是选择正确的容器来存储这些点,然后使用一种有效的算法来检查新点是否已经在这个容器中。【参考方案2】:

是否有内部的 STL 算法来实现相同的目标?如果不, 我怎样才能改进我的代码?例如,我不确定如何使用 check_in() 中的任何距离函数而不是 EuclideanDistance()?

不完全是 STL(如果我没记错的话),但是...

正如其他人所建议的那样,对于点坐标,您应该考虑 std::tuple&lt;double, double, ...&gt;,而不是 std::vector&lt;double&gt;,或者,如果它们是 2D 点(你的情况,如果我没记错的话),std::pair&lt;double, double&gt;

但我建议您使用另一个 tamplate 类(但仅适用于 2D 点,不适用于 3D 点):std::complex&lt;T&gt;

您的dv 可能是

std::vector<std::complex<double>> dv  0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 ;

新点

std::complex<double>  v1 0.0001, 1.0;

另一个建议:避免平方根(计算成本很高);检查距离的平方与Epsilon 的平方;使用std::norm 的几个复数之差,您可以准确地得到(如果我没记错的话)距离的平方

使用std::complex&lt;double&gt;,bames53的例子就变得简单了

#include <vector>
#include <complex>
#include <iostream>
#include <algorithm>

int main()
 
   std::vector<std::complex<double>> dv
     0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 ;

   std::complex<double> v1 0.0001, 1.0;

   auto find_result =
      std::find_if(begin(dv), end(dv), [&v1](std::complex<double> const &dve)
                    return std::norm(v1-dve) < 0.000001; );

   std::cout << *find_result << std::endl;  // print (0,1)

   return 0;
 

--- 编辑---

我想知道 AshleysBrain 的建议是否 (https://***.com/a/3451045/3737891) 改为使用 std::set std::vector 的容器可能会产生任何影响 浮点数?

我认为“浮点”组件对于在std::vectorstd::set 之间进行选择并不重要。

例如,如果(对于您的应用程序)保持点的插入顺序很重要,您应该使用std::vector(或std::queue);如果您必须进行大量搜索,我认为std::set 更好(或者std::multiset 如果您使用带有molteplicity 的点)。

如果你决定使用std::complex&lt;double&gt;,我给你另一个建议:看看std::valarray

我不知道你到底想用你的积分做什么,但是……我想这很有用。

p.s.:对不起我的英语不好

【讨论】:

【参考方案3】:

根据您的评论(“在我的情况下,点可以是任何维数向量......”)我知道将std::complex&lt;double&gt; 用于点坐标(或std::pair&lt;double, double&gt;,或std::tuple&lt;double, double, ...&gt;)的建议不适用。

所以我建议使用std::vector&lt;double&gt;,而不是std::array&lt;double, N&gt;,其中N 是点的维度。

我坚持我的建议以避免平方根并检查距离平方与Epsilon 的平方。

我的第三个建议是按以下方式使用std::inner_product()(以N == 3 为例)

#include <vector>
#include <array>
#include <numeric>
#include <iostream>
#include <algorithm>

int main()
 
   std::vector<std::array<double, 3>> dv
     0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
      0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
      1.0, 1.0, 0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 1.0, 1.0, 1.0, 1.0 ;

   std::array<double, 3> v1  0.0001, 1.0, -0.0001 ;

   auto result = std::find_if(dv.cbegin(), dv.cend(),
           [&v1](std::array<double, 3> const & dve)
             return std::inner_product(v1.cbegin(), v1.cend(),
                 dve.cbegin(), 0.0, std::plus<double>(),
                 [](double d1, double d2) auto d = d1-d2; return d*d; )
                 < 0.00001; );

   std::cout << "result: ";

   for ( const auto & d : *result )
      std::cout << '(' << d << ')';

   std::cout << std::endl;

   return 0;
 

输出是

result: (0)(1)(0)

再次对不起我的英语。

【讨论】:

以上是关于是否有 STL 算法可以根据某些容差来查找容器中是否存在元素?的主要内容,如果未能解决你的问题,请参考以下文章

条目1《慎重选择容器类型》

C++ STL与迭代器

STL算法总结之查找算法示例

是否有类似 STL 的函数来用索引的某些函数填充数组?

STL关联容器

STL关联容器