簡體   English   中英

如何以自定義類/比較器為鍵創建地圖

[英]How to create a map with custom class/comparator as key

我有一個名為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個鍵。 如何使它按預期工作?

為什么您的版本不起作用?

創建自己的比較功能的過程做得不錯。 要回答您的問題,您的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 ,那么它將值1 ,這將有一個值0false (這意味着它們不是彼此的公差范圍內)。 如果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與另一對象的epsilon不同,那可能是不好的。 如果是這種情況,那么在執行<運算符時會使用哪個? 因此,我在類中將它們設置為static const變量。
  4. 對於構造函數,最好在構造函數列表中而不是在構造函數的主體中初始化變量。 除非您正在進行動態資源分配,否則您將希望在構造函數中進行此操作,並確保在最終拋出異常(最好使用RAII模式)時將其清理干凈。 我開始變得太離題了:)
  5. 即使classstruct除了默認的保護級別(在默認情況下, class是私有的,而struct在默認情況下是公共的)之外,基本上是相同的。 如果要直接訪問成員變量,通常將其作為結構。 盡管在這種情況下,我可能會將您的類設置為不可變的。 為此,請將m_tm_f設置為私有變量,並使用吸氣劑m()f() 插入映射后,修改映射中的ItemKey實例可能不是一個好主意。

這種方法的潛在問題

您的方法存在的問題之一是,這將取決於添加元素的順序。 考慮添加以下幾對: (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

對於多集變體:

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的元素數。這對於以下情況可能是有利的:您想問“我有多少點在我的新點的公差范圍內? “

祝好運!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM