【问题标题】:Tolerant key lookup in std::mapstd::map 中的容错键查找
【发布时间】:2014-01-14 15:45:56
【问题描述】:

要求:

  1. 基于数字比较键(例如 std::map)对自身进行排序的容器
  2. 根据浮点容差检查键是否存在(例如 map.find() 并使用自定义比较器)
  3. 还有一个棘手的问题:比较器使用的浮点容差可能会由用户在运行时更改!

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

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 的其他帖子没有提供完整的解决方案。

【问题讨论】:

  • 您必须使用新的排序标准创建一个新地图,并使用旧地图中的元素填充它。但是您认为您的比较器满足严格的弱排序吗?这是一项要求,否则您的地图就会损坏。
  • 这样的比较器是行不通的:关联容器需要严格的排序。您应该能够使用默认排序获得所需的内容,使用lower_bound(两次,对于公差范围的每一端)而不是find。这也给出了您的第三个要求,因为您可以为每次查找指定容差。
  • 排序比较器与容错搜索比较器没有太大关系!
  • 要了解为什么严格排序很重要,请考虑将值 14.9991、15.0 和 15.0009 插入到您的地图中。根据插入顺序,您最终可能会得到 1 个或 2 个条目。搜索也可能会失败,但这更难证明。
  • “在某些情况下未定义”表示您的地图不能保证正常工作。在我的书中,这意味着它已经坏了。

标签: c++ map stl floating-point


【解决方案1】:

在创建 map 之后,您无法更改其顺序(即使此处的浮点类型,您也应该使用普通的旧 operator&lt;),甚至不能使用“宽容”比较运算符,因为这可能会违反 map 维持其状态所需的严格弱排序。

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

【讨论】:

  • Mark - 即将接受这一点,但我现在意识到根据 Mark Ransom 对我上述问题的评论,某些插入可能会失败。有没有办法防止这种情况发生?
  • @tc 如果您使用标准比较器,则不会发生这种情况。只需使用默认排序,仅在查找值时使用容差。
【解决方案2】:

不能map 实例化后更改元素的排序方式。如果您要找到一些技术技巧来做到这一点(例如实现一个自定义比较器,该比较器采用可以在运行时更改的容差),它会引发未定义的行为。

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

或者,您真正想要做的可能不是更改排序,而是保持排序并更改搜索参数。

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

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

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

由于这是一个家庭作业问题,我将把实际实现所有这些的繁琐细节留给你。 :)

【讨论】:

  • 对于宽容搜索,顺序不应该改变...(我知道标题有误导性,你回答了标题;但你没有回答实际问题,即XY问题)跨度>
  • @leemes:嗯,也许我回答了一个没有被问到的问题。
  • @leemes 这个问题确实询问了关于在创建后更改地图的顺序,并提到map::find 作为一种找到具有容差的键的方法。可能是一个 XY 问题,但我认为这个答案足够丰富,值得我 +1。
  • @leemes:怎么样?
  • @John - 明白。我提到如果绝对必要,愿意重新创建地图,但必须有更好的方法。
【解决方案3】:

与其使用带有容差的比较器(这会以微妙的方式失败),不如使用从浮点值派生的一致键。使用舍入使浮点值保持一致。

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

【讨论】:

  • 同意 - 我也想到了这一点,实际上可能会以这种方式实现它。当然,如何进行舍入可以根据我的要求用户定义。
  • 当它们跨越由舍入创建的人工边界之一时,无法找到比容差更接近的值。例如,1.234375 和 1.234619140625 的差异远小于容差,但此函数的分箱方式不同。
  • @EricPostpischil,如果您想保持严格的排序,这是不可避免的。
  • @MarkRansom:其他答案已经展示了如何保持严格的排序并以不存在此问题的不同方式提供第三个要求。
【解决方案4】:

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

您应该做的是使用 operator&lt; 进行浮点数的默认排序,即根本不提供任何自定义比较器。然后,对于查找,不要使用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

【讨论】:

    【解决方案5】:

    最好不要管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的末尾或者直到它们的值超过@987654327 @。

    这基本上意味着insert/add/[]/whatever 函数的行为不是基于tolerance,因此存储值没有真正的问题。

    如果你担心元素之间的距离太近,你可以从val开始搜索,然后逐渐增加toleration,直到找到用户想要的。

    【讨论】:

      猜你喜欢
      • 2018-04-21
      • 2010-12-21
      • 1970-01-01
      • 2010-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多