【问题标题】:Iterating over std::map with custom less operator implementation gives less elements that it contains使用自定义 less 运算符实现迭代 std::map 会减少它包含的元素
【发布时间】:2020-03-18 06:27:39
【问题描述】:

假设我有以下简单程序 (http://cpp.sh/5sygh):

#include <map>
#include <iostream>

using Key = std::pair<unsigned long, unsigned long long>;

struct KeyLess {
  bool operator()(const Key& lhs, const Key& rhs) {
      if (lhs.first < rhs.first) {
        return  true;
      }

      if (lhs.second < rhs.second) {
        return true;
      }

      return false;
  }
};

int main() {
    std::map< Key , int, KeyLess> m;
    m[Key{2, 169}] = 1;
    m[Key{1, 255}] = 2;
    m[Key{1, 391}] = 3;
    m[Key{1, 475}] = 4;

    std::cout << "Elements in map: " << m.size() << std::endl;     
    for(const auto &x: m) {
        std::cout <<"Value: "<< x.second << std::endl;
    }
}

输出仅包含 2 个项目,而不是地图中的 4 个:

Elements in map: 4
Value: 2
Value: 1

我错过了什么?

【问题讨论】:

    标签: c++ dictionary c++11 stdmap ranged-loops


    【解决方案1】:

    您的 less 运算符应该是:

    struct KeyLess {
      bool operator()(const Key& lhs, const Key& rhs) {
          if (lhs.first < rhs.first) {
            return  true;
          }
    
          if (lhs.first == rhs.first && lhs.second < rhs.second) {
            return true;
          }
    
          return false;
      }
    };
    

    当您将结构与多个元素进行比较时,将结构视为单词,将元素视为字符可能会有所帮助。

    通过这种修改,less 操作符按字典顺序工作,当你对它们进行排序时比较两个相同长度的单词的方式:你在下一个位置继续比较,而单词在当前位置具有相同的字符,并决定何时当前位置的字符不同。如果您到达两个单词的末尾,则单词相等。

    【讨论】:

    • 虽然技术上是正确的,但如果您能解释为什么需要以这种方式进行比较,将会更有帮助。
    【解决方案2】:

    您的比较功能不符合strict weak ordering的要求。

    在 SWO 中,如果 A (!(a<b) && !(b<a)) 则a == b。两个键不应该都小于对方。

    为您的密钥和使用您的比较功能

    Key{2, 169} < Key{1, 255} // this is true because 169 < 255
    Key{1, 255} < Key{2, 169} // this is also true because 1 < 2
    

    显然这是一个问题,因为这两个键使用比较器进行的比较少于彼此。

    我建议的解决方案:由于您的键是std::pairs,因此您不需要定义新的比较器。 std::pair 已经默认使用字典比较。

    【讨论】:

      【解决方案3】:

      您可以通过使用std::tie 来隐藏比较器的复杂性并解决错误(@MarkoMahnič 已解释)。

      bool operator()(const Key& lhs, const Key& rhs)
      {
         return std::tie(lhs.first, lhs.second) < std::tie(rhs.first, rhs.second);
      }
      

      【讨论】:

        【解决方案4】:

        您的比较器不符合std::map 的要求,需要提供strict weak ordering。幸运的是,如果您需要比较多个值,std::tuple 会为您实现这一点:

        struct KeyLess {
          bool operator()(const Key& lhs, const Key& rhs) {
              return std::tie(lhs.first, lhs.second) < std::tie(rhs.first, rhs.second);
          }
        };
        

        在您的情况下,您实际上根本不需要自定义比较器,因为 std::pair&lt; 运算符已经具有相同的行为。

        【讨论】:

          【解决方案5】:

          您的KeyLess 代码导致比较不正确:

          KeyLess cmp;
          std::cout << cmp(Key{2, 169},Key{1, 391})<< std::endl;  // yields true
          std::cout << cmp(Key{1, 391},Key{2, 169})<< std::endl;  // yields true
          

          当两个比较结果为假时,这意味着键相等,当它们为真时,映射迭代器的行为没有定义。这与 map 对其元素进行排序这一事实有关。

          请注意,operator() 必须是 const,否则程序可能无法使用标准 C++17 及更高版本进行编译。作为一种可能的变体:

          #include <map>
          #include <iostream>
          
          using Key = std::pair<unsigned long, unsigned long long>;
          
          struct KeyLess {
            bool operator()(const Key& lhs, const Key& rhs) const {
                if (lhs.first < rhs.first) {
                  return  true;
                }
                else  if (lhs.first > rhs.first)
                    return  false;
          
                if (lhs.second < rhs.second) {
                  return true;
                }
          
                return false;
            }
          };
          
          
          int main() 
          {
              std::map< Key , int, KeyLess > m;
              m[Key{2, 169}] = 1;
              m[Key{1, 255}] = 2;
              m[Key{1, 391}] = 3;
              m[Key{1, 475}] = 4;
          
              std::cout << "Elements in map: " << m.size() << std::endl;     
              for(const auto &[key, value]: m) {
                  std::cout << "Key: " << key.first << ", " << key.second << " Value: "<< value << std::endl;
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-28
            • 1970-01-01
            • 2017-08-20
            • 1970-01-01
            • 2011-04-12
            • 1970-01-01
            相关资源
            最近更新 更多