std :: map中的容错键查找

时间:2014-01-14 15:45:57

标签: c++ map stl floating-point

要求:

  
      
  1. 基于数字比较键(例如std :: map)
  2. 对容器进行排序的容器   
  3. 根据浮动容差检查密钥是否存在(例如map.find()并使用自定义比较器)
  4.   
  5. 和棘手的问题:比较器使用的浮动公差可以由用户在运行时更改!
  6.   

前两个可以使用带有自定义比较器的地图来完成:

struct floatCompare : public std::binary_function<float,float,bool>
{
    bool operator()( const float &left, const float &right ) const
    {
        return (fabs(left - right) > 1e-3) && (left < right);
    }
};

typedef std::map< float, float, floatCompare > floatMap;

使用此实现,floatMap.find(15.0001)将在地图中找到15.0。

但是,假设用户不希望浮动容差为1e-3。 使比较器函数在运行时使用可变容差的最简单方法是什么?我不介意每次更新epsilon时都会根据新的比较器重新创建和重新排序地图。

初始化here后使用浮点数作为键here进行修改的其他帖子未提供完整的解决方案。

5 个答案:

答案 0 :(得分:8)

创建map之后你无法改变它的顺序(你应该只使用普通的旧operator<这里的浮点类型),你甚至不能使用“容忍”比较运算符,因为它可能违反map所需的严格弱顺序以维持其状态。

但是可以使用lower_boundupper_bound进行宽容搜索。要点是你要创建一个与equal_range非常相似的包装函数,为{value-tolerance“执行lower_bound,然后为”value + tolerance“执行upper_bound并查看它是否创建符合条件的非空范围值。

答案 1 :(得分:5)

无法更改map中元素在实例化后的排序方式。如果你要找到一些技术黑客(比如实现一个可以在运行时改变容差的自定义比较器),它会引起未定义的行为。

更改排序的主要替代方法是使用不同的排序方案创建另一个地图。这个其他地图可以是索引地图,其中键以不同的方式排序,并且值不是元素本身,而是主映射中的索引。

或许也许你真正想做的不是改变顺序,而是保持顺序并改变搜索参数。

你能做到,有几种方法可以做到。

一个是简单地使用map::lower_bound - 一次使用公差的下限,一次使用公差的上限,刚好超过公差的结束。例如,如果要查找容差为1e-5的15.0。您可以使用14.99995 lower_bound然后再使用15.00005(我的数学可能会在这里)找到该范围内的元素。

另一种方法是将std::find_if与自定义函子lambda或std::function一起使用。您可以以构造中的公差和值来声明仿函数,并在operator()中执行检查。

由于这是一个家庭作业问题,我会留下实际执行所有这些的繁琐细节。 :)

答案 2 :(得分:1)

使用简单的自定义比较器无法实现这一点,即使可以在定义后更改它,也不能使用新的比较器。事实是:“宽容比较器”并不是真正的比较器。对于三个值,a < c(差异足够大)但a < bb < c都不可能(两者差异太小)。示例:a = 5.0, b = 5.5, c = 6.0, tolerance = 0.6

您应该做的是使用operator<对浮点数进行默认排序,即只是不提供任何自定义比较器。然后,对于查找,请不要使用find,而应使用lower_boundupper_bound根据容差修改值。这两个函数调用将为您提供两个迭代器,这两个迭代器定义将使用此容差接受的序列。如果此序列为空,则显然未找到密钥。

然后,您可能希望获得最接近要搜索的值的键。如果这是真的,那么你应该找到这个子序列的min_element,使用比较器来考虑密钥和要搜索的值之间的差异

template<typename Map, typename K>
auto tolerant_find(const Map & map, const K & lookup, const K & tolerance) -> decltype(map.begin()) {
    // First, find sub-sequence of keys "near" the lookup value
    auto first = map.lower_bound(lookup - tolerance);
    auto last = map.upper_bound(lookup + tolerance);

    // If they are equal, the sequence is empty, and thus no entry was found.
    // Return the end iterator to be consistent with std::find.
    if (first == last) {
        return map.end();
    }

    // Then, find the one with the minimum distance to the actual lookup value
    typedef typename Map::mapped_type T;
    return std::min_element(first, last, [lookup](std::pair<K,T> a, std::pair<K,T> b) {
        return std::abs(a.first - lookup) < std::abs(b.first - lookup);
    });
}

演示:http://ideone.com/qT3JIa

答案 3 :(得分:1)

不是使用具有容差的比较器,而是以微妙的方式失败,只需使用从浮点值派生的一致键。使用舍入使浮点值保持一致。

inline double key(double d)
{
    return floor(d * 1000.0 + 0.5);
}

答案 4 :(得分:0)

最好不要单独留下std::map类(好吧,部分至少),然后编写自己的类来实现你提到的三种方法。

template<typename T>
class myMap{
 private:
   float tolerance;
   std::map<float,T> storage;

 public:
   void setTolerance(float t){tolerance=t;};
   std::map<float,T>::iterator find(float val); // ex. same as you provided, just change 1e-3 for tolerance
   /* other methods go here */
};

话虽如此,我认为您需要重新创建容器并根据容差对其进行排序。

  

根据浮动容差检查密钥是否存在

仅表示您必须检查是否存在元素。地图中元素的位置不应更改。您可以从val-tolerance开始搜索,当找到元素时(函数find返回iterator),获取下一个元素,直到map结束}或者直到他们的值超过val+tolerance

这基本上意味着insert / add / [] /任何函数的行为都不是基于tolerance,所以存储没有真正的问题价值观。

如果您害怕元素彼此太近,您可能希望从val开始搜索,然后逐渐增加toleration,直到达到用户所需的值。< / p>