如何使用自定义类/比较器作为键创建映射

时间:2017-12-02 20:55:18

标签: c++ algorithm vector comparator stdmap

我有一个名为ItemType的班级。它有两个成员 - 都是double,名为m_tm_f。如果这两个成员在相应的容忍水平内彼此不同,则认为两个类型ItemType是相等的。通过这种逻辑,比较器功能也是如此定义的。但是,当我将此类型的对象作为键插入到地图中时,地图中只生成一个键,即使至少应存在三个这样的键:

#include <iostream>
#include <string>
#include <map>
#include <cmath>  
#include <vector>

using namespace std;

        class ItemKey
        {
        public:
            ItemKey(double t, double f)
            {
                m_t = t;
                m_f = f;           
            }

            double m_t;
            double m_f;
            double m_tEpsilon = 3;
            double m_fEpsilon = 0.1;

            bool operator<(const ItemKey& itemKey) const
            {
                int s_cmp = (abs(itemKey.m_f - m_f) > m_fEpsilon);
                if (s_cmp == 0)
                {
                    return (abs(itemKey.m_t - m_t) > m_tEpsilon);
                }
                return s_cmp < 0;
            }
        };

int main()
{
    // The pairs are the respective values of m_t and m_f.
    vector<pair<double, double>> pairs;

    // These two should belong in one bucket -> (109.9, 9.0), because m_f differs by 0.09 and m_t differs by just 1
    pairs.emplace_back(109.9, 9.0);
    pairs.emplace_back(110.9, 9.09);

    // This one is separate from above two beause even though m_t is in range, m_f is beyong tolerance level
    pairs.emplace_back(109.5, 10.0);

    // Same for this as well, here both m_t and m_f are beyong tolerance of any of the two categories found above
    pairs.emplace_back(119.9, 19.0);

    // This one matches the second bucket - (109.5, 10.0)
    pairs.emplace_back(109.9, 10.05);

    // And this one too.
    pairs.emplace_back(111.9, 9.87);

    map<ItemKey, size_t> itemMap;

    for (const auto& item: pairs)
    {
        ItemKey key(item.first, item.second);
        auto iter = itemMap.find(key);
        if (iter  == itemMap.end())
        {
            itemMap[key] = 1;
        }
        else
        {
            itemMap[iter->first] = itemMap[iter->first] + 1;
        }
    }

    // The map should have three keys - (109.9, 9.0) -> count 2, (109.5, 10.0) -> count 3 and (119.9, 19.0) -> count 1
    cout << itemMap.size();

}

然而,地图似乎只有一把钥匙。如何使其按预期工作?

1 个答案:

答案 0 :(得分:4)

为什么你的版本不起作用?

您最好创建自己的比较功能。要回答您的问题,您的operator<()函数会出现错误,只有在m_f超出容差且m_t在容差范围内时才会返回true,我猜测的是不是你想要的。我们来看看。

int s_cmp = (abs(itemKey.m_f - m_f) > m_fEpsilon);

以上几行基本上是检查this->m_fitemKey.m_f是否在彼此的容忍范围内(意味着彼此相等)。这可能是预期的。然后你说

if (s_cmp == 0)
{
    return (abs(itemKey.m_t - m_t) > m_tEpsilon);
}

如果s_cmptrue,那么它的值为10的值为false(意味着他们在彼此的容忍范围内)。如果m_t值在容差范围内,则返回true。到目前为止,如果true 相等(根据容差)并且m_f 相等,则返回m_t根据宽容)。然后你的最后一行代码

return s_cmp < 0;

将始终返回true,因为转换为整数的布尔值不能为负数。

如何让它运作?

#include <iostream>
#include <string>
#include <map>
#include <cmath>  
#include <vector>

struct ItemKey
{
    double m_t;
    double m_f;
    static constexpr double t_eps = 3;
    static constexpr double f_eps = 0.1;

    ItemKey(double t, double f) : m_t(t), m_f(f) {}

    bool operator<(const ItemKey& other) const
    {
        // Here it is assumed that f_eps and t_eps are positive
        // We also ignore overflow, underflow, and NaN
        // This is written for readability, and assumed the compiler will be
        // able to optimize it.
        auto fuzzy_less_than = [] (double a, double b, double eps) {
          return a < b - eps;
        };
        bool f_is_less_than    = fuzzy_less_than(this->m_f, other.m_f, f_eps);
        bool f_is_greater_than = fuzzy_less_than(other.m_f, this->m_f, f_eps);
        bool f_is_equal        = !f_is_less_than && !f_is_greater_than;
        bool t_is_less_than    = fuzzy_less_than(this->m_t, other.m_t, t_eps);

        return f_is_less_than || (f_is_equal && t_is_less_than);
    }
};

int main()
{
    using namespace std;

    // The pairs are the respective values of m_t and m_f.
    vector<pair<double, double>> pairs;

    // These two should belong in one bucket
    // -> (109.9, 9.0), because m_f differs by 0.09 and m_t differs by just 1
    pairs.emplace_back(109.9, 9.0);
    pairs.emplace_back(110.9, 9.09);

    // This one is separate from above two beause even though m_t is in range,
    // m_f is beyong tolerance level
    pairs.emplace_back(109.5, 10.0);

    // Same for this as well, here both m_t and m_f are beyong tolerance of any
    // of the two categories found above
    pairs.emplace_back(119.9, 19.0);

    // This one matches the second bucket - (109.5, 10.0)
    pairs.emplace_back(109.9, 10.05);

    // And this one too.
    pairs.emplace_back(111.9, 9.87);

    map<ItemKey, size_t> itemMap;

    for (const auto& item: pairs)
    {
        ItemKey key(item.first, item.second);
        auto iter = itemMap.find(key);
        if (iter  == itemMap.end())
        {
            itemMap[key] = 1;
        }
        else
        {
            itemMap[iter->first] = itemMap[iter->first] + 1;
        }
    }

    // The map should have three keys
    // - (109.9, 9.0) -> count 2
    // - (109.5, 10.0) -> count 3
    // - (119.9, 19.0) -> count 1
    cout << itemMap.size();

    cout << "itemMap contents:" << endl;
    for (auto& item : itemMap) {
        cout << "  (" << item.first << ", " << ")" << endl;
    }

    return 0;
}

上面我改变了一些事情。我有一些与编程错误无关的建议:

  1. 不要将布尔值存储到整数变量中。 这就是C ++引入bool类型的原因。
  2. 以编译器的方式将代码编写为可读 可以轻松优化。您可能会注意到我使用了lambda表达式 和多个布尔。智能编译器将内联调用 lambda表达式,因为它仅在本地范围内使用。 智能编译器也可以简化布尔逻辑并实现它 对我来说很有意义。
  3. m_tEpsilonm_fEpsilon可能并不好 类的可变变量。事实上,如果一个人可能会很糟糕 对象与另一个对象有不同的epsilon。如果那是 case,当你使用<运算符时使用哪种情况?为了这 原因,我在课堂上将它们设置为static const变量。
  4. 对于构造函数,最好初始化变量 初始化列表而不是构造函数的主体。那 除非你正在进行动态资源分配,否则你会这样做 想在构造函数中执行它,并确保如果清理它 你最终会抛出异常(最好使用RAII 图案)。我开始离主题太远了:))
  5. 即使classstruct基本相同,但除外 默认保护级别(class默认为私有 默认情况下,struct是公开的)。将它作为一种惯例是惯例 struct是否要直接访问成员变量。虽然, 在这种情况下,我可能会将您的类设置为不可变。去做 那样,将m_tm_f设置为私有变量并拥有一个getter m()f()。修改ItemKey可能是个坏主意 插入后的地图中的实例。
  6. 此方法的潜在问题

    您在此处遇到的问题之一是它将取决于您添加元素的顺序。考虑添加以下对:(3.0, 10.0) (5.0, 10.0) (7.0, 10.0)。如果我们按此顺序添加它们,我们将获得(3.0, 10.0) (7.0, 10.0),因为(5.0, 10.0)被视为等于(3.0, 10.0)。但是,如果我们先插入(5.0, 10.0),然后插入其他两个,该怎么办?那么列表只有一个元素(5.0, 10.0),因为其他元素的麻烦将被视为等于此元素。

    相反,我建议您使用std::multiset代替,当然这取决于您的申请。考虑这些测试:

    void simple_test_map() {
        std::map<ItemKey, size_t> counter1;
        counter1[{3.0, 10.0}] += 1;
        counter1[{5.0, 10.0}] += 1;
        counter1[{7.0, 10.0}] += 1;
        for (auto &itempair : counter1) {
           std::cout << "simple_test_map()::counter1: ("
                     << itempair.first.m_t << ", "
                     << itempair.first.m_f << ") - "
                     << itempair.second << "\n";
        }
        std::cout << std::endl;
    
        std::map<ItemKey, size_t> counter2;
        counter2[{5.0, 10.0}] += 1;
        counter2[{3.0, 10.0}] += 1;
        counter2[{7.0, 10.0}] += 1;
        for (auto &itempair : counter2) {
           std::cout << "simple_test_map()::counter2: ("
                     << itempair.first.m_t << ", "
                     << itempair.first.m_f << ") - "
                     << itempair.second << "\n";
        }
        std::cout << std::endl;
    }
    

    输出:

    simple_test_map()::counter1: (3, 10) - 2
    simple_test_map()::counter1: (7, 10) - 1
    
    simple_test_map()::counter2: (5, 10) - 3
    

    对于multiset变体:

    void simple_test_multiset() {
        std::multiset<ItemKey> counter1 {{3.0, 10.0}, {5.0, 10.0}, {7.0, 10.0}};
        for (auto &item : counter1) {
           std::cout << "simple_test_multiset()::counter1: ("
                     << item.m_t << ", "
                     << item.m_f << ")\n";
        }
        std::cout << std::endl;
        std::multiset<ItemKey> counter2 {{5.0, 10.0}, {3.0, 10.0}, {7.0, 10.0}};
        for (auto &item : counter2) {
           std::cout << "simple_test_multiset()::counter2: ("
                     << item.m_t << ", "
                     << item.m_f << ")\n";
        }
        std::cout << std::endl;
        std::cout << "simple_test_multiset()::counter2.size() = "
                  << counter2.size() << std::endl;
        for (auto &item : counter1) {
           std::cout << "simple_test_multiset()::counter2.count({"
                     << item.m_t << ", "
                     << item.m_f << "}) = "
                     << counter1.count(item) << std::endl;
        }
        std::cout << std::endl;
    }
    

    此输出

    simple_test_multiset()::counter1: (3, 10)
    simple_test_multiset()::counter1: (5, 10)
    simple_test_multiset()::counter1: (7, 10)
    
    simple_test_multiset()::counter2: (5, 10)
    simple_test_multiset()::counter2: (3, 10)
    simple_test_multiset()::counter2: (7, 10)
    
    simple_test_multiset()::counter2.count({3, 10}) = 2
    simple_test_multiset()::counter2.count({5, 10}) = 3
    simple_test_multiset()::counter2.count({7, 10}) = 2
    simple_test_multiset()::counter2.size() = 3
    

    请注意,count()此处返回多重集中的元素数量,这些元素被认为等于传入的ItemKey。这可能对您想要询问的情况有利我的观点是否符合我对新观点的容忍度?&#34;

    祝你好运!